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.AggregateKeyProperty: exp.Properties.Location.POST_SCHEMA, 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.PhysicalProperty: exp.Properties.Location.UNSUPPORTED, 778 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 779 exp.Property: exp.Properties.Location.POST_WITH, 780 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 781 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 782 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 783 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 784 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 785 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 786 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 787 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 788 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 789 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 790 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 791 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 792 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 793 exp.Set: exp.Properties.Location.POST_SCHEMA, 794 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 795 exp.SetProperty: exp.Properties.Location.POST_CREATE, 796 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 797 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 798 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 799 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 800 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 801 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 802 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 803 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 804 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 805 exp.StorageProperty: exp.Properties.Location.UNSUPPORTED, 806 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 807 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 808 exp.Tags: exp.Properties.Location.POST_WITH, 809 exp.TableSpaceProperty: exp.Properties.Location.UNSUPPORTED, 810 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 811 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 812 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 813 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 814 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 815 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 816 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 817 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 818 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 819 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 820 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 821 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 822 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 823 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 824 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 825 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 826 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 827 } 828 829 # Keywords that can't be used as unquoted identifier names 830 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 831 832 # Exprs whose comments are separated from them for better formatting 833 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 834 exp.Command, 835 exp.Create, 836 exp.Describe, 837 exp.Delete, 838 exp.Drop, 839 exp.From, 840 exp.Insert, 841 exp.Join, 842 exp.MultitableInserts, 843 exp.Order, 844 exp.Group, 845 exp.Having, 846 exp.Select, 847 exp.SetOperation, 848 exp.Update, 849 exp.Where, 850 exp.With, 851 ) 852 853 # Exprs that should not have their comments generated in maybe_comment 854 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 855 exp.Binary, 856 exp.SetOperation, 857 ) 858 859 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 860 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 861 exp.Column, 862 exp.Literal, 863 exp.Neg, 864 exp.Paren, 865 ) 866 867 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 868 exp.DType.NVARCHAR, 869 exp.DType.VARCHAR, 870 exp.DType.CHAR, 871 exp.DType.NCHAR, 872 } 873 874 # Exprs that need to have all CTEs under them bubbled up to them 875 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 876 877 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 878 879 MOD_OPERATOR = "%" 880 881 # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses 882 MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 883 exp.Mul, 884 exp.Div, 885 exp.IntDiv, 886 exp.Mod, 887 ) 888 889 # ALTER options that follow the others without a comma, e.g. MySQL's 890 # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ... 891 ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 892 893 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 894 895 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 896 897 __slots__ = ( 898 "pretty", 899 "identify", 900 "normalize", 901 "pad", 902 "_indent", 903 "normalize_functions", 904 "unsupported_level", 905 "max_unsupported", 906 "leading_comma", 907 "max_text_width", 908 "comments", 909 "dialect", 910 "unsupported_messages", 911 "_escaped_quote_end", 912 "_escaped_byte_quote_end", 913 "_escaped_identifier_end", 914 "_identifier_escaped_sequences", 915 "_next_name", 916 "_identifier_start", 917 "_identifier_end", 918 "_quote_json_path_key_using_brackets", 919 "_dispatch", 920 ) 921 922 def __init__( 923 self, 924 pretty: bool | int | None = None, 925 identify: str | bool = False, 926 normalize: bool = False, 927 pad: int = 2, 928 indent: int = 2, 929 normalize_functions: str | bool | None = None, 930 unsupported_level: ErrorLevel = ErrorLevel.WARN, 931 max_unsupported: int = 3, 932 leading_comma: bool = False, 933 max_text_width: int = 80, 934 comments: bool = True, 935 dialect: DialectType = None, 936 ): 937 import sqlglot 938 import sqlglot.dialects.dialect 939 940 self.pretty = pretty if pretty is not None else sqlglot.pretty 941 self.identify = identify 942 self.normalize = normalize 943 self.pad = pad 944 self._indent = indent 945 self.unsupported_level = unsupported_level 946 self.max_unsupported = max_unsupported 947 self.leading_comma = leading_comma 948 self.max_text_width = max_text_width 949 self.comments = comments 950 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 951 952 # This is both a Dialect property and a Generator argument, so we prioritize the latter 953 self.normalize_functions = ( 954 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 955 ) 956 957 self.unsupported_messages: list[str] = [] 958 self._escaped_quote_end: str = ( 959 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 960 ) 961 self._escaped_byte_quote_end: str = ( 962 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 963 if self.dialect.BYTE_END 964 else "" 965 ) 966 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 967 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 968 969 self._next_name = name_sequence("_t") 970 971 self._identifier_start = self.dialect.IDENTIFIER_START 972 self._identifier_end = self.dialect.IDENTIFIER_END 973 974 self._quote_json_path_key_using_brackets = True 975 976 cls = type(self) 977 dispatch = _DISPATCH_CACHE.get(cls) 978 if dispatch is None: 979 dispatch = _build_dispatch(cls) 980 _DISPATCH_CACHE[cls] = dispatch 981 self._dispatch = dispatch 982 983 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 984 """ 985 Generates the SQL string corresponding to the given syntax tree. 986 987 Args: 988 expression: The syntax tree. 989 copy: Whether to copy the expression. The generator performs mutations so 990 it is safer to copy. 991 992 Returns: 993 The SQL string corresponding to `expression`. 994 """ 995 if copy: 996 expression = expression.copy() 997 998 expression = self.preprocess(expression) 999 1000 self.unsupported_messages = [] 1001 sql = self.sql(expression).strip() 1002 1003 if self.pretty: 1004 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1005 1006 if self.unsupported_level == ErrorLevel.IGNORE: 1007 return sql 1008 1009 if self.unsupported_level == ErrorLevel.WARN: 1010 for msg in self.unsupported_messages: 1011 logger.warning(msg) 1012 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1013 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1014 1015 return sql 1016 1017 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1018 """Apply generic preprocessing transformations to a given expression.""" 1019 expression = self._move_ctes_to_top_level(expression) 1020 1021 if self.ENSURE_BOOLS: 1022 import sqlglot.transforms 1023 1024 expression = sqlglot.transforms.ensure_bools(expression) 1025 1026 return expression 1027 1028 def _move_ctes_to_top_level(self, expression: E) -> E: 1029 if ( 1030 not expression.parent 1031 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 1032 and any(node.parent is not expression for node in expression.find_all(exp.With)) 1033 ): 1034 import sqlglot.transforms 1035 1036 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 1037 return expression 1038 1039 def unsupported(self, message: str) -> None: 1040 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1041 raise UnsupportedError(message) 1042 self.unsupported_messages.append(message) 1043 1044 def sep(self, sep: str = " ") -> str: 1045 return f"{sep.strip()}\n" if self.pretty else sep 1046 1047 def seg(self, sql: str, sep: str = " ") -> str: 1048 return f"{self.sep(sep)}{sql}" 1049 1050 def sanitize_comment(self, comment: str) -> str: 1051 comment = " " + comment if comment[0].strip() else comment 1052 comment = comment + " " if comment[-1].strip() else comment 1053 1054 # Escape block comment markers to prevent premature closure or unintended nesting. 1055 # This is necessary because single-line comments (--) are converted to block comments 1056 # (/* */) on output, and any */ in the original text would close the comment early. 1057 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1058 1059 return comment 1060 1061 def maybe_comment( 1062 self, 1063 sql: str, 1064 expression: exp.Expr | None = None, 1065 comments: list[str] | None = None, 1066 separated: bool = False, 1067 ) -> str: 1068 comments = ( 1069 ((expression and expression.comments) if comments is None else comments) # type: ignore 1070 if self.comments 1071 else None 1072 ) 1073 1074 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1075 return sql 1076 1077 comments_list = [ 1078 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1079 for comment in comments 1080 if comment 1081 ] 1082 1083 if not comments_list: 1084 return sql 1085 1086 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1087 comments_sql = self.sep().join(comments_list) 1088 return ( 1089 f"{self.sep()}{comments_sql}{sql}" 1090 if not sql or sql[0].isspace() 1091 else f"{comments_sql}{self.sep()}{sql}" 1092 ) 1093 1094 return f"{sql} {' '.join(comments_list)}" 1095 1096 def wrap(self, expression: exp.Expr | str) -> str: 1097 this_sql = ( 1098 self.sql(expression) 1099 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1100 else self.sql(expression, "this") 1101 ) 1102 if not this_sql: 1103 return "()" 1104 1105 this_sql = self.indent(this_sql, level=1, pad=0) 1106 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1107 1108 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1109 original = self.identify 1110 self.identify = False 1111 result = func(*args, **kwargs) 1112 self.identify = original 1113 return result 1114 1115 def normalize_func(self, name: str) -> str: 1116 if self.normalize_functions == "upper" or self.normalize_functions is True: 1117 return name.upper() 1118 if self.normalize_functions == "lower": 1119 return name.lower() 1120 return name 1121 1122 def indent( 1123 self, 1124 sql: str, 1125 level: int = 0, 1126 pad: int | None = None, 1127 skip_first: bool = False, 1128 skip_last: bool = False, 1129 ) -> str: 1130 if not self.pretty or not sql: 1131 return sql 1132 1133 pad = self.pad if pad is None else pad 1134 lines = sql.split("\n") 1135 1136 return "\n".join( 1137 ( 1138 line 1139 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1140 else f"{' ' * (level * self._indent + pad)}{line}" 1141 ) 1142 for i, line in enumerate(lines) 1143 ) 1144 1145 def sql( 1146 self, 1147 expression: str | exp.Expr | None, 1148 key: str | None = None, 1149 comment: bool = True, 1150 ) -> str: 1151 if not expression: 1152 return "" 1153 1154 if isinstance(expression, str): 1155 return expression 1156 1157 if key: 1158 value = expression.args.get(key) 1159 if value: 1160 return self.sql(value) 1161 return "" 1162 1163 handler = self._dispatch.get(expression.__class__) 1164 1165 if handler: 1166 sql = handler(self, expression) 1167 elif isinstance(expression, exp.Func): 1168 sql = self.function_fallback_sql(expression) 1169 elif isinstance(expression, exp.Property): 1170 sql = self.property_sql(expression) 1171 else: 1172 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1173 1174 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1175 1176 def uncache_sql(self, expression: exp.Uncache) -> str: 1177 table = self.sql(expression, "this") 1178 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1179 return f"UNCACHE TABLE{exists_sql} {table}" 1180 1181 def cache_sql(self, expression: exp.Cache) -> str: 1182 lazy = " LAZY" if expression.args.get("lazy") else "" 1183 table = self.sql(expression, "this") 1184 options = expression.args.get("options") 1185 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1186 sql = self.sql(expression, "expression") 1187 sql = f" AS{self.sep()}{sql}" if sql else "" 1188 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1189 return self.prepend_ctes(expression, sql) 1190 1191 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1192 default = "DEFAULT " if expression.args.get("default") else "" 1193 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1194 1195 def column_parts(self, expression: exp.Column) -> str: 1196 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1197 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1198 return self.sql(expression, "this") 1199 1200 return ".".join( 1201 self.sql(part) 1202 for part in ( 1203 expression.args.get("catalog"), 1204 expression.args.get("db"), 1205 expression.args.get("table"), 1206 expression.args.get("this"), 1207 ) 1208 if part 1209 ) 1210 1211 def column_sql(self, expression: exp.Column) -> str: 1212 join_mark = " (+)" if expression.args.get("join_mark") else "" 1213 1214 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1215 join_mark = "" 1216 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1217 1218 return f"{self.column_parts(expression)}{join_mark}" 1219 1220 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1221 return self.column_sql(expression) 1222 1223 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1224 this = self.sql(expression, "this") 1225 this = f" {this}" if this else "" 1226 position = self.sql(expression, "position") 1227 return f"{position}{this}" 1228 1229 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1230 column = self.sql(expression, "this") 1231 kind = self.sql(expression, "kind") 1232 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1233 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1234 kind = f"{sep}{kind}" if kind else "" 1235 constraints = f" {constraints}" if constraints else "" 1236 position = self.sql(expression, "position") 1237 position = f" {position}" if position else "" 1238 1239 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1240 kind = "" 1241 1242 return f"{exists}{column}{kind}{constraints}{position}" 1243 1244 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1245 this = self.sql(expression, "this") 1246 kind_sql = self.sql(expression, "kind").strip() 1247 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1248 1249 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1250 this = self.sql(expression, "this") 1251 if expression.args.get("not_null"): 1252 persisted = " PERSISTED NOT NULL" 1253 elif expression.args.get("persisted"): 1254 persisted = " PERSISTED" 1255 else: 1256 persisted = "" 1257 1258 return f"AS {this}{persisted}" 1259 1260 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1261 return self.token_sql(TokenType.AUTO_INCREMENT) 1262 1263 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1264 if isinstance(expression.this, list): 1265 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1266 else: 1267 this = self.sql(expression, "this") 1268 1269 return f"COMPRESS {this}" 1270 1271 def generatedasidentitycolumnconstraint_sql( 1272 self, expression: exp.GeneratedAsIdentityColumnConstraint 1273 ) -> str: 1274 this = "" 1275 if expression.this is not None: 1276 on_null = " ON NULL" if expression.args.get("on_null") else "" 1277 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1278 1279 start = expression.args.get("start") 1280 start = f"START WITH {start}" if start else "" 1281 increment = expression.args.get("increment") 1282 increment = f" INCREMENT BY {increment}" if increment else "" 1283 minvalue = expression.args.get("minvalue") 1284 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1285 maxvalue = expression.args.get("maxvalue") 1286 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1287 cycle = expression.args.get("cycle") 1288 cycle_sql = "" 1289 1290 if cycle is not None: 1291 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1292 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1293 1294 sequence_opts = "" 1295 if start or increment or cycle_sql: 1296 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1297 sequence_opts = f" ({sequence_opts.strip()})" 1298 1299 expr = self.sql(expression, "expression") 1300 expr = f"({expr})" if expr else "IDENTITY" 1301 1302 return f"GENERATED{this} AS {expr}{sequence_opts}" 1303 1304 def generatedasrowcolumnconstraint_sql( 1305 self, expression: exp.GeneratedAsRowColumnConstraint 1306 ) -> str: 1307 start = "START" if expression.args.get("start") else "END" 1308 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1309 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1310 1311 def periodforsystemtimeconstraint_sql( 1312 self, expression: exp.PeriodForSystemTimeConstraint 1313 ) -> str: 1314 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1315 1316 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1317 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1318 1319 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1320 desc = expression.args.get("desc") 1321 if desc is not None: 1322 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1323 options = self.expressions(expression, key="options", flat=True, sep=" ") 1324 options = f" {options}" if options else "" 1325 return f"PRIMARY KEY{options}" 1326 1327 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1328 this = self.sql(expression, "this") 1329 this = f" {this}" if this else "" 1330 index_type = expression.args.get("index_type") 1331 index_type = f" USING {index_type}" if index_type else "" 1332 on_conflict = self.sql(expression, "on_conflict") 1333 on_conflict = f" {on_conflict}" if on_conflict else "" 1334 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1335 options = self.expressions(expression, key="options", flat=True, sep=" ") 1336 options = f" {options}" if options else "" 1337 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1338 1339 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1340 input_ = expression.args.get("input_") 1341 output = expression.args.get("output") 1342 variadic = expression.args.get("variadic") 1343 1344 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1345 if variadic: 1346 return "VARIADIC" 1347 1348 if input_ and output: 1349 return f"IN{self.INOUT_SEPARATOR}OUT" 1350 if input_: 1351 return "IN" 1352 if output: 1353 return "OUT" 1354 1355 return "" 1356 1357 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1358 return self.sql(expression, "this") 1359 1360 def create_sql(self, expression: exp.Create) -> str: 1361 kind = self.sql(expression, "kind") 1362 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1363 1364 properties = expression.args.get("properties") 1365 1366 if ( 1367 kind == "TRIGGER" 1368 and properties 1369 and properties.expressions 1370 and isinstance(properties.expressions[0], exp.TriggerProperties) 1371 and properties.expressions[0].args.get("constraint") 1372 ): 1373 kind = f"CONSTRAINT {kind}" 1374 1375 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1376 1377 this = self.createable_sql(expression, properties_locs) 1378 1379 properties_sql = "" 1380 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1381 exp.Properties.Location.POST_WITH 1382 ): 1383 props_ast = exp.Properties( 1384 expressions=[ 1385 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1386 *properties_locs[exp.Properties.Location.POST_WITH], 1387 ] 1388 ) 1389 props_ast.parent = expression 1390 properties_sql = self.sql(props_ast) 1391 1392 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1393 properties_sql = self.sep() + properties_sql 1394 elif not self.pretty: 1395 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1396 properties_sql = f" {properties_sql}" 1397 1398 begin = " BEGIN" if expression.args.get("begin") else "" 1399 1400 expression_sql = self.sql(expression, "expression") 1401 if expression_sql: 1402 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1403 1404 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1405 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1406 ): 1407 postalias_props_sql = "" 1408 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1409 postalias_props_sql = self.properties( 1410 exp.Properties( 1411 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1412 ), 1413 wrapped=False, 1414 ) 1415 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1416 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1417 1418 postindex_props_sql = "" 1419 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1420 postindex_props_sql = self.properties( 1421 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1422 wrapped=False, 1423 prefix=" ", 1424 ) 1425 1426 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1427 indexes = f" {indexes}" if indexes else "" 1428 index_sql = indexes + postindex_props_sql 1429 1430 replace = " OR REPLACE" if expression.args.get("replace") else "" 1431 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1432 unique = " UNIQUE" if expression.args.get("unique") else "" 1433 1434 clustered = expression.args.get("clustered") 1435 if clustered is None: 1436 clustered_sql = "" 1437 elif clustered: 1438 clustered_sql = " CLUSTERED COLUMNSTORE" 1439 else: 1440 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1441 1442 postcreate_props_sql = "" 1443 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1444 postcreate_props_sql = self.properties( 1445 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1446 sep=" ", 1447 prefix=" ", 1448 wrapped=False, 1449 ) 1450 1451 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1452 1453 postexpression_props_sql = "" 1454 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1455 postexpression_props_sql = self.properties( 1456 exp.Properties( 1457 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1458 ), 1459 sep=" ", 1460 prefix=" ", 1461 wrapped=False, 1462 ) 1463 1464 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1465 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1466 no_schema_binding = ( 1467 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1468 ) 1469 1470 clone = self.sql(expression, "clone") 1471 clone = f" {clone}" if clone else "" 1472 1473 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1474 properties_expression = f"{expression_sql}{properties_sql}" 1475 else: 1476 properties_expression = f"{properties_sql}{expression_sql}" 1477 1478 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1479 return self.prepend_ctes(expression, expression_sql) 1480 1481 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1482 start = self.sql(expression, "start") 1483 start = f"START WITH {start}" if start else "" 1484 increment = self.sql(expression, "increment") 1485 increment = f" INCREMENT BY {increment}" if increment else "" 1486 minvalue = self.sql(expression, "minvalue") 1487 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1488 maxvalue = self.sql(expression, "maxvalue") 1489 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1490 owned = self.sql(expression, "owned") 1491 owned = f" OWNED BY {owned}" if owned else "" 1492 1493 cache = expression.args.get("cache") 1494 if cache is None: 1495 cache_str = "" 1496 elif cache is True: 1497 cache_str = " CACHE" 1498 else: 1499 cache_str = f" CACHE {cache}" 1500 1501 options = self.expressions(expression, key="options", flat=True, sep=" ") 1502 options = f" {options}" if options else "" 1503 1504 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1505 1506 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1507 timing = expression.args.get("timing", "") 1508 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1509 timing_events = f"{timing} {events}".strip() if timing or events else "" 1510 1511 parts = [timing_events, "ON", self.sql(expression, "table")] 1512 1513 if referenced_table := expression.args.get("referenced_table"): 1514 parts.extend(["FROM", self.sql(referenced_table)]) 1515 1516 if deferrable := expression.args.get("deferrable"): 1517 parts.append(deferrable) 1518 1519 if initially := expression.args.get("initially"): 1520 parts.append(f"INITIALLY {initially}") 1521 1522 if referencing := expression.args.get("referencing"): 1523 parts.append(self.sql(referencing)) 1524 1525 if for_each := expression.args.get("for_each"): 1526 parts.append(f"FOR EACH {for_each}") 1527 1528 if when := expression.args.get("when"): 1529 parts.append(f"WHEN ({self.sql(when)})") 1530 1531 parts.append(self.sql(expression, "execute")) 1532 1533 return self.sep().join(parts) 1534 1535 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1536 parts = [] 1537 1538 if old_alias := expression.args.get("old"): 1539 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1540 1541 if new_alias := expression.args.get("new"): 1542 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1543 1544 return f"REFERENCING {' '.join(parts)}" 1545 1546 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1547 columns = expression.args.get("columns") 1548 if columns: 1549 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1550 1551 return self.sql(expression, "this") 1552 1553 def clone_sql(self, expression: exp.Clone) -> str: 1554 this = self.sql(expression, "this") 1555 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1556 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1557 return f"{shallow}{keyword} {this}" 1558 1559 def describe_sql(self, expression: exp.Describe) -> str: 1560 style = expression.args.get("style") 1561 style = f" {style}" if style else "" 1562 partition = self.sql(expression, "partition") 1563 partition = f" {partition}" if partition else "" 1564 format = self.sql(expression, "format") 1565 format = f" {format}" if format else "" 1566 as_json = " AS JSON" if expression.args.get("as_json") else "" 1567 1568 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1569 1570 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1571 tag = self.sql(expression, "tag") 1572 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1573 1574 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1575 with_ = self.sql(expression, "with_") 1576 if with_: 1577 sql = f"{with_}{self.sep()}{sql}" 1578 return sql 1579 1580 def with_sql(self, expression: exp.With) -> str: 1581 udfs = self.expressions(expression, key="udfs", flat=True) 1582 udfs = f"WITH {udfs}" if udfs else "" 1583 1584 sql = self.expressions(expression, flat=True) 1585 1586 recursive = ( 1587 "RECURSIVE " 1588 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1589 else "" 1590 ) 1591 search = self.sql(expression, "search") 1592 search = f" {search}" if search else "" 1593 cycle = self.sql(expression, "cycle") 1594 cycle = f" {cycle}" if cycle else "" 1595 1596 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1597 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1598 1599 def cte_sql(self, expression: exp.CTE) -> str: 1600 alias = expression.args.get("alias") 1601 if alias: 1602 alias.add_comments(expression.pop_comments()) 1603 1604 alias_sql = self.sql(expression, "alias") 1605 1606 materialized = expression.args.get("materialized") 1607 if materialized is False: 1608 materialized = "NOT MATERIALIZED " 1609 elif materialized: 1610 materialized = "MATERIALIZED " 1611 1612 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1613 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1614 1615 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1616 1617 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1618 alias = self.sql(expression, "this") 1619 columns = self.expressions(expression, key="columns", flat=True) 1620 columns = f"({columns})" if columns else "" 1621 1622 if ( 1623 columns 1624 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1625 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1626 ): 1627 columns = "" 1628 self.unsupported("Named columns are not supported in table alias.") 1629 1630 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1631 alias = self._next_name() 1632 1633 return f"{alias}{columns}" 1634 1635 def bitstring_sql(self, expression: exp.BitString) -> str: 1636 this = self.sql(expression, "this") 1637 if self.dialect.BIT_START: 1638 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1639 return f"{int(this, 2)}" 1640 1641 def hexstring_sql( 1642 self, expression: exp.HexString, binary_function_repr: str | None = None 1643 ) -> str: 1644 this = self.sql(expression, "this") 1645 is_integer_type = expression.args.get("is_integer") 1646 1647 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1648 not self.dialect.HEX_START and not binary_function_repr 1649 ): 1650 # Integer representation will be returned if: 1651 # - The read dialect treats the hex value as integer literal but not the write 1652 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1653 return f"{int(this, 16)}" 1654 1655 if not is_integer_type: 1656 # Read dialect treats the hex value as BINARY/BLOB 1657 if binary_function_repr: 1658 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1659 return self.func(binary_function_repr, exp.Literal.string(this)) 1660 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1661 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1662 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1663 1664 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1665 1666 def bytestring_sql(self, expression: exp.ByteString) -> str: 1667 this = self.sql(expression, "this") 1668 is_bytes = bool(expression.args.get("is_bytes")) 1669 if is_bytes and self.BYTE_STRING_FUNCTION: 1670 # Hex digits can be quoted directly without escaping. 1671 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1672 1673 if self.dialect.BYTE_START: 1674 escaped_byte_string = self.escape_str( 1675 this, 1676 escape_backslash=bool( 1677 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1678 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1679 ), 1680 delimiter=self.dialect.BYTE_END, 1681 escaped_delimiter=self._escaped_byte_quote_end, 1682 is_byte_string=True, 1683 is_bytes=is_bytes, 1684 ) 1685 delimited_byte_string = ( 1686 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1687 ) 1688 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1689 return self.sql( 1690 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1691 ) 1692 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1693 return self.sql( 1694 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1695 ) 1696 1697 return delimited_byte_string 1698 1699 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1700 return self.sql(exp.Literal.string(this)) 1701 1702 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1703 return "" 1704 1705 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1706 this = self.sql(expression, "this") 1707 escape = expression.args.get("escape") 1708 unicode_start = self.dialect.UNICODE_START 1709 1710 if unicode_start: 1711 escape_substitute = r"\\\1" 1712 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1713 else: 1714 escape_substitute = r"\\u\1" 1715 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1716 1717 if escape: 1718 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1719 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1720 else: 1721 escape_pattern = ESCAPED_UNICODE_RE 1722 escape_sql = "" 1723 1724 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1725 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1726 1727 if unicode_start: 1728 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1729 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1730 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1731 else: 1732 this = self.escape_str(this, escape_backslash=False) 1733 1734 return f"{left_quote}{this}{right_quote}{escape_sql}" 1735 1736 def rawstring_sql(self, expression: exp.RawString) -> str: 1737 string = expression.this 1738 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1739 string = string.replace("\\", "\\\\") 1740 1741 string = self.escape_str(string, escape_backslash=False) 1742 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1743 1744 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1745 this = self.sql(expression, "this") 1746 specifier = self.sql(expression, "expression") 1747 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1748 return f"{this}{specifier}" 1749 1750 def datatype_param_bound_limiter( 1751 self, 1752 expression: exp.DataType, 1753 type_value: exp.DType, 1754 defaults: tuple[int, ...], 1755 bounds: tuple[int | None, ...], 1756 ) -> exp.DataType: 1757 params = expression.expressions 1758 1759 if not params: 1760 if defaults: 1761 expression.set( 1762 "expressions", 1763 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1764 ) 1765 return expression 1766 1767 if not bounds: 1768 return expression 1769 1770 for i, param in enumerate(params): 1771 bound = bounds[i] if i < len(bounds) else None 1772 if bound is None: 1773 continue 1774 1775 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1776 value = ( 1777 param_value.to_py() 1778 if isinstance(param_value, exp.Literal) and param_value.is_number 1779 else None 1780 ) 1781 if isinstance(value, (int, Decimal)) and value > bound: 1782 self.unsupported( 1783 f"{type_value.value} parameter {param_value.name} exceeds " 1784 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1785 ) 1786 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1787 1788 return expression 1789 1790 def datatype_sql(self, expression: exp.DataType) -> str: 1791 nested = "" 1792 values = "" 1793 1794 expr_nested = expression.args.get("nested") 1795 type_value = expression.this 1796 1797 if ( 1798 not expr_nested 1799 and isinstance(type_value, exp.DType) 1800 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1801 ): 1802 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1803 1804 interior = ( 1805 self.expressions( 1806 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1807 ) 1808 if expr_nested and self.pretty 1809 else self.expressions(expression, flat=True) 1810 ) 1811 1812 if type_value in self.UNSUPPORTED_TYPES: 1813 self.unsupported( 1814 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1815 ) 1816 1817 type_sql: t.Any = "" 1818 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1819 type_sql = self.sql(expression, "kind") 1820 elif type_value == exp.DType.CHARACTER_SET: 1821 char = f"CHAR({interior})" if interior else "CHAR" 1822 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1823 else: 1824 type_sql = ( 1825 self.TYPE_MAPPING.get(type_value, type_value.value) 1826 if isinstance(type_value, exp.DType) 1827 else type_value 1828 ) 1829 1830 if interior: 1831 if expr_nested: 1832 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1833 if expression.args.get("values") is not None: 1834 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1835 values = self.expressions(expression, key="values", flat=True) 1836 values = f"{delimiters[0]}{values}{delimiters[1]}" 1837 elif type_value == exp.DType.INTERVAL: 1838 nested = f" {interior}" 1839 else: 1840 nested = f"({interior})" 1841 1842 type_sql = f"{type_sql}{nested}{values}" 1843 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1844 exp.DType.TIMETZ, 1845 exp.DType.TIMESTAMPTZ, 1846 ): 1847 type_sql = f"{type_sql} WITH TIME ZONE" 1848 1849 collate = self.sql(expression, "collate") 1850 if collate: 1851 type_sql = f"{type_sql} COLLATE {collate}" 1852 1853 return type_sql 1854 1855 def directory_sql(self, expression: exp.Directory) -> str: 1856 local = "LOCAL " if expression.args.get("local") else "" 1857 row_format = self.sql(expression, "row_format") 1858 row_format = f" {row_format}" if row_format else "" 1859 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1860 1861 def delete_sql(self, expression: exp.Delete) -> str: 1862 hint = self.sql(expression, "hint") 1863 this = self.sql(expression, "this") 1864 this = f" FROM {this}" if this else "" 1865 using = self.expressions(expression, key="using") 1866 using = f" USING {using}" if using else "" 1867 cluster = self.sql(expression, "cluster") 1868 cluster = f" {cluster}" if cluster else "" 1869 where = self.sql(expression, "where") 1870 returning = self.sql(expression, "returning") 1871 order = self.sql(expression, "order") 1872 limit = self.sql(expression, "limit") 1873 tables = self.expressions(expression, key="tables") 1874 tables = f" {tables}" if tables else "" 1875 if self.RETURNING_END: 1876 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1877 else: 1878 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1879 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1880 1881 def drop_sql(self, expression: exp.Drop) -> str: 1882 tables = self.expressions(expression, key="tables", flat=True) 1883 expressions = self.expressions(expression, flat=True) 1884 expressions = f" ({expressions})" if expressions else "" 1885 kind = expression.args["kind"] 1886 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1887 iceberg = ( 1888 " ICEBERG" 1889 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1890 else "" 1891 ) 1892 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1893 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1894 on_cluster = self.sql(expression, "cluster") 1895 on_cluster = f" {on_cluster}" if on_cluster else "" 1896 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1897 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1898 cascade = " CASCADE" if expression.args.get("cascade") else "" 1899 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1900 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1901 purge = " PURGE" if expression.args.get("purge") else "" 1902 sync = " SYNC" if expression.args.get("sync") else "" 1903 force = " FORCE" if expression.args.get("force") else "" 1904 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1905 1906 def set_operation(self, expression: exp.SetOperation) -> str: 1907 op_type = type(expression) 1908 op_name = op_type.key.upper() 1909 1910 distinct = expression.args.get("distinct") 1911 if ( 1912 distinct is False 1913 and op_type in (exp.Except, exp.Intersect) 1914 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1915 ): 1916 self.unsupported(f"{op_name} ALL is not supported") 1917 1918 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1919 1920 if distinct is None: 1921 distinct = default_distinct 1922 if distinct is None: 1923 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1924 1925 if distinct is default_distinct: 1926 distinct_or_all = "" 1927 else: 1928 distinct_or_all = " DISTINCT" if distinct else " ALL" 1929 1930 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1931 side_kind = f"{side_kind} " if side_kind else "" 1932 1933 by_name = " BY NAME" if expression.args.get("by_name") else "" 1934 on = self.expressions(expression, key="on", flat=True) 1935 on = f" ON ({on})" if on else "" 1936 1937 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1938 1939 def set_operations(self, expression: exp.SetOperation) -> str: 1940 if not self.SET_OP_MODIFIERS: 1941 limit = expression.args.get("limit") 1942 order = expression.args.get("order") 1943 offset = expression.args.get("offset") 1944 1945 if limit or order or offset: 1946 select = self._move_ctes_to_top_level( 1947 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1948 ) 1949 1950 for arg in ("limit", "order", "offset"): 1951 if value := expression.args.get(arg): 1952 select.set(arg, value.pop()) 1953 return self.sql(select) 1954 1955 sqls: list[str] = [] 1956 stack: list[str | exp.Expr] = [expression] 1957 1958 while stack: 1959 node = stack.pop() 1960 1961 if isinstance(node, exp.SetOperation): 1962 stack.append(node.expression) 1963 stack.append( 1964 self.maybe_comment( 1965 self.set_operation(node), comments=node.comments, separated=True 1966 ) 1967 ) 1968 stack.append(node.this) 1969 else: 1970 if ( 1971 not self.SET_OP_LIMITS 1972 and isinstance(node, exp.Select) 1973 and node.args.get("limit") 1974 ): 1975 node = node.subquery(copy=False) 1976 if not self.SET_OP_PARENTHESIZED_OPERANDS: 1977 node = exp.select("*").from_(node, copy=False) 1978 sqls.append(self.sql(node)) 1979 1980 this = self.sep().join(sqls) 1981 this = self.query_modifiers(expression, this) 1982 return self.prepend_ctes(expression, this) 1983 1984 def fetch_sql(self, expression: exp.Fetch) -> str: 1985 direction = expression.args.get("direction") 1986 direction = f" {direction}" if direction else "" 1987 count = self.sql(expression, "count") 1988 count = f" {count}" if count else "" 1989 limit_options = self.sql(expression, "limit_options") 1990 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1991 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1992 1993 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1994 percent = " PERCENT" if expression.args.get("percent") else "" 1995 rows = " ROWS" if expression.args.get("rows") else "" 1996 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1997 if not with_ties and rows: 1998 with_ties = " ONLY" 1999 return f"{percent}{rows}{with_ties}" 2000 2001 def filter_sql(self, expression: exp.Filter) -> str: 2002 this = self.sql(expression, "this") 2003 where = self.sql(expression, "expression").strip() 2004 return f"{this} FILTER({where})" 2005 2006 def hint_sql(self, expression: exp.Hint) -> str: 2007 if not self.QUERY_HINTS: 2008 self.unsupported("Hints are not supported") 2009 return "" 2010 2011 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 2012 2013 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2014 using = self.sql(expression, "using") 2015 using = f" USING {using}" if using else "" 2016 columns = self.expressions(expression, key="columns", flat=True) 2017 columns = f"({columns})" if columns else "" 2018 partition_by = self.expressions(expression, key="partition_by", flat=True) 2019 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2020 where = self.sql(expression, "where") 2021 include = self.expressions(expression, key="include", flat=True) 2022 if include: 2023 include = f" INCLUDE ({include})" 2024 with_storage = self.expressions(expression, key="with_storage", flat=True) 2025 with_storage = f" WITH ({with_storage})" if with_storage else "" 2026 tablespace = self.sql(expression, "tablespace") 2027 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 2028 on = self.sql(expression, "on") 2029 on = f" ON {on}" if on else "" 2030 2031 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 2032 2033 def index_sql(self, expression: exp.Index) -> str: 2034 unique = "UNIQUE " if expression.args.get("unique") else "" 2035 primary = "PRIMARY " if expression.args.get("primary") else "" 2036 amp = "AMP " if expression.args.get("amp") else "" 2037 name = self.sql(expression, "this") 2038 name = f"{name} " if name else "" 2039 table = self.sql(expression, "table") 2040 table = f"{self.INDEX_ON} {table}" if table else "" 2041 2042 index = "INDEX " if not table else "" 2043 2044 params = self.sql(expression, "params") 2045 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 2046 2047 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2048 this = expression.this 2049 if this and this.is_string: 2050 resolved = maybe_parse(this.name).sql(self.dialect) 2051 if "expressions" in expression.args: 2052 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2053 # We can't safely emit the call to other dialects since name/arg semantics may differ 2054 self.unsupported( 2055 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2056 ) 2057 return resolved 2058 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2059 return self.func("IDENTIFIER", this) 2060 2061 def identifier_sql(self, expression: exp.Identifier) -> str: 2062 text = expression.name 2063 lower = text.lower() 2064 quoted = expression.quoted 2065 text = lower if self.normalize and not quoted else text 2066 if self._identifier_escaped_sequences: 2067 for char, escaped in self._identifier_escaped_sequences.items(): 2068 text = text.replace(char, escaped) 2069 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2070 if ( 2071 quoted 2072 or self.dialect.can_quote(expression, self.identify) 2073 or lower in self.RESERVED_KEYWORDS 2074 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2075 ): 2076 text = ( 2077 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2078 ) 2079 return text 2080 2081 def hex_sql(self, expression: exp.Hex) -> str: 2082 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2083 if self.dialect.HEX_LOWERCASE: 2084 text = self.func("LOWER", text) 2085 2086 return text 2087 2088 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2089 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2090 if not self.dialect.HEX_LOWERCASE: 2091 text = self.func("LOWER", text) 2092 return text 2093 2094 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2095 input_format = self.sql(expression, "input_format") 2096 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2097 output_format = self.sql(expression, "output_format") 2098 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2099 return self.sep().join((input_format, output_format)) 2100 2101 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2102 string = self.sql(exp.Literal.string(expression.name)) 2103 return f"{prefix}{string}" 2104 2105 def partition_sql(self, expression: exp.Partition) -> str: 2106 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2107 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2108 2109 def properties_sql(self, expression: exp.Properties) -> str: 2110 root_properties = [] 2111 with_properties = [] 2112 2113 for p in expression.expressions: 2114 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2115 if p_loc == exp.Properties.Location.POST_WITH: 2116 with_properties.append(p) 2117 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2118 root_properties.append(p) 2119 2120 root_props_ast = exp.Properties(expressions=root_properties) 2121 root_props_ast.parent = expression.parent 2122 2123 with_props_ast = exp.Properties(expressions=with_properties) 2124 with_props_ast.parent = expression.parent 2125 2126 root_props = self.root_properties(root_props_ast) 2127 with_props = self.with_properties(with_props_ast) 2128 2129 if root_props and with_props and not self.pretty: 2130 with_props = " " + with_props 2131 2132 return root_props + with_props 2133 2134 def root_properties(self, properties: exp.Properties) -> str: 2135 if properties.expressions: 2136 return self.expressions(properties, indent=False, sep=" ") 2137 return "" 2138 2139 def properties( 2140 self, 2141 properties: exp.Properties, 2142 prefix: str = "", 2143 sep: str = ", ", 2144 suffix: str = "", 2145 wrapped: bool = True, 2146 ) -> str: 2147 if properties.expressions: 2148 expressions = self.expressions(properties, sep=sep, indent=False) 2149 if expressions: 2150 expressions = self.wrap(expressions) if wrapped else expressions 2151 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2152 return "" 2153 2154 def with_properties(self, properties: exp.Properties) -> str: 2155 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2156 2157 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2158 properties_locs = defaultdict(list) 2159 for p in properties.expressions: 2160 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2161 if p_loc != exp.Properties.Location.UNSUPPORTED: 2162 properties_locs[p_loc].append(p) 2163 else: 2164 self.unsupported(f"Unsupported property {p.key}") 2165 2166 return properties_locs 2167 2168 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2169 if isinstance(expression.this, exp.Dot): 2170 return self.sql(expression, "this") 2171 return f"'{expression.name}'" if string_key else expression.name 2172 2173 def property_sql(self, expression: exp.Property) -> str: 2174 property_cls = expression.__class__ 2175 if property_cls == exp.Property: 2176 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2177 2178 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2179 if not property_name: 2180 self.unsupported(f"Unsupported property {expression.key}") 2181 2182 return f"{property_name}={self.sql(expression, 'this')}" 2183 2184 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2185 return f"UUID {self.sql(expression, 'this')}" 2186 2187 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2188 if self.SUPPORTS_CREATE_TABLE_LIKE: 2189 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2190 options = f" {options}" if options else "" 2191 2192 like = f"LIKE {self.sql(expression, 'this')}{options}" 2193 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2194 like = f"({like})" 2195 2196 return like 2197 2198 if expression.expressions: 2199 self.unsupported("Transpilation of LIKE property options is unsupported") 2200 2201 select = exp.select("*").from_(expression.this).limit(0) 2202 return f"AS {self.sql(select)}" 2203 2204 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2205 no = "NO " if expression.args.get("no") else "" 2206 protection = " PROTECTION" if expression.args.get("protection") else "" 2207 return f"{no}FALLBACK{protection}" 2208 2209 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2210 no = "NO " if expression.args.get("no") else "" 2211 local = expression.args.get("local") 2212 local = f"{local} " if local else "" 2213 dual = "DUAL " if expression.args.get("dual") else "" 2214 before = "BEFORE " if expression.args.get("before") else "" 2215 after = "AFTER " if expression.args.get("after") else "" 2216 return f"{no}{local}{dual}{before}{after}JOURNAL" 2217 2218 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2219 freespace = self.sql(expression, "this") 2220 percent = " PERCENT" if expression.args.get("percent") else "" 2221 return f"FREESPACE={freespace}{percent}" 2222 2223 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2224 if expression.args.get("default"): 2225 property = "DEFAULT" 2226 elif expression.args.get("on"): 2227 property = "ON" 2228 else: 2229 property = "OFF" 2230 return f"CHECKSUM={property}" 2231 2232 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2233 if expression.args.get("no"): 2234 return "NO MERGEBLOCKRATIO" 2235 if expression.args.get("default"): 2236 return "DEFAULT MERGEBLOCKRATIO" 2237 2238 percent = " PERCENT" if expression.args.get("percent") else "" 2239 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2240 2241 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2242 expressions = self.expressions(expression, flat=True) 2243 expressions = f"({expressions})" if expressions else "" 2244 return f"USING {self.sql(expression, 'this')}{expressions}" 2245 2246 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2247 default = expression.args.get("default") 2248 minimum = expression.args.get("minimum") 2249 maximum = expression.args.get("maximum") 2250 if default or minimum or maximum: 2251 if default: 2252 prop = "DEFAULT" 2253 elif minimum: 2254 prop = "MINIMUM" 2255 else: 2256 prop = "MAXIMUM" 2257 return f"{prop} DATABLOCKSIZE" 2258 units = expression.args.get("units") 2259 units = f" {units}" if units else "" 2260 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2261 2262 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2263 autotemp = expression.args.get("autotemp") 2264 always = expression.args.get("always") 2265 default = expression.args.get("default") 2266 manual = expression.args.get("manual") 2267 never = expression.args.get("never") 2268 2269 if autotemp is not None: 2270 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2271 elif always: 2272 prop = "ALWAYS" 2273 elif default: 2274 prop = "DEFAULT" 2275 elif manual: 2276 prop = "MANUAL" 2277 elif never: 2278 prop = "NEVER" 2279 return f"BLOCKCOMPRESSION={prop}" 2280 2281 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2282 no = expression.args.get("no") 2283 no = " NO" if no else "" 2284 concurrent = expression.args.get("concurrent") 2285 concurrent = " CONCURRENT" if concurrent else "" 2286 target = self.sql(expression, "target") 2287 target = f" {target}" if target else "" 2288 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2289 2290 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2291 if isinstance(expression.this, list): 2292 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2293 if expression.this: 2294 modulus = self.sql(expression, "this") 2295 remainder = self.sql(expression, "expression") 2296 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2297 2298 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2299 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2300 return f"FROM ({from_expressions}) TO ({to_expressions})" 2301 2302 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2303 this = self.sql(expression, "this") 2304 2305 for_values_or_default = expression.expression 2306 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2307 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2308 else: 2309 for_values_or_default = " DEFAULT" 2310 2311 return f"PARTITION OF {this}{for_values_or_default}" 2312 2313 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2314 kind = expression.args.get("kind") 2315 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2316 for_or_in = expression.args.get("for_or_in") 2317 for_or_in = f" {for_or_in}" if for_or_in else "" 2318 lock_type = expression.args.get("lock_type") 2319 override = " OVERRIDE" if expression.args.get("override") else "" 2320 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2321 2322 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2323 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2324 statistics = expression.args.get("statistics") 2325 statistics_sql = "" 2326 if statistics is not None: 2327 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2328 return f"{data_sql}{statistics_sql}" 2329 2330 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2331 this = self.sql(expression, "this") 2332 this = f"HISTORY_TABLE={this}" if this else "" 2333 data_consistency: str | None = self.sql(expression, "data_consistency") 2334 data_consistency = ( 2335 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2336 ) 2337 retention_period: str | None = self.sql(expression, "retention_period") 2338 retention_period = ( 2339 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2340 ) 2341 2342 if this: 2343 on_sql = self.func("ON", this, data_consistency, retention_period) 2344 else: 2345 on_sql = "ON" if expression.args.get("on") else "OFF" 2346 2347 sql = f"SYSTEM_VERSIONING={on_sql}" 2348 2349 return f"WITH({sql})" if expression.args.get("with_") else sql 2350 2351 def insert_sql(self, expression: exp.Insert) -> str: 2352 hint = self.sql(expression, "hint") 2353 overwrite = expression.args.get("overwrite") 2354 2355 if isinstance(expression.this, exp.Directory): 2356 this = " OVERWRITE" if overwrite else " INTO" 2357 else: 2358 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2359 2360 stored = self.sql(expression, "stored") 2361 stored = f" {stored}" if stored else "" 2362 alternative = expression.args.get("alternative") 2363 alternative = f" OR {alternative}" if alternative else "" 2364 ignore = " IGNORE" if expression.args.get("ignore") else "" 2365 is_function = expression.args.get("is_function") 2366 if is_function: 2367 this = f"{this} FUNCTION" 2368 this = f"{this} {self.sql(expression, 'this')}" 2369 2370 exists = " IF EXISTS" if expression.args.get("exists") else "" 2371 where = self.sql(expression, "where") 2372 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2373 using = self.expressions(expression, key="using", flat=True) 2374 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2375 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2376 on_conflict = self.sql(expression, "conflict") 2377 on_conflict = f" {on_conflict}" if on_conflict else "" 2378 by_name = " BY NAME" if expression.args.get("by_name") else "" 2379 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2380 returning = self.sql(expression, "returning") 2381 2382 if self.RETURNING_END: 2383 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2384 else: 2385 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2386 2387 partition_by = self.sql(expression, "partition") 2388 partition_by = f" {partition_by}" if partition_by else "" 2389 settings = self.sql(expression, "settings") 2390 settings = f" {settings}" if settings else "" 2391 2392 source = self.sql(expression, "source") 2393 source = f"TABLE {source}" if source else "" 2394 2395 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2396 return self.prepend_ctes(expression, sql) 2397 2398 def introducer_sql(self, expression: exp.Introducer) -> str: 2399 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2400 2401 def kill_sql(self, expression: exp.Kill) -> str: 2402 kind = self.sql(expression, "kind") 2403 kind = f" {kind}" if kind else "" 2404 this = self.sql(expression, "this") 2405 this = f" {this}" if this else "" 2406 return f"KILL{kind}{this}" 2407 2408 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2409 return expression.name 2410 2411 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2412 return expression.name 2413 2414 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2415 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2416 2417 constraint = self.sql(expression, "constraint") 2418 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2419 2420 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2421 if conflict_keys: 2422 conflict_keys = f"({conflict_keys})" 2423 2424 index_predicate = self.sql(expression, "index_predicate") 2425 conflict_keys = f"{conflict_keys}{index_predicate} " 2426 2427 action = self.sql(expression, "action") 2428 2429 expressions = self.expressions(expression, flat=True) 2430 if expressions: 2431 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2432 expressions = f" {set_keyword}{expressions}" 2433 2434 where = self.sql(expression, "where") 2435 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2436 2437 def returning_sql(self, expression: exp.Returning) -> str: 2438 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2439 2440 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2441 fields = self.sql(expression, "fields") 2442 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2443 escaped = self.sql(expression, "escaped") 2444 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2445 items = self.sql(expression, "collection_items") 2446 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2447 keys = self.sql(expression, "map_keys") 2448 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2449 lines = self.sql(expression, "lines") 2450 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2451 null = self.sql(expression, "null") 2452 null = f" NULL DEFINED AS {null}" if null else "" 2453 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2454 2455 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2456 return f"WITH ({self.expressions(expression, flat=True)})" 2457 2458 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2459 this = f"{self.sql(expression, 'this')} INDEX" 2460 target = self.sql(expression, "target") 2461 target = f" FOR {target}" if target else "" 2462 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2463 2464 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2465 this = self.sql(expression, "this") 2466 kind = self.sql(expression, "kind") 2467 expr = self.sql(expression, "expression") 2468 return f"{this} ({kind} => {expr})" 2469 2470 def table_parts(self, expression: exp.Table) -> str: 2471 return ".".join( 2472 self.sql(part) 2473 for part in ( 2474 expression.args.get("catalog"), 2475 expression.args.get("db"), 2476 expression.args.get("this"), 2477 ) 2478 if part is not None 2479 ) 2480 2481 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2482 table = self.table_parts(expression) 2483 only = "ONLY " if expression.args.get("only") else "" 2484 partition = self.sql(expression, "partition") 2485 partition = f" {partition}" if partition else "" 2486 version = self.sql(expression, "version") 2487 version = f" {version}" if version else "" 2488 alias = self.sql(expression, "alias") 2489 alias = f"{sep}{alias}" if alias else "" 2490 2491 sample = self.sql(expression, "sample") 2492 post_alias = "" 2493 pre_alias = "" 2494 2495 if self.dialect.ALIAS_POST_TABLESAMPLE: 2496 pre_alias = sample 2497 else: 2498 post_alias = sample 2499 2500 if self.dialect.ALIAS_POST_VERSION: 2501 pre_alias = f"{pre_alias}{version}" 2502 else: 2503 post_alias = f"{post_alias}{version}" 2504 2505 hints = self.expressions(expression, key="hints", sep=" ") 2506 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2507 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2508 joins = self.indent( 2509 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2510 ) 2511 laterals = self.expressions(expression, key="laterals", sep="") 2512 2513 file_format = self.sql(expression, "format") 2514 pattern = self.sql(expression, "pattern") 2515 if file_format: 2516 pattern = f", PATTERN => {pattern}" if pattern else "" 2517 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2518 elif pattern: 2519 file_format = f" (PATTERN => {pattern})" 2520 2521 ordinality = expression.args.get("ordinality") or "" 2522 if ordinality: 2523 ordinality = f" WITH ORDINALITY{alias}" 2524 alias = "" 2525 2526 when = self.sql(expression, "when") 2527 if when: 2528 if self.HISTORICAL_DATA_POST_ALIAS: 2529 alias = f"{alias} {when}" 2530 else: 2531 table = f"{table} {when}" 2532 2533 changes = self.sql(expression, "changes") 2534 changes = f" {changes}" if changes else "" 2535 2536 rows_from = self.expressions(expression, key="rows_from") 2537 if rows_from: 2538 table = f"ROWS FROM {self.wrap(rows_from)}" 2539 2540 indexed = expression.args.get("indexed") 2541 if indexed is not None: 2542 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2543 else: 2544 indexed = "" 2545 2546 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2547 2548 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2549 table = self.func("TABLE", expression.this) 2550 alias = self.sql(expression, "alias") 2551 alias = f" AS {alias}" if alias else "" 2552 sample = self.sql(expression, "sample") 2553 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2554 joins = self.indent( 2555 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2556 ) 2557 return f"{table}{alias}{pivots}{sample}{joins}" 2558 2559 def tablesample_sql( 2560 self, 2561 expression: exp.TableSample, 2562 tablesample_keyword: str | None = None, 2563 ) -> str: 2564 method = self.sql(expression, "method") 2565 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2566 numerator = self.sql(expression, "bucket_numerator") 2567 denominator = self.sql(expression, "bucket_denominator") 2568 field = self.sql(expression, "bucket_field") 2569 field = f" ON {field}" if field else "" 2570 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2571 seed = self.sql(expression, "seed") 2572 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2573 2574 size = self.sql(expression, "size") 2575 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2576 size = f"{size} ROWS" 2577 2578 percent = self.sql(expression, "percent") 2579 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2580 percent = f"{percent} PERCENT" 2581 2582 expr = f"{bucket}{percent}{size}" 2583 if self.TABLESAMPLE_REQUIRES_PARENS: 2584 expr = f"({expr})" 2585 2586 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2587 2588 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2589 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2590 # the stored column name differs from the target dialect's natural output. 2591 columns = expression.args.get("columns") 2592 if not columns or len(expression.fields) != 1: 2593 return None 2594 2595 args = expression.args 2596 parser_cls = self.dialect.parser_class 2597 2598 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2599 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2600 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2601 2602 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2603 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2604 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2605 2606 if ( 2607 src_identify_pivot_strings == tgt_identify_pivot_strings 2608 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2609 and src_pivot_column_naming == tgt_pivot_column_naming 2610 ): 2611 return None 2612 2613 in_exprs = expression.fields[0].expressions 2614 step = len(columns) // len(in_exprs) 2615 2616 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2617 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2618 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2619 first_stored = columns[0].name 2620 2621 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2622 if not first_stored.startswith(first_base): 2623 return None 2624 2625 suffix = first_stored[len(first_base) :] 2626 2627 # Whether the target dialect would append an agg-name suffix for this pivot. 2628 # Spark single-agg uniquely drops the agg alias entirely. 2629 target_has_suffix = ( 2630 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2631 ) and any(a.alias for a in expression.expressions) 2632 source_has_suffix = suffix != "" 2633 2634 new_exprs: list[exp.Expression] = [] 2635 modified = False 2636 for val_idx, e in enumerate(in_exprs): 2637 if isinstance(e, exp.PivotAlias): 2638 new_exprs.append(e) 2639 continue 2640 2641 i = val_idx * step 2642 stored_full = columns[i].name 2643 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2644 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2645 2646 # Source had a suffix, but target won't apply one 2647 if source_has_suffix and not target_has_suffix: 2648 new_exprs.append( 2649 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2650 ) 2651 modified = True 2652 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2653 elif stored_value != target_value: 2654 new_exprs.append( 2655 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2656 ) 2657 modified = True 2658 else: 2659 new_exprs.append(e) 2660 2661 return new_exprs if modified else None 2662 2663 def pivot_sql(self, expression: exp.Pivot) -> str: 2664 expressions = self.expressions(expression, flat=True) 2665 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2666 2667 group = self.sql(expression, "group") 2668 2669 if expression.this: 2670 this = self.sql(expression, "this") 2671 if not expressions: 2672 sql = f"UNPIVOT {this}" 2673 else: 2674 on = f"{self.seg('ON')} {expressions}" 2675 into = self.sql(expression, "into") 2676 into = f"{self.seg('INTO')} {into}" if into else "" 2677 using = self.expressions(expression, key="using", flat=True) 2678 using = f"{self.seg('USING')} {using}" if using else "" 2679 sql = f"{direction} {this}{on}{into}{using}{group}" 2680 return self.prepend_ctes(expression, sql) 2681 2682 if not expression.unpivot: 2683 # Wrap IN-list values with explicit aliases where the target dialect would differ 2684 new_field_exprs = self._pivot_in_value_aliases(expression) 2685 if new_field_exprs is not None: 2686 expression.fields[0].set("expressions", new_field_exprs) 2687 2688 alias = self.sql(expression, "alias") 2689 if alias: 2690 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2691 2692 fields = self.expressions( 2693 expression, 2694 "fields", 2695 sep=" ", 2696 dynamic=True, 2697 new_line=True, 2698 skip_first=True, 2699 skip_last=True, 2700 ) 2701 2702 include_nulls = expression.args.get("include_nulls") 2703 if include_nulls is not None: 2704 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2705 else: 2706 nulls = "" 2707 2708 default_on_null = self.sql(expression, "default_on_null") 2709 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2710 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2711 return self.prepend_ctes(expression, sql) 2712 2713 def version_sql(self, expression: exp.Version) -> str: 2714 this = f"FOR {expression.name}" 2715 kind = expression.text("kind") 2716 expr = self.sql(expression, "expression") 2717 return f"{this} {kind} {expr}" 2718 2719 def tuple_sql(self, expression: exp.Tuple) -> str: 2720 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2721 2722 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2723 """ 2724 Returns (join_sql, from_sql) for UPDATE statements. 2725 - join_sql: placed after UPDATE table, before SET 2726 - from_sql: placed after SET clause (standard position) 2727 Dialects like MySQL need to convert FROM to JOIN syntax. 2728 """ 2729 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2730 return ("", self.sql(expression, "from_")) 2731 2732 # Qualify unqualified columns in SET clause with the target table 2733 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2734 target_table = expression.this 2735 if isinstance(target_table, exp.Table): 2736 target_name = exp.to_identifier(target_table.alias_or_name) 2737 for eq in expression.expressions: 2738 col = eq.this 2739 if isinstance(col, exp.Column) and not col.table: 2740 col.set("table", target_name) 2741 2742 table = from_expr.this 2743 if nested_joins := table.args.get("joins", []): 2744 table.set("joins", None) 2745 2746 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2747 for nested in nested_joins: 2748 if not nested.args.get("on") and not nested.args.get("using"): 2749 nested.set("on", exp.true()) 2750 join_sql += self.sql(nested) 2751 2752 return (join_sql, "") 2753 2754 def update_sql(self, expression: exp.Update) -> str: 2755 hint = self.sql(expression, "hint") 2756 this = self.sql(expression, "this") 2757 join_sql, from_sql = self._update_from_joins_sql(expression) 2758 set_sql = self.expressions(expression, flat=True) 2759 where_sql = self.sql(expression, "where") 2760 returning = self.sql(expression, "returning") 2761 order = self.sql(expression, "order") 2762 limit = self.sql(expression, "limit") 2763 if self.RETURNING_END: 2764 expression_sql = f"{from_sql}{where_sql}{returning}" 2765 else: 2766 expression_sql = f"{returning}{from_sql}{where_sql}" 2767 options = self.expressions(expression, key="options") 2768 options = f" OPTION({options})" if options else "" 2769 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2770 return self.prepend_ctes(expression, sql) 2771 2772 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2773 values_as_table = values_as_table and self.VALUES_AS_TABLE 2774 2775 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2776 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2777 args = self.expressions(expression) 2778 alias = self.sql(expression, "alias") 2779 values = f"VALUES{self.seg('')}{args}" 2780 values = ( 2781 f"({values})" 2782 if self.WRAP_DERIVED_VALUES 2783 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2784 else values 2785 ) 2786 values = self.query_modifiers(expression, values) 2787 return f"{values} AS {alias}" if alias else values 2788 2789 # Converts `VALUES...` expression into a series of select unions. 2790 alias_node = expression.args.get("alias") 2791 column_names = alias_node and alias_node.columns 2792 2793 selects: list[exp.Query] = [] 2794 2795 for i, tup in enumerate(expression.expressions): 2796 row = tup.expressions 2797 2798 if i == 0 and column_names: 2799 row = [ 2800 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2801 ] 2802 2803 selects.append(exp.Select(expressions=row)) 2804 2805 if self.pretty: 2806 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2807 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2808 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2809 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2810 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2811 2812 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2813 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2814 return f"({unions}){alias}" 2815 2816 def var_sql(self, expression: exp.Var) -> str: 2817 return self.sql(expression, "this") 2818 2819 @unsupported_args("expressions") 2820 def into_sql(self, expression: exp.Into) -> str: 2821 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2822 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2823 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2824 2825 def from_sql(self, expression: exp.From) -> str: 2826 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2827 2828 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2829 grouping_sets = self.expressions(expression, indent=False) 2830 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2831 2832 def rollup_sql(self, expression: exp.Rollup) -> str: 2833 expressions = self.expressions(expression, indent=False) 2834 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2835 2836 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2837 this = self.sql(expression, "this") 2838 2839 columns = self.expressions(expression, flat=True) 2840 2841 from_sql = self.sql(expression, "from_index") 2842 from_sql = f" FROM {from_sql}" if from_sql else "" 2843 2844 properties = expression.args.get("properties") 2845 properties_sql = ( 2846 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2847 ) 2848 2849 return f"{this}({columns}){from_sql}{properties_sql}" 2850 2851 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2852 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2853 2854 def cube_sql(self, expression: exp.Cube) -> str: 2855 expressions = self.expressions(expression, indent=False) 2856 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2857 2858 def group_sql(self, expression: exp.Group) -> str: 2859 group_by_all = expression.args.get("all") 2860 if group_by_all is True: 2861 modifier = " ALL" 2862 elif group_by_all is False: 2863 modifier = " DISTINCT" 2864 else: 2865 modifier = "" 2866 2867 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2868 2869 grouping_sets = self.expressions(expression, key="grouping_sets") 2870 cube = self.expressions(expression, key="cube") 2871 rollup = self.expressions(expression, key="rollup") 2872 2873 groupings = csv( 2874 self.seg(grouping_sets) if grouping_sets else "", 2875 self.seg(cube) if cube else "", 2876 self.seg(rollup) if rollup else "", 2877 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2878 sep=self.GROUPINGS_SEP, 2879 ) 2880 2881 if ( 2882 expression.expressions 2883 and groupings 2884 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2885 ): 2886 add_separator = True 2887 2888 if grouping_sets: 2889 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2890 add_separator = False 2891 else: 2892 self.unsupported( 2893 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2894 ) 2895 2896 if add_separator: 2897 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2898 2899 return f"{group_by}{groupings}" 2900 2901 def having_sql(self, expression: exp.Having) -> str: 2902 this = self.indent(self.sql(expression, "this")) 2903 return f"{self.seg('HAVING')}{self.sep()}{this}" 2904 2905 def connect_sql(self, expression: exp.Connect) -> str: 2906 start = self.sql(expression, "start") 2907 start = self.seg(f"START WITH {start}") if start else "" 2908 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2909 connect = self.sql(expression, "connect") 2910 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2911 return start + connect 2912 2913 def prior_sql(self, expression: exp.Prior) -> str: 2914 return f"PRIOR {self.sql(expression, 'this')}" 2915 2916 def join_sql(self, expression: exp.Join) -> str: 2917 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2918 side = None 2919 else: 2920 side = expression.side 2921 2922 op_sql = " ".join( 2923 op 2924 for op in ( 2925 expression.method, 2926 "GLOBAL" if expression.args.get("global_") else None, 2927 side, 2928 expression.kind, 2929 expression.hint if self.JOIN_HINTS else None, 2930 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2931 ) 2932 if op 2933 ) 2934 match_cond = self.sql(expression, "match_condition") 2935 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2936 on_sql = self.sql(expression, "on") 2937 using = expression.args.get("using") 2938 2939 if not on_sql and using: 2940 on_sql = csv(*(self.sql(column) for column in using)) 2941 2942 this = expression.this 2943 this_sql = self.sql(this) 2944 2945 exprs = self.expressions(expression) 2946 if exprs: 2947 this_sql = f"{this_sql},{self.seg(exprs)}" 2948 2949 if on_sql: 2950 on_sql = self.indent(on_sql, skip_first=True) 2951 space = self.seg(" " * self.pad) if self.pretty else " " 2952 if using: 2953 on_sql = f"{space}USING ({on_sql})" 2954 else: 2955 on_sql = f"{space}ON {on_sql}" 2956 elif not op_sql: 2957 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2958 return f" {this_sql}" 2959 2960 return f", {this_sql}" 2961 2962 if op_sql != "STRAIGHT_JOIN": 2963 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2964 2965 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2966 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2967 2968 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2969 args = self.expressions(expression, flat=True) 2970 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2971 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2972 2973 def lateral_op(self, expression: exp.Lateral) -> str: 2974 cross_apply = expression.args.get("cross_apply") 2975 2976 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2977 if cross_apply is True: 2978 op = "INNER JOIN " 2979 elif cross_apply is False: 2980 op = "LEFT JOIN " 2981 else: 2982 op = "" 2983 2984 return f"{op}LATERAL" 2985 2986 def lateral_sql(self, expression: exp.Lateral) -> str: 2987 this = self.sql(expression, "this") 2988 2989 if expression.args.get("view"): 2990 alias = expression.args["alias"] 2991 columns = self.expressions(alias, key="columns", flat=True) 2992 table = f" {alias.name}" if alias.name else "" 2993 columns = f" AS {columns}" if columns else "" 2994 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2995 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2996 2997 table_alias = expression.args.get("alias") 2998 offset = expression.this.args.get("offset") 2999 3000 if ( 3001 self.UNNEST_WITH_ORDINALITY 3002 and table_alias 3003 and isinstance(expression.this, exp.Unnest) 3004 and isinstance(offset, exp.Identifier) 3005 ): 3006 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3007 table_alias = table_alias.copy() 3008 table_alias.append("columns", offset.copy()) 3009 3010 alias = self.sql(table_alias) 3011 alias = f" AS {alias}" if alias else "" 3012 3013 ordinality = expression.args.get("ordinality") or "" 3014 if ordinality: 3015 ordinality = f" WITH ORDINALITY{alias}" 3016 alias = "" 3017 3018 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 3019 3020 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3021 this = self.sql(expression, "this") 3022 3023 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3024 return this 3025 3026 args = [ 3027 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3028 for e in (expression.args.get(k) for k in ("offset", "expression")) 3029 if e 3030 ] 3031 3032 args_sql = ", ".join(self.sql(e) for e in args) 3033 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3034 expressions = self.expressions(expression, flat=True) 3035 limit_options = self.sql(expression, "limit_options") 3036 expressions = f" BY {expressions}" if expressions else "" 3037 3038 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 3039 3040 def offset_sql(self, expression: exp.Offset) -> str: 3041 this = self.sql(expression, "this") 3042 value = expression.expression 3043 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3044 expressions = self.expressions(expression, flat=True) 3045 expressions = f" BY {expressions}" if expressions else "" 3046 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 3047 3048 def setitem_sql(self, expression: exp.SetItem) -> str: 3049 kind = self.sql(expression, "kind") 3050 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3051 kind = "" 3052 else: 3053 kind = f"{kind} " if kind else "" 3054 this = self.sql(expression, "this") 3055 expressions = self.expressions(expression) 3056 collate = self.sql(expression, "collate") 3057 collate = f" COLLATE {collate}" if collate else "" 3058 global_ = "GLOBAL " if expression.args.get("global_") else "" 3059 return f"{global_}{kind}{this}{expressions}{collate}" 3060 3061 def set_sql(self, expression: exp.Set) -> str: 3062 expressions = f" {self.expressions(expression, flat=True)}" 3063 tag = " TAG" if expression.args.get("tag") else "" 3064 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3065 3066 def queryband_sql(self, expression: exp.QueryBand) -> str: 3067 this = self.sql(expression, "this") 3068 update = " UPDATE" if expression.args.get("update") else "" 3069 scope = self.sql(expression, "scope") 3070 scope = f" FOR {scope}" if scope else "" 3071 3072 return f"QUERY_BAND = {this}{update}{scope}" 3073 3074 def pragma_sql(self, expression: exp.Pragma) -> str: 3075 return f"PRAGMA {self.sql(expression, 'this')}" 3076 3077 def lock_sql(self, expression: exp.Lock) -> str: 3078 if not self.LOCKING_READS_SUPPORTED: 3079 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3080 return "" 3081 3082 update = expression.args["update"] 3083 key = expression.args.get("key") 3084 if update: 3085 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3086 else: 3087 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3088 expressions = self.expressions(expression, flat=True) 3089 expressions = f" OF {expressions}" if expressions else "" 3090 wait = expression.args.get("wait") 3091 3092 if wait is not None: 3093 if isinstance(wait, exp.Literal): 3094 wait = f" WAIT {self.sql(wait)}" 3095 else: 3096 wait = " NOWAIT" if wait else " SKIP LOCKED" 3097 3098 return f"{lock_type}{expressions}{wait or ''}" 3099 3100 def literal_sql(self, expression: exp.Literal) -> str: 3101 text = expression.this or "" 3102 if expression.is_string: 3103 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3104 return text 3105 3106 def escape_str( 3107 self, 3108 text: str, 3109 escape_backslash: bool = True, 3110 delimiter: str | None = None, 3111 escaped_delimiter: str | None = None, 3112 is_byte_string: bool = False, 3113 is_bytes: bool = False, 3114 ) -> str: 3115 if is_byte_string: 3116 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3117 else: 3118 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3119 3120 if supports_escape_sequences: 3121 text = "".join( 3122 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3123 for ch in text 3124 ) 3125 3126 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3127 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3128 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3129 3130 delimiter = delimiter or self.dialect.QUOTE_END 3131 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3132 3133 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3134 3135 def loaddata_sql(self, expression: exp.LoadData) -> str: 3136 is_overwrite = expression.args.get("overwrite") 3137 overwrite = " OVERWRITE" if is_overwrite else "" 3138 this = self.sql(expression, "this") 3139 3140 files = expression.args.get("files") 3141 if files: 3142 files_sql = self.expressions(files, flat=True) 3143 files_sql = f"FILES{self.wrap(files_sql)}" 3144 if is_overwrite: 3145 this = f" {this}" 3146 elif expression.args.get("temp"): 3147 this = f" INTO TEMP TABLE {this}" 3148 else: 3149 this = f" INTO TABLE {this}" 3150 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3151 3152 local = " LOCAL" if expression.args.get("local") else "" 3153 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3154 this = f" INTO TABLE {this}" 3155 partition = self.sql(expression, "partition") 3156 partition = f" {partition}" if partition else "" 3157 input_format = self.sql(expression, "input_format") 3158 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3159 serde = self.sql(expression, "serde") 3160 serde = f" SERDE {serde}" if serde else "" 3161 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3162 3163 def null_sql(self, *_) -> str: 3164 return "NULL" 3165 3166 def boolean_sql(self, expression: exp.Boolean) -> str: 3167 return "TRUE" if expression.this else "FALSE" 3168 3169 def booland_sql(self, expression: exp.Booland) -> str: 3170 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3171 3172 def boolor_sql(self, expression: exp.Boolor) -> str: 3173 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3174 3175 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3176 this = self.sql(expression, "this") 3177 this = f"{this} " if this else this 3178 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3179 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3180 3181 def withfill_sql(self, expression: exp.WithFill) -> str: 3182 from_sql = self.sql(expression, "from_") 3183 from_sql = f" FROM {from_sql}" if from_sql else "" 3184 to_sql = self.sql(expression, "to") 3185 to_sql = f" TO {to_sql}" if to_sql else "" 3186 step_sql = self.sql(expression, "step") 3187 step_sql = f" STEP {step_sql}" if step_sql else "" 3188 interpolated_values = [ 3189 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3190 if isinstance(e, exp.Alias) 3191 else self.sql(e, "this") 3192 for e in expression.args.get("interpolate") or [] 3193 ] 3194 interpolate = ( 3195 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3196 ) 3197 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3198 3199 def cluster_sql(self, expression: exp.Cluster) -> str: 3200 return self.op_expressions("CLUSTER BY", expression) 3201 3202 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3203 if expression.this: 3204 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3205 return "" 3206 expressions = self.expressions(expression, flat=True) 3207 return f"CLUSTER BY ({expressions})" 3208 3209 def distribute_sql(self, expression: exp.Distribute) -> str: 3210 return self.op_expressions("DISTRIBUTE BY", expression) 3211 3212 def sort_sql(self, expression: exp.Sort) -> str: 3213 return self.op_expressions("SORT BY", expression) 3214 3215 def _resolve_ordered_for_null_ordering_simulation( 3216 self, expression: exp.Ordered 3217 ) -> exp.Expr | None: 3218 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3219 3220 Returns the underlying expression of the uniquely-matching projection 3221 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3222 simulation, since the CASE is evaluated in FROM-clause scope rather 3223 than alias scope (MySQL error 1052). Returns None if no safe 3224 substitution applies, leaving the original behaviour unchanged. 3225 """ 3226 this = expression.this 3227 if not (isinstance(this, exp.Column) and not this.table): 3228 return None 3229 3230 # Only a query's own ORDER BY can reference its projection aliases 3231 order = expression.parent 3232 ancestor = order.parent if isinstance(order, exp.Order) else None 3233 if not isinstance(ancestor, exp.Select): 3234 return None 3235 3236 column_name = this.name 3237 matched: list[exp.Expr] = [ 3238 p.this if isinstance(p, exp.Alias) else p 3239 for p in ancestor.selects 3240 if p.output_name == column_name 3241 ] 3242 match = matched[0] if len(matched) == 1 else None 3243 3244 # Skip the substitution when it would be identical to the existing 3245 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3246 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3247 return None 3248 3249 return match 3250 3251 def ordered_sql(self, expression: exp.Ordered) -> str: 3252 desc = expression.args.get("desc") 3253 asc = not desc 3254 3255 nulls_first = expression.args.get("nulls_first") 3256 nulls_last = not nulls_first 3257 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3258 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3259 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3260 3261 this = self.sql(expression, "this") 3262 3263 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3264 nulls_sort_change = "" 3265 if nulls_first and ( 3266 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3267 ): 3268 nulls_sort_change = " NULLS FIRST" 3269 elif ( 3270 nulls_last 3271 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3272 and not nulls_are_last 3273 ): 3274 nulls_sort_change = " NULLS LAST" 3275 3276 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3277 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3278 window = expression.find_ancestor(exp.Window, exp.Select) 3279 3280 if isinstance(window, exp.Window): 3281 window_this = window.this 3282 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3283 window_this = window_this.this 3284 spec = window.args.get("spec") 3285 else: 3286 window_this = None 3287 spec = None 3288 3289 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3290 # without a spec or with a ROWS spec, but not with RANGE 3291 if not ( 3292 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3293 and (not spec or spec.text("kind").upper() == "ROWS") 3294 ): 3295 if window_this and spec: 3296 self.unsupported( 3297 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3298 ) 3299 nulls_sort_change = "" 3300 elif self.NULL_ORDERING_SUPPORTED is False and ( 3301 (asc and nulls_sort_change == " NULLS LAST") 3302 or (desc and nulls_sort_change == " NULLS FIRST") 3303 ): 3304 # BigQuery does not allow these ordering/nulls combinations when used under 3305 # an aggregation func or under a window containing one 3306 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3307 3308 if isinstance(ancestor, exp.Window): 3309 ancestor = ancestor.this 3310 if isinstance(ancestor, exp.AggFunc): 3311 self.unsupported( 3312 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3313 ) 3314 nulls_sort_change = "" 3315 elif self.NULL_ORDERING_SUPPORTED is None: 3316 if expression.this.is_int: 3317 self.unsupported( 3318 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3319 ) 3320 elif not isinstance(expression.this, exp.Rand): 3321 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3322 target = self.sql(resolved) if resolved is not None else this 3323 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3324 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3325 nulls_sort_change = "" 3326 3327 with_fill = self.sql(expression, "with_fill") 3328 with_fill = f" {with_fill}" if with_fill else "" 3329 3330 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3331 3332 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3333 window_frame = self.sql(expression, "window_frame") 3334 window_frame = f"{window_frame} " if window_frame else "" 3335 3336 this = self.sql(expression, "this") 3337 3338 return f"{window_frame}{this}" 3339 3340 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3341 partition = self.partition_by_sql(expression) 3342 order = self.sql(expression, "order") 3343 measures = self.expressions(expression, key="measures") 3344 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3345 rows = self.sql(expression, "rows") 3346 rows = self.seg(rows) if rows else "" 3347 after = self.sql(expression, "after") 3348 after = self.seg(after) if after else "" 3349 pattern = self.sql(expression, "pattern") 3350 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3351 definition_sqls = [ 3352 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3353 for definition in expression.args.get("define", []) 3354 ] 3355 definitions = self.expressions(sqls=definition_sqls) 3356 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3357 body = "".join( 3358 ( 3359 partition, 3360 order, 3361 measures, 3362 rows, 3363 after, 3364 pattern, 3365 define, 3366 ) 3367 ) 3368 alias = self.sql(expression, "alias") 3369 alias = f" {alias}" if alias else "" 3370 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3371 3372 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3373 limit = expression.args.get("limit") 3374 3375 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3376 count = limit.args.get("count") 3377 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3378 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3379 limit = exp.Limit( 3380 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3381 ) 3382 elif ( 3383 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3384 ): 3385 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3386 3387 return csv( 3388 *sqls, 3389 *[self.sql(join) for join in expression.args.get("joins") or []], 3390 self.sql(expression, "match"), 3391 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3392 self.sql(expression, "prewhere"), 3393 self.sql(expression, "where"), 3394 self.sql(expression, "connect"), 3395 self.sql(expression, "group"), 3396 self.sql(expression, "having"), 3397 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3398 self.sql(expression, "order"), 3399 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3400 *self.after_limit_modifiers(expression), 3401 self.sql(expression, "for_"), 3402 self.options_modifier(expression), 3403 sep="", 3404 ) 3405 3406 def options_modifier(self, expression: exp.Expr) -> str: 3407 options = self.expressions(expression, key="options") 3408 return f" {options}" if options else "" 3409 3410 def forclause_sql(self, expression: exp.ForClause) -> str: 3411 kind = expression.args["kind"] 3412 if kind == "BROWSE": 3413 return f"{self.sep()}FOR BROWSE" 3414 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3415 # the target dialect doesn't support QueryOption, so we drop the clause. 3416 options = self.expressions(expression, key="expressions") 3417 if not options: 3418 return "" 3419 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3420 3421 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3422 self.unsupported("Unsupported query option.") 3423 return "" 3424 3425 def offset_limit_modifiers( 3426 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3427 ) -> list[str]: 3428 return [ 3429 self.sql(expression, "offset") if fetch else self.sql(limit), 3430 self.sql(limit) if fetch else self.sql(expression, "offset"), 3431 ] 3432 3433 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3434 locks = self.expressions(expression, key="locks", sep=" ") 3435 locks = f" {locks}" if locks else "" 3436 return [locks, self.sql(expression, "sample")] 3437 3438 def select_sql(self, expression: exp.Select) -> str: 3439 into = expression.args.get("into") 3440 if not self.SUPPORTS_SELECT_INTO and into: 3441 into.pop() 3442 3443 hint = self.sql(expression, "hint") 3444 distinct = self.sql(expression, "distinct") 3445 distinct = f" {distinct}" if distinct else "" 3446 kind = self.sql(expression, "kind") 3447 3448 limit = expression.args.get("limit") 3449 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3450 top = self.limit_sql(limit, top=True) 3451 limit.pop() 3452 else: 3453 top = "" 3454 3455 expressions = self.expressions(expression) 3456 3457 if kind: 3458 if kind in self.SELECT_KINDS: 3459 kind = f" AS {kind}" 3460 else: 3461 if kind == "STRUCT": 3462 expressions = self.expressions( 3463 sqls=[ 3464 self.sql( 3465 exp.Struct( 3466 expressions=[ 3467 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3468 if isinstance(e, exp.Alias) 3469 else e 3470 for e in expression.expressions 3471 ] 3472 ) 3473 ) 3474 ] 3475 ) 3476 kind = "" 3477 3478 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3479 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3480 3481 exclude = expression.args.get("exclude") 3482 3483 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3484 exclude_sql = self.expressions(sqls=exclude, flat=True) 3485 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3486 3487 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3488 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3489 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3490 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3491 sql = self.query_modifiers( 3492 expression, 3493 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3494 self.sql(expression, "into", comment=False), 3495 self.sql(expression, "from_", comment=False), 3496 ) 3497 3498 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3499 if expression.args.get("with_"): 3500 sql = self.maybe_comment(sql, expression) 3501 expression.pop_comments() 3502 3503 sql = self.prepend_ctes(expression, sql) 3504 3505 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3506 expression.set("exclude", None) 3507 subquery = expression.subquery(copy=False) 3508 star = exp.Star(except_=exclude) 3509 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3510 3511 if not self.SUPPORTS_SELECT_INTO and into: 3512 if into.args.get("temporary"): 3513 table_kind = " TEMPORARY" 3514 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3515 table_kind = " UNLOGGED" 3516 else: 3517 table_kind = "" 3518 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3519 3520 return sql 3521 3522 def schema_sql(self, expression: exp.Schema) -> str: 3523 this = self.sql(expression, "this") 3524 sql = self.schema_columns_sql(expression) 3525 return f"{this} {sql}" if this and sql else this or sql 3526 3527 def schema_columns_sql(self, expression: exp.Expr) -> str: 3528 if expression.expressions: 3529 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3530 return "" 3531 3532 def star_sql(self, expression: exp.Star) -> str: 3533 except_ = self.expressions(expression, key="except_", flat=True) 3534 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3535 replace = self.expressions(expression, key="replace", flat=True) 3536 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3537 rename = self.expressions(expression, key="rename", flat=True) 3538 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3539 ilike = self.sql(expression, "ilike") 3540 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3541 return f"*{ilike}{except_}{replace}{rename}" 3542 3543 def parameter_sql(self, expression: exp.Parameter) -> str: 3544 this = self.sql(expression, "this") 3545 return f"{self.PARAMETER_TOKEN}{this}" 3546 3547 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3548 this = self.sql(expression, "this") 3549 kind = expression.text("kind") 3550 if kind: 3551 kind = f"{kind}." 3552 return f"@@{kind}{this}" 3553 3554 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3555 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3556 3557 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3558 alias = self.sql(expression, "alias") 3559 alias = f"{sep}{alias}" if alias else "" 3560 sample = self.sql(expression, "sample") 3561 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3562 alias = f"{sample}{alias}" 3563 3564 # Set to None so it's not generated again by self.query_modifiers() 3565 expression.set("sample", None) 3566 3567 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3568 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3569 return self.prepend_ctes(expression, sql) 3570 3571 def qualify_sql(self, expression: exp.Qualify) -> str: 3572 this = self.indent(self.sql(expression, "this")) 3573 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3574 3575 def unnest_sql(self, expression: exp.Unnest) -> str: 3576 args = self.expressions(expression, flat=True) 3577 3578 alias = expression.args.get("alias") 3579 offset = expression.args.get("offset") 3580 3581 if self.UNNEST_WITH_ORDINALITY: 3582 if alias and isinstance(offset, exp.Expr): 3583 alias.append("columns", offset) 3584 expression.set("offset", None) 3585 3586 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3587 columns = alias.columns 3588 alias = self.sql(columns[0]) if columns else "" 3589 else: 3590 alias = self.sql(alias) 3591 3592 alias = f" AS {alias}" if alias else alias 3593 if self.UNNEST_WITH_ORDINALITY: 3594 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3595 else: 3596 if isinstance(offset, exp.Expr): 3597 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3598 elif offset: 3599 suffix = f"{alias} WITH OFFSET" 3600 else: 3601 suffix = alias 3602 3603 return f"UNNEST({args}){suffix}" 3604 3605 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3606 return "" 3607 3608 def where_sql(self, expression: exp.Where) -> str: 3609 this = self.indent(self.sql(expression, "this")) 3610 return f"{self.seg('WHERE')}{self.sep()}{this}" 3611 3612 def window_sql(self, expression: exp.Window) -> str: 3613 this = self.sql(expression, "this") 3614 partition = self.partition_by_sql(expression) 3615 order = expression.args.get("order") 3616 order = self.order_sql(order, flat=True) if order else "" 3617 spec = self.sql(expression, "spec") 3618 alias = self.sql(expression, "alias") 3619 over = self.sql(expression, "over") or "OVER" 3620 3621 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3622 3623 first = expression.args.get("first") 3624 if first is None: 3625 first = "" 3626 else: 3627 first = "FIRST" if first else "LAST" 3628 3629 if not partition and not order and not spec and alias: 3630 return f"{this} {alias}" 3631 3632 args = self.format_args( 3633 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3634 ) 3635 return f"{this} ({args})" 3636 3637 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3638 partition = self.expressions(expression, key="partition_by", flat=True) 3639 return f"PARTITION BY {partition}" if partition else "" 3640 3641 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3642 kind = self.sql(expression, "kind") 3643 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3644 end = ( 3645 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3646 or "CURRENT ROW" 3647 ) 3648 3649 window_spec = f"{kind} BETWEEN {start} AND {end}" 3650 3651 exclude = self.sql(expression, "exclude") 3652 if exclude: 3653 if self.SUPPORTS_WINDOW_EXCLUDE: 3654 window_spec += f" EXCLUDE {exclude}" 3655 else: 3656 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3657 3658 return window_spec 3659 3660 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3661 this = self.sql(expression, "this") 3662 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3663 return f"{this} WITHIN GROUP ({expression_sql})" 3664 3665 def between_sql(self, expression: exp.Between) -> str: 3666 this = self.sql(expression, "this") 3667 low = self.sql(expression, "low") 3668 high = self.sql(expression, "high") 3669 symmetric = expression.args.get("symmetric") 3670 3671 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3672 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3673 3674 flag = ( 3675 " SYMMETRIC" 3676 if symmetric 3677 else " ASYMMETRIC" 3678 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3679 else "" # silently drop ASYMMETRIC – semantics identical 3680 ) 3681 return f"{this} BETWEEN{flag} {low} AND {high}" 3682 3683 def bracket_offset_expressions( 3684 self, expression: exp.Bracket, index_offset: int | None = None 3685 ) -> list[exp.Expr]: 3686 if expression.args.get("json_access"): 3687 return expression.expressions 3688 3689 return apply_index_offset( 3690 expression.this, 3691 expression.expressions, 3692 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3693 dialect=self.dialect, 3694 ) 3695 3696 def bracket_sql(self, expression: exp.Bracket) -> str: 3697 expressions = self.bracket_offset_expressions(expression) 3698 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3699 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3700 3701 def all_sql(self, expression: exp.All) -> str: 3702 this = self.sql(expression, "this") 3703 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3704 this = self.wrap(this) 3705 return f"ALL {this}" 3706 3707 def any_sql(self, expression: exp.Any) -> str: 3708 this = self.sql(expression, "this") 3709 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3710 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3711 this = self.wrap(this) 3712 return f"ANY{this}" 3713 return f"ANY {this}" 3714 3715 def exists_sql(self, expression: exp.Exists) -> str: 3716 return f"EXISTS{self.wrap(expression)}" 3717 3718 def case_sql(self, expression: exp.Case) -> str: 3719 this = self.sql(expression, "this") 3720 statements = [f"CASE {this}" if this else "CASE"] 3721 3722 for e in expression.args["ifs"]: 3723 statements.append(f"WHEN {self.sql(e, 'this')}") 3724 statements.append(f"THEN {self.sql(e, 'true')}") 3725 3726 default = self.sql(expression, "default") 3727 3728 if default: 3729 statements.append(f"ELSE {default}") 3730 3731 statements.append("END") 3732 3733 if self.pretty and self.too_wide(statements): 3734 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3735 3736 return " ".join(statements) 3737 3738 def constraint_sql(self, expression: exp.Constraint) -> str: 3739 this = self.sql(expression, "this") 3740 expressions = self.expressions(expression, flat=True) 3741 return f"CONSTRAINT {this} {expressions}" 3742 3743 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3744 order = expression.args.get("order") 3745 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3746 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3747 3748 def extract_sql(self, expression: exp.Extract) -> str: 3749 import sqlglot.dialects.dialect 3750 3751 this = ( 3752 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3753 if self.NORMALIZE_EXTRACT_DATE_PARTS 3754 else expression.this 3755 ) 3756 if self.EXTRACT_ALLOWS_QUOTES: 3757 this_sql = self.sql(this) 3758 elif isinstance(this, exp.WeekStart): 3759 this_sql = self.weekstart_name(this) 3760 else: 3761 this_sql = this.name 3762 expression_sql = self.sql(expression, "expression") 3763 3764 return f"EXTRACT({this_sql} FROM {expression_sql})" 3765 3766 def trim_sql(self, expression: exp.Trim) -> str: 3767 trim_type = self.sql(expression, "position") 3768 3769 if trim_type == "LEADING": 3770 func_name = "LTRIM" 3771 elif trim_type == "TRAILING": 3772 func_name = "RTRIM" 3773 else: 3774 func_name = "TRIM" 3775 3776 return self.func(func_name, expression.this, expression.expression) 3777 3778 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3779 args = expression.expressions 3780 if isinstance(expression, exp.ConcatWs): 3781 args = args[1:] # Skip the delimiter 3782 3783 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3784 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3785 3786 concat_coalesce = ( 3787 self.dialect.CONCAT_WS_COALESCE 3788 if isinstance(expression, exp.ConcatWs) 3789 else self.dialect.CONCAT_COALESCE 3790 ) 3791 3792 if not concat_coalesce and expression.args.get("coalesce"): 3793 3794 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3795 if not e.type: 3796 import sqlglot.optimizer.annotate_types 3797 3798 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3799 3800 if e.is_string or e.is_type(exp.DType.ARRAY): 3801 return e 3802 3803 return exp.func("coalesce", e, exp.Literal.string("")) 3804 3805 args = [_wrap_with_coalesce(e) for e in args] 3806 3807 return args 3808 3809 def concat_sql(self, expression: exp.Concat) -> str: 3810 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3811 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3812 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3813 # instead of coalescing them to empty string. 3814 import sqlglot.dialects.dialect 3815 3816 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3817 3818 expressions = self.convert_concat_args(expression) 3819 3820 # Some dialects don't allow a single-argument CONCAT call 3821 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3822 return self.sql(expressions[0]) 3823 3824 return self.func("CONCAT", *expressions) 3825 3826 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3827 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3828 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3829 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3830 all_args = expression.expressions 3831 expression.set("coalesce", True) 3832 return self.sql( 3833 exp.case() 3834 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3835 .else_(expression) 3836 ) 3837 3838 return self.func( 3839 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3840 ) 3841 3842 def check_sql(self, expression: exp.Check) -> str: 3843 this = self.sql(expression, key="this") 3844 return f"CHECK ({this})" 3845 3846 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3847 this = self.sql(expression, "this") 3848 this = f" {this}" if this else "" 3849 expressions = self.expressions(expression, flat=True) 3850 expressions = f" ({expressions})" if expressions else "" 3851 reference = self.sql(expression, "reference") 3852 reference = f" {reference}" if reference else "" 3853 delete = self.sql(expression, "delete") 3854 delete = f" ON DELETE {delete}" if delete else "" 3855 update = self.sql(expression, "update") 3856 update = f" ON UPDATE {update}" if update else "" 3857 options = self.expressions(expression, key="options", flat=True, sep=" ") 3858 options = f" {options}" if options else "" 3859 return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}" 3860 3861 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3862 this = self.sql(expression, "this") 3863 this = f" {this}" if this else "" 3864 expressions = self.expressions(expression, flat=True) 3865 include = self.sql(expression, "include") 3866 options = self.expressions(expression, key="options", flat=True, sep=" ") 3867 options = f" {options}" if options else "" 3868 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3869 3870 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3871 self.unsupported("TIMESERIES primary key columns are not supported") 3872 return self.sql(expression, "this") 3873 3874 def if_sql(self, expression: exp.If) -> str: 3875 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3876 3877 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3878 if self.MATCH_AGAINST_TABLE_PREFIX: 3879 expressions = [] 3880 for expr in expression.expressions: 3881 if isinstance(expr, exp.Table): 3882 expressions.append(f"TABLE {self.sql(expr)}") 3883 else: 3884 expressions.append(expr) 3885 else: 3886 expressions = expression.expressions 3887 3888 modifier = expression.args.get("modifier") 3889 modifier = f" {modifier}" if modifier else "" 3890 return ( 3891 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3892 ) 3893 3894 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3895 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3896 3897 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3898 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3899 3900 if self.QUOTE_JSON_PATH: 3901 path = self.escape_str(path) 3902 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3903 3904 return path 3905 3906 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3907 if isinstance(expression, exp.JSONPathPart): 3908 transform = self.TRANSFORMS.get(expression.__class__) 3909 if not callable(transform): 3910 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3911 return "" 3912 3913 return transform(self, expression) 3914 3915 if isinstance(expression, int): 3916 return str(expression) 3917 3918 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3919 escaped = expression.replace("'", "\\'") 3920 escaped = f"'{escaped}'" 3921 else: 3922 escaped = expression.replace('"', '\\"') 3923 escaped = f'"{escaped}"' 3924 3925 return escaped 3926 3927 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3928 return f"{self.sql(expression, 'this')} FORMAT JSON" 3929 3930 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3931 # Output the Teradata column FORMAT override. 3932 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3933 this = self.sql(expression, "this") 3934 fmt = self.sql(expression, "format") 3935 return f"{this} (FORMAT {fmt})" 3936 3937 def _jsonobject_sql( 3938 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3939 ) -> str: 3940 null_handling = expression.args.get("null_handling") 3941 null_handling = f" {null_handling}" if null_handling else "" 3942 3943 unique_keys = expression.args.get("unique_keys") 3944 if unique_keys is not None: 3945 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3946 else: 3947 unique_keys = "" 3948 3949 return_type = self.sql(expression, "return_type") 3950 return_type = f" RETURNING {return_type}" if return_type else "" 3951 encoding = self.sql(expression, "encoding") 3952 encoding = f" ENCODING {encoding}" if encoding else "" 3953 3954 if not name: 3955 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3956 3957 return self.func( 3958 name, 3959 *expression.expressions, 3960 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3961 ) 3962 3963 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3964 null_handling = expression.args.get("null_handling") 3965 null_handling = f" {null_handling}" if null_handling else "" 3966 return_type = self.sql(expression, "return_type") 3967 return_type = f" RETURNING {return_type}" if return_type else "" 3968 strict = " STRICT" if expression.args.get("strict") else "" 3969 return self.func( 3970 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3971 ) 3972 3973 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3974 this = self.sql(expression, "this") 3975 order = self.sql(expression, "order") 3976 null_handling = expression.args.get("null_handling") 3977 null_handling = f" {null_handling}" if null_handling else "" 3978 return_type = self.sql(expression, "return_type") 3979 return_type = f" RETURNING {return_type}" if return_type else "" 3980 strict = " STRICT" if expression.args.get("strict") else "" 3981 return self.func( 3982 "JSON_ARRAYAGG", 3983 this, 3984 suffix=f"{order}{null_handling}{return_type}{strict})", 3985 ) 3986 3987 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3988 path = self.sql(expression, "path") 3989 path = f" PATH {path}" if path else "" 3990 nested_schema = self.sql(expression, "nested_schema") 3991 3992 if nested_schema: 3993 return f"NESTED{path} {nested_schema}" 3994 3995 this = self.sql(expression, "this") 3996 kind = self.sql(expression, "kind") 3997 kind = f" {kind}" if kind else "" 3998 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3999 4000 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 4001 return f"{this}{kind}{format_json}{path}{ordinality}" 4002 4003 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 4004 return self.func("COLUMNS", *expression.expressions) 4005 4006 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4007 this = self.sql(expression, "this") 4008 path = self.sql(expression, "path") 4009 path = f", {path}" if path else "" 4010 error_handling = expression.args.get("error_handling") 4011 error_handling = f" {error_handling}" if error_handling else "" 4012 empty_handling = expression.args.get("empty_handling") 4013 empty_handling = f" {empty_handling}" if empty_handling else "" 4014 schema = self.sql(expression, "schema") 4015 return self.func( 4016 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4017 ) 4018 4019 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4020 this = self.sql(expression, "this") 4021 kind = self.sql(expression, "kind") 4022 path = self.sql(expression, "path") 4023 path = f" {path}" if path else "" 4024 as_json = " AS JSON" if expression.args.get("as_json") else "" 4025 return f"{this} {kind}{path}{as_json}" 4026 4027 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4028 this = self.sql(expression, "this") 4029 path = self.sql(expression, "path") 4030 path = f", {path}" if path else "" 4031 expressions = self.expressions(expression) 4032 with_ = ( 4033 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4034 if expressions 4035 else "" 4036 ) 4037 return f"OPENJSON({this}{path}){with_}" 4038 4039 def in_sql(self, expression: exp.In) -> str: 4040 query = expression.args.get("query") 4041 unnest = expression.args.get("unnest") 4042 field = expression.args.get("field") 4043 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4044 4045 if query: 4046 in_sql = self.sql(query) 4047 elif unnest: 4048 in_sql = self.in_unnest_op(unnest) 4049 elif field: 4050 in_sql = self.sql(field) 4051 else: 4052 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4053 4054 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 4055 4056 def in_unnest_op(self, unnest: exp.Unnest) -> str: 4057 return f"(SELECT {self.sql(unnest)})" 4058 4059 def interval_sql(self, expression: exp.Interval) -> str: 4060 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4061 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4062 exp.AutoRefreshProperty, 4063 ) 4064 interval_keyword = "INTERVAL" if include_keyword else "" 4065 unit_expression = expression.args.get("unit") 4066 unit = self.sql(unit_expression) if unit_expression else "" 4067 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4068 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4069 unit = f" {unit}" if unit else "" 4070 4071 if self.SINGLE_STRING_INTERVAL: 4072 this = expression.this.name if expression.this else "" 4073 if this: 4074 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4075 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4076 return f"{interval_keyword}'{this}'{unit}" 4077 return f"{interval_keyword}'{this}{unit}'" 4078 return f"{interval_keyword}{unit}" 4079 4080 this = self.sql(expression, "this") 4081 if this: 4082 if not include_keyword and expression.this.is_string: 4083 this = expression.this.name 4084 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4085 this = f"({this})" 4086 if include_keyword: 4087 this = f" {this}" 4088 4089 return f"{interval_keyword}{this}{unit}" 4090 4091 def return_sql(self, expression: exp.Return) -> str: 4092 return f"RETURN {self.sql(expression, 'this')}" 4093 4094 def reference_sql(self, expression: exp.Reference) -> str: 4095 this = self.sql(expression, "this") 4096 expressions = self.expressions(expression, flat=True) 4097 expressions = f"({expressions})" if expressions else "" 4098 options = self.expressions(expression, key="options", flat=True, sep=" ") 4099 options = f" {options}" if options else "" 4100 return f"REFERENCES {this}{expressions}{options}" 4101 4102 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4103 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4104 parent = expression.parent 4105 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4106 4107 return self.func( 4108 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4109 ) 4110 4111 def paren_sql(self, expression: exp.Paren) -> str: 4112 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4113 return f"({sql}{self.seg(')', sep='')}" 4114 4115 def neg_sql(self, expression: exp.Neg) -> str: 4116 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4117 this_sql = self.sql(expression, "this") 4118 sep = " " if this_sql[0] == "-" else "" 4119 return f"-{sep}{this_sql}" 4120 4121 def not_sql(self, expression: exp.Not) -> str: 4122 return f"NOT {self.sql(expression, 'this')}" 4123 4124 def alias_sql(self, expression: exp.Alias) -> str: 4125 alias = self.sql(expression, "alias") 4126 alias = f" AS {alias}" if alias else "" 4127 return f"{self.sql(expression, 'this')}{alias}" 4128 4129 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4130 alias = expression.args["alias"] 4131 4132 parent = expression.parent 4133 pivot = parent and parent.parent 4134 4135 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4136 identifier_alias = isinstance(alias, exp.Identifier) 4137 literal_alias = isinstance(alias, exp.Literal) 4138 4139 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4140 alias.replace(exp.Literal.string(alias.output_name)) 4141 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4142 alias.replace(exp.to_identifier(alias.output_name)) 4143 4144 return self.alias_sql(expression) 4145 4146 def aliases_sql(self, expression: exp.Aliases) -> str: 4147 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4148 4149 def atindex_sql(self, expression: exp.AtIndex) -> str: 4150 this = self.sql(expression, "this") 4151 index = self.sql(expression, "expression") 4152 return f"{this} AT {index}" 4153 4154 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4155 this = self.sql(expression, "this") 4156 zone = self.sql(expression, "zone") 4157 return f"{this} AT TIME ZONE {zone}" 4158 4159 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4160 this = self.sql(expression, "this") 4161 zone = self.sql(expression, "zone") 4162 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4163 4164 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4165 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4166 4167 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4168 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4169 4170 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4171 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4172 4173 def add_sql(self, expression: exp.Add) -> str: 4174 return self.binary(expression, "+") 4175 4176 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4177 return self.connector_sql(expression, "AND", stack) 4178 4179 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4180 return self.connector_sql(expression, "OR", stack) 4181 4182 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4183 return self.connector_sql(expression, "XOR", stack) 4184 4185 def connector_sql( 4186 self, 4187 expression: exp.Connector, 4188 op: str, 4189 stack: list[str | exp.Expr] | None = None, 4190 ) -> str: 4191 if stack is not None: 4192 stack.append(expression.right) 4193 if expression.comments and self.comments: 4194 op = self.maybe_comment(op, comments=expression.comments) 4195 4196 stack.extend((op, expression.left)) 4197 return op 4198 4199 stack = [expression] 4200 sqls: list[str] = [] 4201 ops = set() 4202 4203 while stack: 4204 node = stack.pop() 4205 if isinstance(node, exp.Connector): 4206 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4207 else: 4208 sql = self.sql(node) 4209 if sqls and sqls[-1] in ops: 4210 sqls[-1] += f" {sql}" 4211 else: 4212 sqls.append(sql) 4213 4214 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4215 return sep.join(sqls) 4216 4217 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4218 return self.binary(expression, "&") 4219 4220 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4221 return self.binary(expression, "<<") 4222 4223 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4224 return f"~{self.sql(expression, 'this')}" 4225 4226 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4227 return self.binary(expression, "|") 4228 4229 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4230 return self.binary(expression, ">>") 4231 4232 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4233 return self.binary(expression, "^") 4234 4235 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4236 format_sql = self.sql(expression, "format") 4237 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4238 to_sql = self.sql(expression, "to") 4239 to_sql = f" {to_sql}" if to_sql else "" 4240 action = self.sql(expression, "action") 4241 action = f" {action}" if action else "" 4242 default = self.sql(expression, "default") 4243 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4244 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4245 4246 # Base implementation that excludes safe, zone, and target_type metadata args 4247 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4248 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4249 4250 # Base implementation that excludes the safe and default_year metadata args 4251 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4252 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4253 4254 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4255 return self.func( 4256 "PARSE_DATETIME", 4257 expression.this, 4258 expression.args.get("format"), 4259 expression.args.get("zone"), 4260 ) 4261 4262 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4263 zone = self.sql(expression, "this") 4264 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4265 4266 def collate_sql(self, expression: exp.Collate) -> str: 4267 if self.COLLATE_IS_FUNC: 4268 return self.function_fallback_sql(expression) 4269 return self.binary(expression, "COLLATE") 4270 4271 def command_sql(self, expression: exp.Command) -> str: 4272 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4273 4274 def comment_sql(self, expression: exp.Comment) -> str: 4275 this = self.sql(expression, "this") 4276 kind = expression.args["kind"] 4277 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4278 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4279 expression_sql = self.sql(expression, "expression") 4280 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4281 4282 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4283 this = self.sql(expression, "this") 4284 delete = " DELETE" if expression.args.get("delete") else "" 4285 recompress = self.sql(expression, "recompress") 4286 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4287 to_disk = self.sql(expression, "to_disk") 4288 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4289 to_volume = self.sql(expression, "to_volume") 4290 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4291 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4292 4293 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4294 where = self.sql(expression, "where") 4295 group = self.sql(expression, "group") 4296 aggregates = self.expressions(expression, key="aggregates") 4297 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4298 4299 if not (where or group or aggregates) and len(expression.expressions) == 1: 4300 return f"TTL {self.expressions(expression, flat=True)}" 4301 4302 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4303 4304 def transaction_sql(self, expression: exp.Transaction) -> str: 4305 modes = self.expressions(expression, key="modes") 4306 modes = f" {modes}" if modes else "" 4307 return f"BEGIN{modes}" 4308 4309 def commit_sql(self, expression: exp.Commit) -> str: 4310 chain = expression.args.get("chain") 4311 if chain is not None: 4312 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4313 4314 return f"COMMIT{chain or ''}" 4315 4316 def rollback_sql(self, expression: exp.Rollback) -> str: 4317 savepoint = expression.args.get("savepoint") 4318 savepoint = f" TO {savepoint}" if savepoint else "" 4319 return f"ROLLBACK{savepoint}" 4320 4321 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4322 this = self.sql(expression, "this") 4323 4324 exists = "" 4325 if expression.args.get("exists"): 4326 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4327 exists = " IF EXISTS" 4328 else: 4329 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4330 4331 dtype = self.sql(expression, "dtype") 4332 if dtype: 4333 collate = self.sql(expression, "collate") 4334 collate = f" COLLATE {collate}" if collate else "" 4335 using = self.sql(expression, "using") 4336 using = f" USING {using}" if using else "" 4337 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4338 null_constraint = self._alter_column_null_constraint_sql(expression) 4339 4340 return ( 4341 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4342 f"{collate}{using}{null_constraint}" 4343 ) 4344 4345 default = self.sql(expression, "default") 4346 if default: 4347 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4348 4349 comment = self.sql(expression, "comment") 4350 if comment: 4351 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4352 4353 visible = expression.args.get("visible") 4354 if visible: 4355 return f"ALTER COLUMN{exists} {this} SET {visible}" 4356 4357 allow_null = expression.args.get("allow_null") 4358 drop = expression.args.get("drop") 4359 4360 if not drop and not allow_null: 4361 self.unsupported("Unsupported ALTER COLUMN syntax") 4362 4363 if allow_null is not None: 4364 keyword = "DROP" if drop else "SET" 4365 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4366 4367 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4368 4369 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4370 allow_null = expression.args.get("allow_null") 4371 if allow_null is None: 4372 return "" 4373 4374 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4375 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4376 return "" 4377 4378 return " NULL" if allow_null else " NOT NULL" 4379 4380 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4381 this = self.sql(expression, "this") 4382 rename_from = self.sql(expression, "rename_from") 4383 if rename_from: 4384 if not self.SUPPORTS_CHANGE_COLUMN: 4385 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4386 return f"CHANGE COLUMN {rename_from} {this}" 4387 if not self.SUPPORTS_MODIFY_COLUMN: 4388 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4389 return f"MODIFY COLUMN {this}" 4390 4391 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4392 this = self.sql(expression, "this") 4393 4394 visible = expression.args.get("visible") 4395 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4396 4397 return f"ALTER INDEX {this} {visible_sql}" 4398 4399 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4400 this = self.sql(expression, "this") 4401 if not isinstance(expression.this, exp.Var): 4402 this = f"KEY DISTKEY {this}" 4403 return f"ALTER DISTSTYLE {this}" 4404 4405 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4406 compound = " COMPOUND" if expression.args.get("compound") else "" 4407 this = self.sql(expression, "this") 4408 expressions = self.expressions(expression, flat=True) 4409 expressions = f"({expressions})" if expressions else "" 4410 return f"ALTER{compound} SORTKEY {this or expressions}" 4411 4412 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4413 if not self.RENAME_TABLE_WITH_DB: 4414 # Remove db from tables 4415 expression = expression.transform( 4416 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4417 ).assert_is(exp.AlterRename) 4418 this = self.sql(expression, "this") 4419 to_kw = " TO" if include_to else "" 4420 return f"RENAME{to_kw} {this}" 4421 4422 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4423 exists = " IF EXISTS" if expression.args.get("exists") else "" 4424 old_column = self.sql(expression, "this") 4425 new_column = self.sql(expression, "to") 4426 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4427 4428 def alterset_sql(self, expression: exp.AlterSet) -> str: 4429 exprs = self.expressions(expression, flat=True) 4430 if self.ALTER_SET_WRAPPED: 4431 exprs = f"({exprs})" 4432 4433 return f"SET {exprs}" 4434 4435 def alter_sql(self, expression: exp.Alter) -> str: 4436 actions = expression.actions 4437 4438 if ( 4439 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4440 and actions 4441 and isinstance(actions[0], exp.ColumnDef) 4442 ): 4443 actions_sql = self.expressions(expression, key="actions", flat=True) 4444 actions_sql = f"ADD {actions_sql}" 4445 else: 4446 actions_list = [] 4447 for action in actions: 4448 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4449 action_sql = self.add_column_sql(action) 4450 else: 4451 action_sql = self.sql(action) 4452 if isinstance(action, exp.Query): 4453 action_sql = f"AS {action_sql}" 4454 4455 actions_list.append(action_sql) 4456 4457 actions_sql = self.format_args(*actions_list).lstrip("\n") 4458 4459 iceberg = ( 4460 "ICEBERG " 4461 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4462 else "" 4463 ) 4464 exists = " IF EXISTS" if expression.args.get("exists") else "" 4465 on_cluster = self.sql(expression, "cluster") 4466 on_cluster = f" {on_cluster}" if on_cluster else "" 4467 only = " ONLY" if expression.args.get("only") else "" 4468 options = expression.args.get("options") or [] 4469 options_sql = self.expressions( 4470 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4471 ) 4472 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4473 partitions_sql = self.expressions( 4474 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4475 sep=" ", 4476 flat=True, 4477 ) 4478 if partitions_sql and (actions_sql or options_sql): 4479 partitions_sql = f" {partitions_sql}" 4480 kind = self.sql(expression, "kind") 4481 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4482 check = " WITH CHECK" if expression.args.get("check") else "" 4483 cascade = ( 4484 " CASCADE" 4485 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4486 else "" 4487 ) 4488 this = self.sql(expression, "this") 4489 this = f" {this}" if this else "" 4490 4491 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}" 4492 4493 def altersession_sql(self, expression: exp.AlterSession) -> str: 4494 items_sql = self.expressions(expression, flat=True) 4495 keyword = "UNSET" if expression.args.get("unset") else "SET" 4496 return f"{keyword} {items_sql}" 4497 4498 def add_column_sql(self, expression: exp.Expr) -> str: 4499 sql = self.sql(expression) 4500 if isinstance(expression, exp.Schema): 4501 column_text = " COLUMNS" 4502 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4503 column_text = " COLUMN" 4504 else: 4505 column_text = "" 4506 4507 return f"ADD{column_text} {sql}" 4508 4509 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4510 expressions = self.expressions(expression) 4511 exists = " IF EXISTS " if expression.args.get("exists") else " " 4512 return f"DROP{exists}{expressions}" 4513 4514 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4515 return "DROP PRIMARY KEY" 4516 4517 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4518 return f"ADD {self.expressions(expression, indent=False)}" 4519 4520 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4521 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4522 location = self.sql(expression, "location") 4523 location = f" {location}" if location else "" 4524 return f"ADD {exists}{self.sql(expression.this)}{location}" 4525 4526 def distinct_sql(self, expression: exp.Distinct) -> str: 4527 this = self.expressions(expression, flat=True) 4528 4529 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4530 case = exp.case() 4531 for arg in expression.expressions: 4532 case = case.when(arg.is_(exp.null()), exp.null()) 4533 this = self.sql(case.else_(f"({this})")) 4534 4535 this = f" {this}" if this else "" 4536 4537 on = self.sql(expression, "on") 4538 on = f" ON {on}" if on else "" 4539 return f"DISTINCT{this}{on}" 4540 4541 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4542 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4543 4544 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4545 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4546 4547 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4548 this_sql = self.sql(expression, "this") 4549 expression_sql = self.sql(expression, "expression") 4550 kind = "MAX" if expression.args.get("max") else "MIN" 4551 return f"{this_sql} HAVING {kind} {expression_sql}" 4552 4553 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4554 return self._truncated_div_sql(expression.this, expression.expression) 4555 4556 def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str: 4557 div: exp.Expr = exp.Div(this=this, expression=divisor) 4558 if self.CAST_TO_INT_ROUNDS: 4559 div = exp.Trunc(this=div) 4560 4561 return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT))) 4562 4563 def dpipe_sql(self, expression: exp.DPipe) -> str: 4564 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4565 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4566 return self.binary(expression, "||") 4567 4568 def div_sql(self, expression: exp.Div) -> str: 4569 l, r = expression.left, expression.right 4570 4571 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4572 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4573 4574 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4575 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4576 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4577 4578 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4579 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4580 # Native integer division depends on the target's operand types, which may differ 4581 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4582 # integer literals or casts 4583 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4584 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4585 ): 4586 return self.sql(exp.IntDiv(this=l, expression=r)) 4587 return self._truncated_div_sql(l, r) 4588 4589 return self.binary(expression, "/") 4590 4591 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4592 n = exp._wrap(expression.this, exp.Binary) 4593 d = exp._wrap(expression.expression, exp.Binary) 4594 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4595 4596 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4597 return self.binary(expression, "OVERLAPS") 4598 4599 def distance_sql(self, expression: exp.Distance) -> str: 4600 return self.binary(expression, "<->") 4601 4602 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4603 return self.binary(expression, "<<->>") 4604 4605 def dot_sql(self, expression: exp.Dot) -> str: 4606 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4607 4608 def eq_sql(self, expression: exp.EQ) -> str: 4609 return self.binary(expression, "=") 4610 4611 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4612 return self.binary(expression, ":=") 4613 4614 def escape_sql(self, expression: exp.Escape) -> str: 4615 this = expression.this 4616 if ( 4617 isinstance(this, (exp.Like, exp.ILike)) 4618 and isinstance(this.expression, (exp.All, exp.Any)) 4619 and not self.SUPPORTS_LIKE_QUANTIFIERS 4620 ): 4621 return self._like_sql(this, escape=expression) 4622 return self.binary(expression, "ESCAPE") 4623 4624 def glob_sql(self, expression: exp.Glob) -> str: 4625 return self.binary(expression, "GLOB") 4626 4627 def gt_sql(self, expression: exp.GT) -> str: 4628 return self.binary(expression, ">") 4629 4630 def gte_sql(self, expression: exp.GTE) -> str: 4631 return self.binary(expression, ">=") 4632 4633 def is_sql(self, expression: exp.Is) -> str: 4634 negate = expression.args.get("negate") 4635 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4636 positive = bool(expression.expression.this) != bool(negate) 4637 return self.sql(expression.this if positive else exp.not_(expression.this)) 4638 return self.binary(expression, "IS NOT" if negate else "IS") 4639 4640 def _like_sql( 4641 self, 4642 expression: exp.Like | exp.ILike, 4643 escape: exp.Escape | None = None, 4644 ) -> str: 4645 this = expression.this 4646 rhs = expression.expression 4647 4648 if isinstance(expression, exp.Like): 4649 exp_class: type[exp.Like | exp.ILike] = exp.Like 4650 op = "LIKE" 4651 else: 4652 exp_class = exp.ILike 4653 op = "ILIKE" 4654 4655 if expression.args.get("negate"): 4656 op = f"NOT {op}" 4657 4658 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4659 exprs = rhs.this.unnest() 4660 4661 if isinstance(exprs, exp.Tuple): 4662 exprs = exprs.expressions 4663 else: 4664 exprs = [exprs] 4665 4666 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4667 4668 def _make_like(expr: exp.Expression) -> exp.Expression: 4669 like: exp.Expression = exp_class( 4670 this=this, expression=expr, negate=expression.args.get("negate") 4671 ) 4672 if escape: 4673 like = exp.Escape(this=like, expression=escape.expression.copy()) 4674 return like 4675 4676 like_expr: exp.Expr = _make_like(exprs[0]) 4677 for expr in exprs[1:]: 4678 like_expr = connective(like_expr, _make_like(expr), copy=False) 4679 4680 parent = escape.parent if escape else expression.parent 4681 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4682 parent, exp.Condition 4683 ): 4684 like_expr = exp.paren(like_expr, copy=False) 4685 4686 return self.sql(like_expr) 4687 4688 return self.binary(expression, op) 4689 4690 def like_sql(self, expression: exp.Like) -> str: 4691 return self._like_sql(expression) 4692 4693 def ilike_sql(self, expression: exp.ILike) -> str: 4694 return self._like_sql(expression) 4695 4696 def match_sql(self, expression: exp.Match) -> str: 4697 if expression.args.get("kind") or expression.args.get("analyzer"): 4698 self.unsupported("MATCH variants and USING ANALYZER are not supported in this dialect") 4699 return self.binary(expression, "MATCH") 4700 4701 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4702 return self.binary(expression, "SIMILAR TO") 4703 4704 def lt_sql(self, expression: exp.LT) -> str: 4705 return self.binary(expression, "<") 4706 4707 def lte_sql(self, expression: exp.LTE) -> str: 4708 return self.binary(expression, "<=") 4709 4710 def mod_sql(self, expression: exp.Mod) -> str: 4711 this = self.sql(expression, "this") 4712 expr = self.sql(expression, "expression") 4713 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4714 4715 parent = expression.parent 4716 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4717 return f"({sql})" 4718 4719 return sql 4720 4721 def mul_sql(self, expression: exp.Mul) -> str: 4722 return self.binary(expression, "*") 4723 4724 def neq_sql(self, expression: exp.NEQ) -> str: 4725 return self.binary(expression, "<>") 4726 4727 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4728 return self.binary(expression, "IS NOT DISTINCT FROM") 4729 4730 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4731 return self.binary(expression, "IS DISTINCT FROM") 4732 4733 def sub_sql(self, expression: exp.Sub) -> str: 4734 return self.binary(expression, "-") 4735 4736 def trycast_sql(self, expression: exp.TryCast) -> str: 4737 return self.cast_sql(expression, safe_prefix="TRY_") 4738 4739 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4740 return self.cast_sql(expression) 4741 4742 def try_sql(self, expression: exp.Try) -> str: 4743 if not self.TRY_SUPPORTED: 4744 self.unsupported("Unsupported TRY function") 4745 return self.sql(expression, "this") 4746 4747 return self.func("TRY", expression.this) 4748 4749 def log_sql(self, expression: exp.Log) -> str: 4750 this = expression.this 4751 expr = expression.expression 4752 4753 if self.dialect.LOG_BASE_FIRST is False: 4754 this, expr = expr, this 4755 elif self.dialect.LOG_BASE_FIRST is None and expr: 4756 if this.name in ("2", "10"): 4757 return self.func(f"LOG{this.name}", expr) 4758 4759 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4760 4761 return self.func("LOG", this, expr) 4762 4763 def use_sql(self, expression: exp.Use) -> str: 4764 kind = self.sql(expression, "kind") 4765 kind = f" {kind}" if kind else "" 4766 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4767 this = f" {this}" if this else "" 4768 return f"USE{kind}{this}" 4769 4770 def binary(self, expression: exp.Binary, op: str) -> str: 4771 sqls: list[str] = [] 4772 stack: list[None | str | exp.Expr] = [expression] 4773 binary_type = type(expression) 4774 4775 while stack: 4776 node = stack.pop() 4777 4778 if type(node) is binary_type: 4779 op_func = node.args.get("operator") 4780 if op_func: 4781 op = f"OPERATOR({self.sql(op_func)})" 4782 4783 stack.append(node.args.get("expression")) 4784 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4785 stack.append(node.args.get("this")) 4786 else: 4787 sqls.append(self.sql(node)) 4788 4789 return "".join(sqls) 4790 4791 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4792 to_clause = self.sql(expression, "to") 4793 if to_clause: 4794 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4795 4796 return self.function_fallback_sql(expression) 4797 4798 def function_fallback_sql(self, expression: exp.Func) -> str: 4799 args = [] 4800 4801 for key in expression.arg_types: 4802 arg_value = expression.args.get(key) 4803 4804 if isinstance(arg_value, list): 4805 for value in arg_value: 4806 args.append(value) 4807 elif arg_value is not None: 4808 args.append(arg_value) 4809 4810 return self.func(self.function_name(expression), *args) 4811 4812 def function_name(self, expression: exp.Func) -> str: 4813 name_key = self.dialect.ORIGINAL_NAME_META_KEY 4814 return (expression.meta_get(name_key) if name_key else None) or expression.sql_name() 4815 4816 def func( 4817 self, 4818 name: str, 4819 *args: t.Any, 4820 prefix: str = "(", 4821 suffix: str = ")", 4822 normalize: bool = True, 4823 ) -> str: 4824 name = self.normalize_func(name) if normalize else name 4825 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4826 4827 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4828 arg_sqls = tuple( 4829 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4830 ) 4831 if self.pretty and self.too_wide(arg_sqls): 4832 return self.indent( 4833 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4834 ) 4835 return sep.join(arg_sqls) 4836 4837 def too_wide(self, args: t.Iterable) -> bool: 4838 return sum(len(arg) for arg in args) > self.max_text_width 4839 4840 def format_time( 4841 self, 4842 expression: exp.Expr, 4843 inverse_time_mapping: dict[str, str] | None = None, 4844 inverse_time_trie: dict | None = None, 4845 ) -> str | None: 4846 return format_time( 4847 self.sql(expression, "format"), 4848 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4849 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4850 ) 4851 4852 def expressions( 4853 self, 4854 expression: exp.Expr | None = None, 4855 key: str | None = None, 4856 sqls: t.Collection[str | exp.Expr] | None = None, 4857 flat: bool = False, 4858 indent: bool = True, 4859 skip_first: bool = False, 4860 skip_last: bool = False, 4861 sep: str = ", ", 4862 prefix: str = "", 4863 dynamic: bool = False, 4864 new_line: bool = False, 4865 ) -> str: 4866 expressions = expression.args.get(key or "expressions") if expression else sqls 4867 4868 if not expressions: 4869 return "" 4870 4871 if flat: 4872 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4873 4874 num_sqls = len(expressions) 4875 result_sqls = [] 4876 4877 for i, e in enumerate(expressions): 4878 sql = self.sql(e, comment=False) 4879 if not sql: 4880 continue 4881 4882 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4883 4884 if self.pretty: 4885 if self.leading_comma: 4886 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4887 else: 4888 result_sqls.append( 4889 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4890 ) 4891 else: 4892 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4893 4894 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4895 if new_line: 4896 result_sqls.insert(0, "") 4897 result_sqls.append("") 4898 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4899 else: 4900 result_sql = "".join(result_sqls) 4901 4902 return ( 4903 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4904 if indent 4905 else result_sql 4906 ) 4907 4908 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4909 flat = flat or isinstance(expression.parent, exp.Properties) 4910 expressions_sql = self.expressions(expression, flat=flat) 4911 if flat: 4912 return f"{op} {expressions_sql}" 4913 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4914 4915 def naked_property(self, expression: exp.Property) -> str: 4916 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4917 if not property_name: 4918 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4919 return f"{property_name} {self.sql(expression, 'this')}" 4920 4921 def tag_sql(self, expression: exp.Tag) -> str: 4922 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4923 4924 def token_sql(self, token_type: TokenType) -> str: 4925 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4926 4927 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4928 this = self.sql(expression, "this") 4929 expressions = self.no_identify(self.expressions, expression) 4930 expressions = ( 4931 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4932 ) 4933 return f"{this}{expressions}" if expressions.strip() != "" else this 4934 4935 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4936 return self.expressions(expression, flat=True) 4937 4938 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4939 params = self.no_identify(self.expressions, expression, flat=True) 4940 body = self.sql(expression, "this") 4941 prefix = "TABLE " if expression.args.get("is_table") else "" 4942 return f"({params}) AS {prefix}{body}" 4943 4944 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4945 this = self.sql(expression, "this") 4946 expressions = self.expressions(expression, flat=True) 4947 return f"{this}({expressions})" 4948 4949 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4950 return self.binary(expression, "=>") 4951 4952 def when_sql(self, expression: exp.When) -> str: 4953 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4954 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4955 condition = self.sql(expression, "condition") 4956 condition = f" AND {condition}" if condition else "" 4957 4958 then_expression = expression.args.get("then") 4959 if isinstance(then_expression, exp.Insert): 4960 this = self.sql(then_expression, "this") 4961 this = f"INSERT {this}" if this else "INSERT" 4962 then = self.sql(then_expression, "expression") 4963 then = f"{this} VALUES {then}" if then else this 4964 elif isinstance(then_expression, exp.Update): 4965 if isinstance(then_expression.args.get("expressions"), exp.Star): 4966 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4967 else: 4968 expressions_sql = self.expressions(then_expression) 4969 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4970 else: 4971 then = self.sql(then_expression) 4972 4973 if isinstance(then_expression, (exp.Insert, exp.Update)): 4974 where = self.sql(then_expression, "where") 4975 if where and not self.SUPPORTS_MERGE_WHERE: 4976 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4977 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4978 where = "" 4979 then = f"{then}{where}" 4980 return f"WHEN {matched}{source}{condition} THEN {then}" 4981 4982 def whens_sql(self, expression: exp.Whens) -> str: 4983 return self.expressions(expression, sep=" ", indent=False) 4984 4985 def merge_sql(self, expression: exp.Merge) -> str: 4986 table = expression.this 4987 table_alias = "" 4988 4989 hints = table.args.get("hints") 4990 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4991 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4992 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4993 4994 this = self.sql(table) 4995 using = f"USING {self.sql(expression, 'using')}" 4996 whens = self.sql(expression, "whens") 4997 4998 on = self.sql(expression, "on") 4999 on = f"ON {on}" if on else "" 5000 5001 if not on: 5002 on = self.expressions(expression, key="using_cond") 5003 on = f"USING ({on})" if on else "" 5004 5005 returning = self.sql(expression, "returning") 5006 if returning: 5007 whens = f"{whens}{returning}" 5008 5009 sep = self.sep() 5010 5011 return self.prepend_ctes( 5012 expression, 5013 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5014 ) 5015 5016 @unsupported_args("format") 5017 def tochar_sql(self, expression: exp.ToChar) -> str: 5018 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 5019 5020 @unsupported_args("default") 5021 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5022 if not self.SUPPORTS_TO_NUMBER: 5023 self.unsupported("Unsupported TO_NUMBER function") 5024 return self._tonumber_cast_sql(expression) 5025 5026 fmt = expression.args.get("format") 5027 if not fmt: 5028 self.unsupported("Conversion format is required for TO_NUMBER") 5029 return self._tonumber_cast_sql(expression) 5030 5031 return self.func("TO_NUMBER", expression.this, fmt) 5032 5033 def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str: 5034 if expression.args.get("safe"): 5035 return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE"))) 5036 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 5037 5038 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5039 this = self.sql(expression, "this") 5040 kind = self.sql(expression, "kind") 5041 settings_sql = self.expressions(expression, key="settings", sep=" ") 5042 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5043 return f"{this}({kind}{args})" 5044 5045 def dictrange_sql(self, expression: exp.DictRange) -> str: 5046 this = self.sql(expression, "this") 5047 max = self.sql(expression, "max") 5048 min = self.sql(expression, "min") 5049 return f"{this}(MIN {min} MAX {max})" 5050 5051 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 5052 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 5053 5054 def aggregatekeyproperty_sql(self, expression: exp.AggregateKeyProperty) -> str: 5055 return f"AGGREGATE KEY ({self.expressions(expression, flat=True)})" 5056 5057 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 5058 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 5059 5060 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 5061 def uniquekeyproperty_sql( 5062 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 5063 ) -> str: 5064 return f"{prefix} ({self.expressions(expression, flat=True)})" 5065 5066 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 5067 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5068 expressions = self.expressions(expression, flat=True) 5069 expressions = f" {self.wrap(expressions)}" if expressions else "" 5070 buckets = self.sql(expression, "buckets") 5071 kind = self.sql(expression, "kind") 5072 buckets = f" BUCKETS {buckets}" if buckets else "" 5073 order = self.sql(expression, "order") 5074 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 5075 5076 def oncluster_sql(self, expression: exp.OnCluster) -> str: 5077 return "" 5078 5079 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5080 expressions = self.expressions(expression, key="expressions", flat=True) 5081 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5082 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5083 buckets = self.sql(expression, "buckets") 5084 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 5085 5086 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5087 this = self.sql(expression, "this") 5088 having = self.sql(expression, "having") 5089 5090 if having: 5091 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5092 5093 return self.func("ANY_VALUE", this) 5094 5095 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5096 transform = self.func("TRANSFORM", *expression.expressions) 5097 row_format_before = self.sql(expression, "row_format_before") 5098 row_format_before = f" {row_format_before}" if row_format_before else "" 5099 record_writer = self.sql(expression, "record_writer") 5100 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5101 using = f" USING {self.sql(expression, 'command_script')}" 5102 schema = self.sql(expression, "schema") 5103 schema = f" AS {schema}" if schema else "" 5104 row_format_after = self.sql(expression, "row_format_after") 5105 row_format_after = f" {row_format_after}" if row_format_after else "" 5106 record_reader = self.sql(expression, "record_reader") 5107 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5108 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5109 5110 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5111 key_block_size = self.sql(expression, "key_block_size") 5112 if key_block_size: 5113 return f"KEY_BLOCK_SIZE = {key_block_size}" 5114 5115 using = self.sql(expression, "using") 5116 if using: 5117 return f"USING {using}" 5118 5119 parser = self.sql(expression, "parser") 5120 if parser: 5121 return f"WITH PARSER {parser}" 5122 5123 comment = self.sql(expression, "comment") 5124 if comment: 5125 return f"COMMENT {comment}" 5126 5127 visible = expression.args.get("visible") 5128 if visible is not None: 5129 return "VISIBLE" if visible else "INVISIBLE" 5130 5131 engine_attr = self.sql(expression, "engine_attr") 5132 if engine_attr: 5133 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5134 5135 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5136 if secondary_engine_attr: 5137 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5138 5139 self.unsupported("Unsupported index constraint option.") 5140 return "" 5141 5142 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5143 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5144 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5145 5146 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5147 kind = self.sql(expression, "kind") 5148 kind = f"{kind} INDEX" if kind else "INDEX" 5149 this = self.sql(expression, "this") 5150 this = f" {this}" if this else "" 5151 index_type = self.sql(expression, "index_type") 5152 index_type = f" USING {index_type}" if index_type else "" 5153 expressions = self.expressions(expression, flat=True) 5154 expressions = f" ({expressions})" if expressions else "" 5155 options = self.expressions(expression, key="options", sep=" ") 5156 options = f" {options}" if options else "" 5157 return f"{kind}{this}{index_type}{expressions}{options}" 5158 5159 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5160 if self.NVL2_SUPPORTED: 5161 return self.function_fallback_sql(expression) 5162 5163 case = exp.Case().when( 5164 expression.this.is_(exp.null()).not_(copy=False), 5165 expression.args["true"], 5166 copy=False, 5167 ) 5168 else_cond = expression.args.get("false") 5169 if else_cond: 5170 case.else_(else_cond, copy=False) 5171 5172 return self.sql(case) 5173 5174 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5175 if expression.args.get("from_first") is False: 5176 self.unsupported("NTH_VALUE FROM LAST is not supported") 5177 5178 return self.function_fallback_sql(expression) 5179 5180 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5181 this = self.sql(expression, "this") 5182 expr = self.sql(expression, "expression") 5183 position = self.sql(expression, "position") 5184 position = f", {position}" if position else "" 5185 iterator = self.sql(expression, "iterator") 5186 condition = self.sql(expression, "condition") 5187 condition = f" IF {condition}" if condition else "" 5188 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5189 5190 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5191 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5192 5193 def opclass_sql(self, expression: exp.Opclass) -> str: 5194 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5195 5196 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5197 model = self.sql(expression, "this") 5198 model = f"MODEL {model}" 5199 expr = expression.expression 5200 if expr: 5201 expr_sql = self.sql(expression, "expression") 5202 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5203 else: 5204 expr_sql = None 5205 5206 parameters = self.sql(expression, "params_struct") or None 5207 5208 return self.func(name, model, expr_sql, parameters) 5209 5210 def predict_sql(self, expression: exp.Predict) -> str: 5211 return self._ml_sql(expression, "PREDICT") 5212 5213 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5214 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5215 return self._ml_sql(expression, name) 5216 5217 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5218 return self._ml_sql(expression, "GENERATE_TEXT") 5219 5220 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5221 return self._ml_sql(expression, "GENERATE_TABLE") 5222 5223 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5224 return self._ml_sql(expression, "GENERATE_BOOL") 5225 5226 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5227 return self._ml_sql(expression, "GENERATE_INT") 5228 5229 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5230 return self._ml_sql(expression, "GENERATE_DOUBLE") 5231 5232 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5233 return self._ml_sql(expression, "TRANSLATE") 5234 5235 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5236 return self._ml_sql(expression, "FORECAST") 5237 5238 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5239 this_sql = self.sql(expression, "this") 5240 if isinstance(expression.this, exp.Table): 5241 this_sql = f"TABLE {this_sql}" 5242 5243 return self.func( 5244 "FORECAST", 5245 this_sql, 5246 expression.args.get("data_col"), 5247 expression.args.get("timestamp_col"), 5248 expression.args.get("model"), 5249 expression.args.get("id_cols"), 5250 expression.args.get("horizon"), 5251 expression.args.get("forecast_end_timestamp"), 5252 expression.args.get("confidence_level"), 5253 expression.args.get("output_historical_time_series"), 5254 expression.args.get("context_window"), 5255 ) 5256 5257 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5258 this_sql = self.sql(expression, "this") 5259 if isinstance(expression.this, exp.Table): 5260 this_sql = f"TABLE {this_sql}" 5261 5262 return self.func( 5263 "FEATURES_AT_TIME", 5264 this_sql, 5265 expression.args.get("time"), 5266 expression.args.get("num_rows"), 5267 expression.args.get("ignore_feature_nulls"), 5268 ) 5269 5270 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5271 this_sql = self.sql(expression, "this") 5272 if isinstance(expression.this, exp.Table): 5273 this_sql = f"TABLE {this_sql}" 5274 5275 query_table = self.sql(expression, "query_table") 5276 if isinstance(expression.args["query_table"], exp.Table): 5277 query_table = f"TABLE {query_table}" 5278 5279 return self.func( 5280 "VECTOR_SEARCH", 5281 this_sql, 5282 expression.args.get("column_to_search"), 5283 query_table, 5284 expression.args.get("query_column_to_search"), 5285 expression.args.get("top_k"), 5286 expression.args.get("distance_type"), 5287 expression.args.get("options"), 5288 ) 5289 5290 def forin_sql(self, expression: exp.ForIn) -> str: 5291 this = self.sql(expression, "this") 5292 expression_sql = self.sql(expression, "expression") 5293 return f"FOR {this} DO {expression_sql}" 5294 5295 @unsupported_args("partition_start", "partition_end", "force", "mode") 5296 def refresh_sql(self, expression: exp.Refresh) -> str: 5297 this = self.sql(expression, "this") 5298 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5299 return f"REFRESH {kind}{this}" 5300 5301 def toarray_sql(self, expression: exp.ToArray) -> str: 5302 arg = expression.this 5303 if not arg.type: 5304 import sqlglot.optimizer.annotate_types 5305 5306 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5307 5308 if arg.is_type(exp.DType.ARRAY): 5309 return self.sql(arg) 5310 5311 cond_for_null = arg.is_(exp.null()) 5312 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5313 5314 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5315 this = expression.this 5316 time_format = self.format_time(expression) 5317 5318 if time_format: 5319 return self.sql( 5320 exp.cast( 5321 exp.StrToTime(this=this, format=expression.args["format"]), 5322 exp.DType.TIME, 5323 ) 5324 ) 5325 5326 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5327 return self.sql(this) 5328 5329 return self.sql(exp.cast(this, exp.DType.TIME)) 5330 5331 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5332 this = expression.this 5333 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5334 return self.sql(this) 5335 5336 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5337 5338 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5339 this = expression.this 5340 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5341 return self.sql(this) 5342 5343 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5344 5345 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5346 this = expression.this 5347 time_format = self.format_time(expression) 5348 safe = expression.args.get("safe") 5349 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5350 return self.sql( 5351 exp.cast( 5352 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5353 exp.DType.DATE, 5354 ) 5355 ) 5356 5357 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5358 return self.sql(this) 5359 5360 if safe: 5361 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5362 5363 return self.sql(exp.cast(this, exp.DType.DATE)) 5364 5365 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5366 return self.sql( 5367 exp.func( 5368 "DATEDIFF", 5369 expression.this, 5370 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5371 "day", 5372 ) 5373 ) 5374 5375 def lastday_sql(self, expression: exp.LastDay) -> str: 5376 if self.LAST_DAY_SUPPORTS_DATE_PART: 5377 return self.function_fallback_sql(expression) 5378 5379 unit = expression.args.get("unit") 5380 if unit and unit.name.upper() != "MONTH": 5381 self.unsupported("Date parts are not supported in LAST_DAY.") 5382 5383 return self.func("LAST_DAY", expression.this) 5384 5385 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5386 import sqlglot.dialects.dialect 5387 5388 return self.func( 5389 "DATE_ADD", 5390 expression.this, 5391 expression.expression, 5392 sqlglot.dialects.dialect.unit_to_str(expression), 5393 ) 5394 5395 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5396 this = expression.this 5397 position = expression.args["position"] 5398 offset = index_offset - (expression.args.get("offset") or 0) 5399 5400 if offset: 5401 if position.is_int: 5402 value = position.to_py() 5403 if value >= 0: 5404 position = exp.Literal.number(value + offset) 5405 elif offset < 0 and value == -1: 5406 # 1-based -1 appends, which a 0-based position can only express as the size 5407 position = exp.ArraySize(this=this.copy()) 5408 else: 5409 # Negative positions count from the end, so they shift in the opposite 5410 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5411 position = exp.Literal.number(value - offset) 5412 else: 5413 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5414 5415 return self.func("ARRAY_INSERT", this, position, expression.expression) 5416 5417 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5418 if self.CAN_IMPLEMENT_ARRAY_ANY: 5419 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5420 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5421 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5422 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5423 5424 import sqlglot.dialects.dialect 5425 5426 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5427 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5428 self.unsupported("ARRAY_ANY is unsupported") 5429 5430 return self.function_fallback_sql(expression) 5431 5432 def struct_sql(self, expression: exp.Struct) -> str: 5433 expression.set( 5434 "expressions", 5435 [ 5436 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5437 if isinstance(e, exp.PropertyEQ) 5438 else e 5439 for e in expression.expressions 5440 ], 5441 ) 5442 5443 return self.function_fallback_sql(expression) 5444 5445 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5446 low = self.sql(expression, "this") 5447 high = self.sql(expression, "expression") 5448 5449 return f"{low} TO {high}" 5450 5451 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5452 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5453 tables = f" {self.expressions(expression)}" 5454 5455 exists = " IF EXISTS" if expression.args.get("exists") else "" 5456 5457 on_cluster = self.sql(expression, "cluster") 5458 on_cluster = f" {on_cluster}" if on_cluster else "" 5459 5460 identity = self.sql(expression, "identity") 5461 identity = f" {identity} IDENTITY" if identity else "" 5462 5463 option = self.sql(expression, "option") 5464 option = f" {option}" if option else "" 5465 5466 partition = self.sql(expression, "partition") 5467 partition = f" {partition}" if partition else "" 5468 5469 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5470 5471 # This transpiles T-SQL's CONVERT function 5472 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5473 def convert_sql(self, expression: exp.Convert) -> str: 5474 to = expression.this 5475 value = expression.expression 5476 style = expression.args.get("style") 5477 safe = expression.args.get("safe") 5478 strict = expression.args.get("strict") 5479 5480 if not to or not value: 5481 return "" 5482 5483 # Retrieve length of datatype and override to default if not specified 5484 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5485 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5486 5487 transformed: exp.Expr | None = None 5488 cast = exp.Cast if strict else exp.TryCast 5489 5490 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5491 if isinstance(style, exp.Literal) and style.is_int: 5492 import sqlglot.dialects.tsql 5493 5494 style_value = style.name 5495 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5496 if not converted_style: 5497 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5498 5499 fmt = exp.Literal.string(converted_style) 5500 5501 if to.this == exp.DType.DATE: 5502 transformed = exp.StrToDate(this=value, format=fmt) 5503 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5504 transformed = exp.StrToTime(this=value, format=fmt) 5505 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5506 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5507 elif to.this == exp.DType.TEXT: 5508 transformed = exp.TimeToStr(this=value, format=fmt) 5509 5510 if not transformed: 5511 transformed = cast(this=value, to=to, safe=safe) 5512 5513 return self.sql(transformed) 5514 5515 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5516 this = expression.this 5517 if isinstance(this, exp.JSONPathWildcard): 5518 this = self.json_path_part(this) 5519 return f".{this}" if this else "" 5520 5521 quoted = expression.args.get("quoted") 5522 if not ( 5523 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5524 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5525 return f".{this}" 5526 5527 this = self.json_path_part(this) 5528 5529 return ( 5530 f"[{this}]" 5531 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5532 else f".{this}" 5533 ) 5534 5535 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5536 this = self.json_path_part(expression.this) 5537 return f"[{this}]" if this else "" 5538 5539 def _simplify_unless_literal(self, expression: E) -> E: 5540 if not isinstance(expression, exp.Literal): 5541 import sqlglot.optimizer.simplify 5542 5543 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5544 5545 return expression 5546 5547 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5548 this = expression.this 5549 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5550 self.unsupported( 5551 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5552 ) 5553 return self.sql(this) 5554 5555 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5556 if self.IGNORE_NULLS_BEFORE_ORDER: 5557 from sqlglot.optimizer.scope import find_all_in_scope 5558 5559 # The first modifier here will be the one closest to the AggFunc's arg 5560 mods = sorted( 5561 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5562 key=lambda x: ( 5563 0 5564 if isinstance(x, exp.HavingMax) 5565 else (1 if isinstance(x, exp.Order) else 2) 5566 ), 5567 ) 5568 5569 if mods: 5570 mod = mods[0] 5571 this = expression.__class__(this=mod.this.copy()) 5572 this.meta["inline"] = True 5573 mod.this.replace(this) 5574 return self.sql(expression.this) 5575 5576 agg_func = expression.find(exp.AggFunc) 5577 5578 if agg_func: 5579 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5580 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5581 5582 return f"{self.sql(expression, 'this')} {text}" 5583 5584 def _replace_line_breaks(self, string: str) -> str: 5585 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5586 if self.pretty: 5587 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5588 return string 5589 5590 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5591 option = self.sql(expression, "this") 5592 5593 if expression.expressions: 5594 upper = option.upper() 5595 5596 # Snowflake FILE_FORMAT options are separated by whitespace 5597 sep = " " if upper == "FILE_FORMAT" else ", " 5598 5599 # Databricks copy/format options do not set their list of values with EQ 5600 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5601 values = self.expressions(expression, flat=True, sep=sep) 5602 return f"{option}{op}({values})" 5603 5604 value = self.sql(expression, "expression") 5605 5606 if not value: 5607 return option 5608 5609 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5610 5611 return f"{option}{op}{value}" 5612 5613 def credentials_sql(self, expression: exp.Credentials) -> str: 5614 cred_expr = expression.args.get("credentials") 5615 if isinstance(cred_expr, exp.Literal): 5616 # Redshift case: CREDENTIALS <string> 5617 credentials = self.sql(expression, "credentials") 5618 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5619 else: 5620 # Snowflake case: CREDENTIALS = (...) 5621 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5622 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5623 5624 storage = self.sql(expression, "storage") 5625 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5626 5627 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5628 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5629 5630 iam_role = self.sql(expression, "iam_role") 5631 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5632 5633 region = self.sql(expression, "region") 5634 region = f" REGION {region}" if region else "" 5635 5636 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5637 5638 def copy_sql(self, expression: exp.Copy) -> str: 5639 this = self.sql(expression, "this") 5640 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5641 5642 credentials = self.sql(expression, "credentials") 5643 credentials = self.seg(credentials) if credentials else "" 5644 files = self.expressions(expression, key="files", flat=True) 5645 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5646 5647 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5648 params = self.expressions( 5649 expression, 5650 key="params", 5651 sep=sep, 5652 new_line=True, 5653 skip_last=True, 5654 skip_first=True, 5655 indent=self.COPY_PARAMS_ARE_WRAPPED, 5656 ) 5657 5658 if params: 5659 if self.COPY_PARAMS_ARE_WRAPPED: 5660 params = f" WITH ({params})" 5661 elif not self.pretty and (files or credentials): 5662 params = f" {params}" 5663 5664 return f"COPY{this}{kind} {files}{credentials}{params}" 5665 5666 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5667 return "" 5668 5669 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5670 on_sql = "ON" if expression.args.get("on") else "OFF" 5671 filter_col: str | None = self.sql(expression, "filter_column") 5672 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5673 retention_period: str | None = self.sql(expression, "retention_period") 5674 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5675 5676 if filter_col or retention_period: 5677 on_sql = self.func("ON", filter_col, retention_period) 5678 5679 return f"DATA_DELETION={on_sql}" 5680 5681 def maskingpolicycolumnconstraint_sql( 5682 self, expression: exp.MaskingPolicyColumnConstraint 5683 ) -> str: 5684 this = self.sql(expression, "this") 5685 expressions = self.expressions(expression, flat=True) 5686 expressions = f" USING ({expressions})" if expressions else "" 5687 return f"MASKING POLICY {this}{expressions}" 5688 5689 def gapfill_sql(self, expression: exp.GapFill) -> str: 5690 this = self.sql(expression, "this") 5691 this = f"TABLE {this}" 5692 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5693 5694 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5695 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5696 5697 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5698 this = self.sql(expression, "this") 5699 expr = expression.expression 5700 5701 if isinstance(expr, exp.Func): 5702 # T-SQL's CLR functions are case sensitive 5703 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5704 else: 5705 expr = self.sql(expression, "expression") 5706 5707 return self.scope_resolution(expr, this) 5708 5709 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5710 if self.PARSE_JSON_NAME is None: 5711 return self.sql(expression.this) 5712 5713 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5714 5715 def rand_sql(self, expression: exp.Rand) -> str: 5716 lower = self.sql(expression, "lower") 5717 upper = self.sql(expression, "upper") 5718 5719 if lower and upper: 5720 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5721 return self.func("RAND", expression.this) 5722 5723 def changes_sql(self, expression: exp.Changes) -> str: 5724 information = self.sql(expression, "information") 5725 information = f"INFORMATION => {information}" 5726 at_before = self.sql(expression, "at_before") 5727 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5728 end = self.sql(expression, "end") 5729 end = f"{self.seg('')}{end}" if end else "" 5730 5731 return f"CHANGES ({information}){at_before}{end}" 5732 5733 def pad_sql(self, expression: exp.Pad) -> str: 5734 prefix = "L" if expression.args.get("is_left") else "R" 5735 5736 fill_pattern = self.sql(expression, "fill_pattern") or None 5737 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5738 fill_pattern = "' '" 5739 5740 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5741 5742 def summarize_sql(self, expression: exp.Summarize) -> str: 5743 table = " TABLE" if expression.args.get("table") else "" 5744 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5745 5746 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5747 generate_series = exp.GenerateSeries(**expression.args) 5748 5749 parent = expression.parent 5750 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5751 parent = parent.parent 5752 5753 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5754 return self.sql(exp.Unnest(expressions=[generate_series])) 5755 5756 if isinstance(parent, exp.Select): 5757 self.unsupported("GenerateSeries projection unnesting is not supported.") 5758 5759 return self.sql(generate_series) 5760 5761 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5762 if self.SUPPORTS_CONVERT_TIMEZONE: 5763 return self.function_fallback_sql(expression) 5764 5765 source_tz = expression.args.get("source_tz") 5766 target_tz = expression.args.get("target_tz") 5767 timestamp = expression.args.get("timestamp") 5768 5769 if source_tz and timestamp: 5770 timestamp = exp.AtTimeZone( 5771 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5772 ) 5773 5774 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5775 5776 return self.sql(expr) 5777 5778 def json_sql(self, expression: exp.JSON) -> str: 5779 this = self.sql(expression, "this") 5780 this = f" {this}" if this else "" 5781 5782 _with = expression.args.get("with_") 5783 5784 if _with is None: 5785 with_sql = "" 5786 elif not _with: 5787 with_sql = " WITHOUT" 5788 else: 5789 with_sql = " WITH" 5790 5791 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5792 5793 return f"JSON{this}{with_sql}{unique_sql}" 5794 5795 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5796 path = self.sql(expression, "path") 5797 returning = self.sql(expression, "returning") 5798 returning = f" RETURNING {returning}" if returning else "" 5799 5800 on_condition = self.sql(expression, "on_condition") 5801 on_condition = f" {on_condition}" if on_condition else "" 5802 5803 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5804 5805 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5806 regexp = " REGEXP" if expression.args.get("regexp") else "" 5807 return f"SKIP{regexp} {self.sql(expression.expression)}" 5808 5809 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5810 else_ = "ELSE " if expression.args.get("else_") else "" 5811 condition = self.sql(expression, "expression") 5812 condition = f"WHEN {condition} THEN " if condition else else_ 5813 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5814 return f"{condition}{insert}" 5815 5816 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5817 kind = self.sql(expression, "kind") 5818 source = self.sql(expression, "source") 5819 5820 if kind: 5821 expressions = self.seg(self.expressions(expression, sep=" ")) 5822 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5823 else: 5824 # If kind isn't present, we have Hive's FROM-first form: 5825 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5826 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5827 sql = f"FROM {source}{self.seg(inserts)}" 5828 5829 return self.prepend_ctes(expression, sql) 5830 5831 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5832 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5833 empty = expression.args.get("empty") 5834 empty = ( 5835 f"DEFAULT {empty} ON EMPTY" 5836 if isinstance(empty, exp.Expr) 5837 else self.sql(expression, "empty") 5838 ) 5839 5840 error = expression.args.get("error") 5841 error = ( 5842 f"DEFAULT {error} ON ERROR" 5843 if isinstance(error, exp.Expr) 5844 else self.sql(expression, "error") 5845 ) 5846 5847 if error and empty: 5848 error = ( 5849 f"{empty} {error}" 5850 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5851 else f"{error} {empty}" 5852 ) 5853 empty = "" 5854 5855 null = self.sql(expression, "null") 5856 5857 return f"{empty}{error}{null}" 5858 5859 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5860 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5861 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5862 5863 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5864 this = self.sql(expression, "this") 5865 path = self.sql(expression, "path") 5866 5867 passing = self.expressions(expression, "passing") 5868 passing = f" PASSING {passing}" if passing else "" 5869 5870 on_condition = self.sql(expression, "on_condition") 5871 on_condition = f" {on_condition}" if on_condition else "" 5872 5873 path = f"{path}{passing}{on_condition}" 5874 5875 return self.func("JSON_EXISTS", this, path) 5876 5877 def _add_arrayagg_null_filter( 5878 self, 5879 array_agg_sql: str, 5880 array_agg_expr: exp.ArrayAgg, 5881 column_expr: exp.Expr, 5882 ) -> str: 5883 """ 5884 Add NULL filter to ARRAY_AGG if dialect requires it. 5885 5886 Args: 5887 array_agg_sql: The generated ARRAY_AGG SQL string 5888 array_agg_expr: The ArrayAgg expression node 5889 column_expr: The column/expression to filter (before ORDER BY wrapping) 5890 5891 Returns: 5892 SQL string with FILTER clause added if needed 5893 """ 5894 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5895 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5896 if not ( 5897 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5898 ): 5899 return array_agg_sql 5900 5901 parent = array_agg_expr.parent 5902 if isinstance(parent, exp.Filter): 5903 parent_cond = parent.expression.this 5904 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5905 elif column_expr.find(exp.Column): 5906 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5907 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5908 this_sql = ( 5909 self.expressions(column_expr) 5910 if isinstance(column_expr, exp.Distinct) 5911 else self.sql(column_expr) 5912 ) 5913 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5914 5915 return array_agg_sql 5916 5917 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5918 array_agg = self.function_fallback_sql(expression) 5919 column_expr = expression.this 5920 if isinstance(column_expr, exp.Order): 5921 column_expr = column_expr.this 5922 5923 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5924 5925 def slice_sql(self, expression: exp.Slice) -> str: 5926 step = self.sql(expression, "step") 5927 end = self.sql(expression.expression) 5928 begin = self.sql(expression.this) 5929 5930 sql = f"{end}:{step}" if step else end 5931 return f"{begin}:{sql}" if sql else f"{begin}:" 5932 5933 def apply_sql(self, expression: exp.Apply) -> str: 5934 this = self.sql(expression, "this") 5935 expr = self.sql(expression, "expression") 5936 5937 return f"{this} APPLY({expr})" 5938 5939 def _grant_or_revoke_sql( 5940 self, 5941 expression: exp.Grant | exp.Revoke, 5942 keyword: str, 5943 preposition: str, 5944 grant_option_prefix: str = "", 5945 grant_option_suffix: str = "", 5946 ) -> str: 5947 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5948 5949 kind = self.sql(expression, "kind") 5950 kind = f" {kind}" if kind else "" 5951 5952 securable = self.sql(expression, "securable") 5953 securable = f" {securable}" if securable else "" 5954 5955 principals = self.expressions(expression, key="principals", flat=True) 5956 5957 if not expression.args.get("grant_option"): 5958 grant_option_prefix = grant_option_suffix = "" 5959 5960 # cascade for revoke only 5961 cascade = self.sql(expression, "cascade") 5962 cascade = f" {cascade}" if cascade else "" 5963 5964 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5965 5966 def grant_sql(self, expression: exp.Grant) -> str: 5967 return self._grant_or_revoke_sql( 5968 expression, 5969 keyword="GRANT", 5970 preposition="TO", 5971 grant_option_suffix=" WITH GRANT OPTION", 5972 ) 5973 5974 def revoke_sql(self, expression: exp.Revoke) -> str: 5975 return self._grant_or_revoke_sql( 5976 expression, 5977 keyword="REVOKE", 5978 preposition="FROM", 5979 grant_option_prefix="GRANT OPTION FOR ", 5980 ) 5981 5982 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5983 this = self.sql(expression, "this") 5984 columns = self.expressions(expression, flat=True) 5985 columns = f"({columns})" if columns else "" 5986 5987 return f"{this}{columns}" 5988 5989 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5990 this = self.sql(expression, "this") 5991 5992 kind = self.sql(expression, "kind") 5993 kind = f"{kind} " if kind else "" 5994 5995 return f"{kind}{this}" 5996 5997 def columns_sql(self, expression: exp.Columns) -> str: 5998 func = self.function_fallback_sql(expression) 5999 if expression.args.get("unpack"): 6000 func = f"*{func}" 6001 6002 return func 6003 6004 def overlay_sql(self, expression: exp.Overlay) -> str: 6005 this = self.sql(expression, "this") 6006 expr = self.sql(expression, "expression") 6007 from_sql = self.sql(expression, "from_") 6008 for_sql = self.sql(expression, "for_") 6009 for_sql = f" FOR {for_sql}" if for_sql else "" 6010 6011 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 6012 6013 @unsupported_args("format") 6014 def todouble_sql(self, expression: exp.ToDouble) -> str: 6015 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 6016 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 6017 6018 def string_sql(self, expression: exp.String) -> str: 6019 this = expression.this 6020 zone = expression.args.get("zone") 6021 6022 if zone: 6023 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6024 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6025 # set for source_tz to transpile the time conversion before the STRING cast 6026 this = exp.ConvertTimezone( 6027 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6028 ) 6029 6030 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 6031 6032 def median_sql(self, expression: exp.Median) -> str: 6033 if not self.SUPPORTS_MEDIAN: 6034 return self.sql( 6035 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 6036 ) 6037 6038 return self.function_fallback_sql(expression) 6039 6040 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6041 filler = self.sql(expression, "this") 6042 filler = f" {filler}" if filler else "" 6043 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6044 return f"TRUNCATE{filler} {with_count}" 6045 6046 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6047 if self.SUPPORTS_UNIX_SECONDS: 6048 return self.function_fallback_sql(expression) 6049 6050 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6051 6052 return self.sql( 6053 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6054 ) 6055 6056 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6057 dim = expression.expression 6058 6059 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6060 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6061 if not (dim.is_int and dim.name == "1"): 6062 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6063 dim = None 6064 6065 # If dimension is required but not specified, default initialize it 6066 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6067 dim = exp.Literal.number(1) 6068 6069 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 6070 6071 def attach_sql(self, expression: exp.Attach) -> str: 6072 this = self.sql(expression, "this") 6073 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6074 expressions = self.expressions(expression) 6075 expressions = f" ({expressions})" if expressions else "" 6076 6077 return f"ATTACH{exists_sql} {this}{expressions}" 6078 6079 def detach_sql(self, expression: exp.Detach) -> str: 6080 kind = self.sql(expression, "kind") 6081 kind = f" {kind}" if kind else "" 6082 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6083 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6084 exists = " IF EXISTS" if expression.args.get("exists") else "" 6085 if exists: 6086 kind = kind or " DATABASE" 6087 6088 this = self.sql(expression, "this") 6089 this = f" {this}" if this else "" 6090 cluster = self.sql(expression, "cluster") 6091 cluster = f" {cluster}" if cluster else "" 6092 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6093 sync = " SYNC" if expression.args.get("sync") else "" 6094 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 6095 6096 def attachoption_sql(self, expression: exp.AttachOption) -> str: 6097 this = self.sql(expression, "this") 6098 value = self.sql(expression, "expression") 6099 value = f" {value}" if value else "" 6100 return f"{this}{value}" 6101 6102 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 6103 return ( 6104 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 6105 ) 6106 6107 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6108 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6109 encode = f"{encode} {self.sql(expression, 'this')}" 6110 6111 properties = expression.args.get("properties") 6112 if properties: 6113 encode = f"{encode} {self.properties(properties)}" 6114 6115 return encode 6116 6117 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6118 this = self.sql(expression, "this") 6119 include = f"INCLUDE {this}" 6120 6121 column_def = self.sql(expression, "column_def") 6122 if column_def: 6123 include = f"{include} {column_def}" 6124 6125 alias = self.sql(expression, "alias") 6126 if alias: 6127 include = f"{include} AS {alias}" 6128 6129 return include 6130 6131 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6132 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6133 name = f"{prefix} {self.sql(expression, 'this')}" 6134 return self.func("XMLELEMENT", name, *expression.expressions) 6135 6136 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6137 this = self.sql(expression, "this") 6138 expr = self.sql(expression, "expression") 6139 expr = f"({expr})" if expr else "" 6140 return f"{this}{expr}" 6141 6142 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6143 partitions = self.expressions(expression, "partition_expressions") 6144 create = self.expressions(expression, "create_expressions") 6145 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6146 6147 def partitionbyrangepropertydynamic_sql( 6148 self, expression: exp.PartitionByRangePropertyDynamic 6149 ) -> str: 6150 start = self.sql(expression, "start") 6151 end = self.sql(expression, "end") 6152 6153 every = expression.args["every"] 6154 if isinstance(every, exp.Interval) and every.this.is_string: 6155 every.this.replace(exp.Literal.number(every.name)) 6156 6157 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6158 6159 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6160 name = self.sql(expression, "this") 6161 values = self.expressions(expression, flat=True) 6162 6163 return f"NAME {name} VALUE {values}" 6164 6165 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6166 kind = self.sql(expression, "kind") 6167 sample = self.sql(expression, "sample") 6168 return f"SAMPLE {sample} {kind}" 6169 6170 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6171 kind = self.sql(expression, "kind") 6172 option = self.sql(expression, "option") 6173 option = f" {option}" if option else "" 6174 this = self.sql(expression, "this") 6175 this = f" {this}" if this else "" 6176 columns = self.expressions(expression) 6177 columns = f" {columns}" if columns else "" 6178 return f"{kind}{option} STATISTICS{this}{columns}" 6179 6180 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6181 this = self.sql(expression, "this") 6182 columns = self.expressions(expression) 6183 inner_expression = self.sql(expression, "expression") 6184 inner_expression = f" {inner_expression}" if inner_expression else "" 6185 update_options = self.sql(expression, "update_options") 6186 update_options = f" {update_options} UPDATE" if update_options else "" 6187 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6188 6189 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6190 kind = self.sql(expression, "kind") 6191 kind = f" {kind}" if kind else "" 6192 return f"DELETE{kind} STATISTICS" 6193 6194 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6195 inner_expression = self.sql(expression, "expression") 6196 return f"LIST CHAINED ROWS{inner_expression}" 6197 6198 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6199 kind = self.sql(expression, "kind") 6200 this = self.sql(expression, "this") 6201 this = f" {this}" if this else "" 6202 inner_expression = self.sql(expression, "expression") 6203 return f"VALIDATE {kind}{this}{inner_expression}" 6204 6205 def analyze_sql(self, expression: exp.Analyze) -> str: 6206 options = self.expressions(expression, key="options", sep=" ") 6207 options = f" {options}" if options else "" 6208 kind = self.sql(expression, "kind") 6209 kind = f" {kind}" if kind else "" 6210 tables = self.expressions(expression, key="tables", flat=True) 6211 tables = f" {tables}" if tables else "" 6212 mode = self.sql(expression, "mode") 6213 mode = f" {mode}" if mode else "" 6214 properties = self.sql(expression, "properties") 6215 properties = f" {properties}" if properties else "" 6216 partition = self.sql(expression, "partition") 6217 partition = f" {partition}" if partition else "" 6218 inner_expression = self.sql(expression, "expression") 6219 inner_expression = f" {inner_expression}" if inner_expression else "" 6220 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6221 6222 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6223 this = self.sql(expression, "this") 6224 namespaces = self.expressions(expression, key="namespaces") 6225 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6226 passing = self.expressions(expression, key="passing") 6227 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6228 columns = self.expressions(expression, key="columns") 6229 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6230 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6231 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6232 6233 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6234 this = self.sql(expression, "this") 6235 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6236 6237 def export_sql(self, expression: exp.Export) -> str: 6238 this = self.sql(expression, "this") 6239 connection = self.sql(expression, "connection") 6240 connection = f"WITH CONNECTION {connection} " if connection else "" 6241 options = self.sql(expression, "options") 6242 return f"EXPORT DATA {connection}{options} AS {this}" 6243 6244 def declare_sql(self, expression: exp.Declare) -> str: 6245 replace = "OR REPLACE " if expression.args.get("replace") else "" 6246 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6247 6248 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6249 variables = self.expressions(expression, "this") 6250 default = self.sql(expression, "default") 6251 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6252 6253 kind = self.sql(expression, "kind") 6254 if isinstance(expression.args.get("kind"), exp.Schema): 6255 kind = f"TABLE {kind}" 6256 6257 kind = f" {kind}" if kind else "" 6258 6259 return f"{variables}{kind}{default}" 6260 6261 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6262 kind = self.sql(expression, "kind") 6263 this = self.sql(expression, "this") 6264 set = self.sql(expression, "expression") 6265 to = self.sql(expression, "to") 6266 to = f" TO {to}" if to else "" 6267 default = self.sql(expression, "default") 6268 default = f" DEFAULT {default}" if default else "" 6269 using = self.sql(expression, "using") 6270 using = f" USING {using}" if using else "" 6271 6272 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6273 6274 return f"{kind_sql} {this} SET {set}{to}{default}{using}" 6275 6276 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6277 params = self.expressions(expression, key="params", flat=True) 6278 return self.func(expression.name, *expression.expressions) + f"({params})" 6279 6280 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6281 return self.func(expression.name, *expression.expressions) 6282 6283 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6284 return self.anonymousaggfunc_sql(expression) 6285 6286 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6287 return self.parameterizedagg_sql(expression) 6288 6289 def show_sql(self, expression: exp.Show) -> str: 6290 self.unsupported("Unsupported SHOW statement") 6291 return "" 6292 6293 def install_sql(self, expression: exp.Install) -> str: 6294 self.unsupported("Unsupported INSTALL statement") 6295 return "" 6296 6297 def liststage_sql(self, expression: exp.ListStage) -> str: 6298 pattern = self.sql(expression, "pattern") 6299 pattern = f" PATTERN = {pattern}" if pattern else "" 6300 return f"LIST {self.sql(expression, 'this')}{pattern}" 6301 6302 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6303 # Snowflake GET/PUT statements: 6304 # PUT <file> <internalStage> <properties> 6305 # GET <internalStage> <file> <properties> 6306 props = expression.args.get("properties") 6307 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6308 this = self.sql(expression, "this") 6309 target = self.sql(expression, "target") 6310 6311 if isinstance(expression, exp.Put): 6312 return f"PUT {this} {target}{props_sql}" 6313 else: 6314 return f"GET {target} {this}{props_sql}" 6315 6316 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6317 this = self.sql(expression, "this") 6318 expr = self.sql(expression, "expression") 6319 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6320 return f"TRANSLATE({this} USING {expr}{with_error})" 6321 6322 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6323 if self.SUPPORTS_DECODE_CASE: 6324 return self.func("DECODE", *expression.expressions) 6325 6326 decode_expr, *expressions = expression.expressions 6327 6328 ifs = [] 6329 for search, result in zip(expressions[::2], expressions[1::2]): 6330 if isinstance(search, exp.Literal): 6331 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6332 elif isinstance(search, exp.Null): 6333 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6334 else: 6335 if isinstance(search, exp.Binary): 6336 search = exp.paren(search) 6337 6338 cond = exp.or_( 6339 decode_expr.eq(search), 6340 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6341 copy=False, 6342 ) 6343 ifs.append(exp.If(this=cond, true=result)) 6344 6345 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6346 return self.sql(case) 6347 6348 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6349 this = self.sql(expression, "this") 6350 this = self.seg(this, sep="") 6351 dimensions = self.expressions( 6352 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6353 ) 6354 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6355 metrics = self.expressions( 6356 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6357 ) 6358 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6359 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6360 facts = self.seg(f"FACTS {facts}") if facts else "" 6361 where = self.sql(expression, "where") 6362 where = self.seg(f"WHERE {where}") if where else "" 6363 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6364 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6365 6366 def getextract_sql(self, expression: exp.GetExtract) -> str: 6367 this = expression.this 6368 expr = expression.expression 6369 6370 if not this.type or not expression.type: 6371 import sqlglot.optimizer.annotate_types 6372 6373 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6374 6375 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6376 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6377 6378 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6379 6380 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6381 return self.sql( 6382 exp.DateAdd( 6383 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6384 expression=expression.this, 6385 unit=exp.var("DAY"), 6386 ) 6387 ) 6388 6389 def space_sql(self: Generator, expression: exp.Space) -> str: 6390 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6391 6392 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6393 return f"BUILD {self.sql(expression, 'this')}" 6394 6395 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6396 method = self.sql(expression, "method") 6397 kind = expression.args.get("kind") 6398 if not kind: 6399 return f"REFRESH {method}" 6400 6401 every = self.sql(expression, "every") 6402 unit = self.sql(expression, "unit") 6403 every = f" EVERY {every} {unit}" if every else "" 6404 starts = self.sql(expression, "starts") 6405 starts = f" STARTS {starts}" if starts else "" 6406 6407 return f"REFRESH {method} ON {kind}{every}{starts}" 6408 6409 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6410 self.unsupported("The model!attribute syntax is not supported") 6411 return "" 6412 6413 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6414 return self.func("DIRECTORY", expression.this) 6415 6416 def uuid_sql(self, expression: exp.Uuid) -> str: 6417 is_string = expression.args.get("is_string", False) 6418 uuid_func_sql = self.func("UUID") 6419 6420 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6421 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6422 6423 return uuid_func_sql 6424 6425 def initcap_sql(self, expression: exp.Initcap) -> str: 6426 delimiters = expression.expression 6427 6428 if delimiters: 6429 # do not generate delimiters arg if we are round-tripping from default delimiters 6430 if ( 6431 delimiters.is_string 6432 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6433 ): 6434 delimiters = None 6435 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6436 self.unsupported("INITCAP does not support custom delimiters") 6437 delimiters = None 6438 6439 return self.func("INITCAP", expression.this, delimiters) 6440 6441 def localtime_sql(self, expression: exp.Localtime) -> str: 6442 this = expression.this 6443 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6444 6445 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6446 this = expression.this 6447 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6448 6449 def weekstart_name(self, expression: exp.WeekStart) -> str: 6450 import sqlglot.dialects.dialect 6451 6452 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6453 this = expression.this.name.upper() 6454 6455 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6456 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6457 6458 if dow_from_week_start_day != dow_from_week_offset: 6459 self.unsupported( 6460 f"WEEK({this}) is not supported; falling back to the default week start day" 6461 ) 6462 6463 return "WEEK" 6464 6465 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6466 name = self.weekstart_name(expression) 6467 6468 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6469 if isinstance(expression.parent, exp.DateTrunc): 6470 return self.sql(exp.Literal.string(name)) 6471 6472 return name 6473 6474 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6475 this = self.expressions(expression) 6476 charset = self.sql(expression, "charset") 6477 using = f" USING {charset}" if charset else "" 6478 return self.func(name, this + using) 6479 6480 def weightstring_sql(self, expression: exp.WeightString) -> str: 6481 to = self.sql(expression, "to") 6482 to = f" AS {to}" if to else "" 6483 return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}") 6484 6485 def block_sql(self, expression: exp.Block) -> str: 6486 expressions = self.expressions(expression, sep="; ", flat=True) 6487 begin = "BEGIN " if expression.args.get("begin") else "" 6488 return f"{begin}{expressions}" if expressions else "" 6489 6490 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6491 self.unsupported("Unsupported Inline UDFs syntax") 6492 return "" 6493 6494 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6495 self.unsupported("Unsupported Stored Procedure syntax") 6496 return "" 6497 6498 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6499 self.unsupported("Unsupported If block syntax") 6500 return "" 6501 6502 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6503 self.unsupported("Unsupported Case statement syntax") 6504 return "" 6505 6506 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6507 self.unsupported("Unsupported While block syntax") 6508 return "" 6509 6510 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6511 self.unsupported("Unsupported Loop block syntax") 6512 return "" 6513 6514 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6515 self.unsupported("Unsupported Repeat block syntax") 6516 return "" 6517 6518 def leave_sql(self, expression: exp.Leave) -> str: 6519 self.unsupported("Unsupported Leave syntax") 6520 return "" 6521 6522 def iterate_sql(self, expression: exp.Iterate) -> str: 6523 self.unsupported("Unsupported Iterate syntax") 6524 return "" 6525 6526 def execute_sql(self, expression: exp.Execute) -> str: 6527 self.unsupported("Unsupported Execute syntax") 6528 return "" 6529 6530 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6531 self.unsupported("Unsupported Execute syntax") 6532 return "" 6533 6534 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6535 props = self.expressions(expression, sep=" ") 6536 return f"MODIFY {props}" 6537 6538 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6539 kind = expression.args.get("kind") 6540 return f"USING {kind} {self.sql(expression, 'this')}" 6541 6542 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6543 this = self.sql(expression, "this") 6544 to = self.sql(expression, "to") 6545 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.AggregateKeyProperty: exp.Properties.Location.POST_SCHEMA, 711 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 712 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 713 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 714 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 715 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 717 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 718 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 719 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 720 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 721 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 723 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 725 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 727 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 729 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 730 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 731 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 732 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 733 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 735 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 736 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 737 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 738 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 742 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 746 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 747 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 748 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 749 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 750 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 751 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 752 exp.HeapProperty: exp.Properties.Location.POST_WITH, 753 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 754 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 756 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 759 exp.JournalProperty: exp.Properties.Location.POST_NAME, 760 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 761 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 762 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 763 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 764 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 765 exp.LogProperty: exp.Properties.Location.POST_NAME, 766 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 767 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 768 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 769 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 770 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 771 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 772 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 773 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 774 exp.Order: exp.Properties.Location.POST_SCHEMA, 775 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 776 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 777 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 778 exp.PhysicalProperty: exp.Properties.Location.UNSUPPORTED, 779 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 780 exp.Property: exp.Properties.Location.POST_WITH, 781 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 782 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 783 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 784 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 785 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 786 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 787 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 788 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 789 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 790 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 791 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 792 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 793 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 794 exp.Set: exp.Properties.Location.POST_SCHEMA, 795 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 796 exp.SetProperty: exp.Properties.Location.POST_CREATE, 797 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 798 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 799 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 800 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 801 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 802 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 803 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 804 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 805 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 806 exp.StorageProperty: exp.Properties.Location.UNSUPPORTED, 807 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 808 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 809 exp.Tags: exp.Properties.Location.POST_WITH, 810 exp.TableSpaceProperty: exp.Properties.Location.UNSUPPORTED, 811 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 812 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 813 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 814 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 815 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 816 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 817 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 818 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 819 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 820 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 821 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 822 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 823 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 824 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 825 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 826 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 827 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 828 } 829 830 # Keywords that can't be used as unquoted identifier names 831 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 832 833 # Exprs whose comments are separated from them for better formatting 834 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 835 exp.Command, 836 exp.Create, 837 exp.Describe, 838 exp.Delete, 839 exp.Drop, 840 exp.From, 841 exp.Insert, 842 exp.Join, 843 exp.MultitableInserts, 844 exp.Order, 845 exp.Group, 846 exp.Having, 847 exp.Select, 848 exp.SetOperation, 849 exp.Update, 850 exp.Where, 851 exp.With, 852 ) 853 854 # Exprs that should not have their comments generated in maybe_comment 855 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 856 exp.Binary, 857 exp.SetOperation, 858 ) 859 860 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 861 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 862 exp.Column, 863 exp.Literal, 864 exp.Neg, 865 exp.Paren, 866 ) 867 868 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 869 exp.DType.NVARCHAR, 870 exp.DType.VARCHAR, 871 exp.DType.CHAR, 872 exp.DType.NCHAR, 873 } 874 875 # Exprs that need to have all CTEs under them bubbled up to them 876 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 877 878 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 879 880 MOD_OPERATOR = "%" 881 882 # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses 883 MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 884 exp.Mul, 885 exp.Div, 886 exp.IntDiv, 887 exp.Mod, 888 ) 889 890 # ALTER options that follow the others without a comma, e.g. MySQL's 891 # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ... 892 ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 893 894 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 895 896 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 897 898 __slots__ = ( 899 "pretty", 900 "identify", 901 "normalize", 902 "pad", 903 "_indent", 904 "normalize_functions", 905 "unsupported_level", 906 "max_unsupported", 907 "leading_comma", 908 "max_text_width", 909 "comments", 910 "dialect", 911 "unsupported_messages", 912 "_escaped_quote_end", 913 "_escaped_byte_quote_end", 914 "_escaped_identifier_end", 915 "_identifier_escaped_sequences", 916 "_next_name", 917 "_identifier_start", 918 "_identifier_end", 919 "_quote_json_path_key_using_brackets", 920 "_dispatch", 921 ) 922 923 def __init__( 924 self, 925 pretty: bool | int | None = None, 926 identify: str | bool = False, 927 normalize: bool = False, 928 pad: int = 2, 929 indent: int = 2, 930 normalize_functions: str | bool | None = None, 931 unsupported_level: ErrorLevel = ErrorLevel.WARN, 932 max_unsupported: int = 3, 933 leading_comma: bool = False, 934 max_text_width: int = 80, 935 comments: bool = True, 936 dialect: DialectType = None, 937 ): 938 import sqlglot 939 import sqlglot.dialects.dialect 940 941 self.pretty = pretty if pretty is not None else sqlglot.pretty 942 self.identify = identify 943 self.normalize = normalize 944 self.pad = pad 945 self._indent = indent 946 self.unsupported_level = unsupported_level 947 self.max_unsupported = max_unsupported 948 self.leading_comma = leading_comma 949 self.max_text_width = max_text_width 950 self.comments = comments 951 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 952 953 # This is both a Dialect property and a Generator argument, so we prioritize the latter 954 self.normalize_functions = ( 955 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 956 ) 957 958 self.unsupported_messages: list[str] = [] 959 self._escaped_quote_end: str = ( 960 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 961 ) 962 self._escaped_byte_quote_end: str = ( 963 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 964 if self.dialect.BYTE_END 965 else "" 966 ) 967 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 968 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 969 970 self._next_name = name_sequence("_t") 971 972 self._identifier_start = self.dialect.IDENTIFIER_START 973 self._identifier_end = self.dialect.IDENTIFIER_END 974 975 self._quote_json_path_key_using_brackets = True 976 977 cls = type(self) 978 dispatch = _DISPATCH_CACHE.get(cls) 979 if dispatch is None: 980 dispatch = _build_dispatch(cls) 981 _DISPATCH_CACHE[cls] = dispatch 982 self._dispatch = dispatch 983 984 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 985 """ 986 Generates the SQL string corresponding to the given syntax tree. 987 988 Args: 989 expression: The syntax tree. 990 copy: Whether to copy the expression. The generator performs mutations so 991 it is safer to copy. 992 993 Returns: 994 The SQL string corresponding to `expression`. 995 """ 996 if copy: 997 expression = expression.copy() 998 999 expression = self.preprocess(expression) 1000 1001 self.unsupported_messages = [] 1002 sql = self.sql(expression).strip() 1003 1004 if self.pretty: 1005 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1006 1007 if self.unsupported_level == ErrorLevel.IGNORE: 1008 return sql 1009 1010 if self.unsupported_level == ErrorLevel.WARN: 1011 for msg in self.unsupported_messages: 1012 logger.warning(msg) 1013 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1014 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1015 1016 return sql 1017 1018 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1019 """Apply generic preprocessing transformations to a given expression.""" 1020 expression = self._move_ctes_to_top_level(expression) 1021 1022 if self.ENSURE_BOOLS: 1023 import sqlglot.transforms 1024 1025 expression = sqlglot.transforms.ensure_bools(expression) 1026 1027 return expression 1028 1029 def _move_ctes_to_top_level(self, expression: E) -> E: 1030 if ( 1031 not expression.parent 1032 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 1033 and any(node.parent is not expression for node in expression.find_all(exp.With)) 1034 ): 1035 import sqlglot.transforms 1036 1037 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 1038 return expression 1039 1040 def unsupported(self, message: str) -> None: 1041 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1042 raise UnsupportedError(message) 1043 self.unsupported_messages.append(message) 1044 1045 def sep(self, sep: str = " ") -> str: 1046 return f"{sep.strip()}\n" if self.pretty else sep 1047 1048 def seg(self, sql: str, sep: str = " ") -> str: 1049 return f"{self.sep(sep)}{sql}" 1050 1051 def sanitize_comment(self, comment: str) -> str: 1052 comment = " " + comment if comment[0].strip() else comment 1053 comment = comment + " " if comment[-1].strip() else comment 1054 1055 # Escape block comment markers to prevent premature closure or unintended nesting. 1056 # This is necessary because single-line comments (--) are converted to block comments 1057 # (/* */) on output, and any */ in the original text would close the comment early. 1058 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1059 1060 return comment 1061 1062 def maybe_comment( 1063 self, 1064 sql: str, 1065 expression: exp.Expr | None = None, 1066 comments: list[str] | None = None, 1067 separated: bool = False, 1068 ) -> str: 1069 comments = ( 1070 ((expression and expression.comments) if comments is None else comments) # type: ignore 1071 if self.comments 1072 else None 1073 ) 1074 1075 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1076 return sql 1077 1078 comments_list = [ 1079 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1080 for comment in comments 1081 if comment 1082 ] 1083 1084 if not comments_list: 1085 return sql 1086 1087 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1088 comments_sql = self.sep().join(comments_list) 1089 return ( 1090 f"{self.sep()}{comments_sql}{sql}" 1091 if not sql or sql[0].isspace() 1092 else f"{comments_sql}{self.sep()}{sql}" 1093 ) 1094 1095 return f"{sql} {' '.join(comments_list)}" 1096 1097 def wrap(self, expression: exp.Expr | str) -> str: 1098 this_sql = ( 1099 self.sql(expression) 1100 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1101 else self.sql(expression, "this") 1102 ) 1103 if not this_sql: 1104 return "()" 1105 1106 this_sql = self.indent(this_sql, level=1, pad=0) 1107 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1108 1109 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1110 original = self.identify 1111 self.identify = False 1112 result = func(*args, **kwargs) 1113 self.identify = original 1114 return result 1115 1116 def normalize_func(self, name: str) -> str: 1117 if self.normalize_functions == "upper" or self.normalize_functions is True: 1118 return name.upper() 1119 if self.normalize_functions == "lower": 1120 return name.lower() 1121 return name 1122 1123 def indent( 1124 self, 1125 sql: str, 1126 level: int = 0, 1127 pad: int | None = None, 1128 skip_first: bool = False, 1129 skip_last: bool = False, 1130 ) -> str: 1131 if not self.pretty or not sql: 1132 return sql 1133 1134 pad = self.pad if pad is None else pad 1135 lines = sql.split("\n") 1136 1137 return "\n".join( 1138 ( 1139 line 1140 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1141 else f"{' ' * (level * self._indent + pad)}{line}" 1142 ) 1143 for i, line in enumerate(lines) 1144 ) 1145 1146 def sql( 1147 self, 1148 expression: str | exp.Expr | None, 1149 key: str | None = None, 1150 comment: bool = True, 1151 ) -> str: 1152 if not expression: 1153 return "" 1154 1155 if isinstance(expression, str): 1156 return expression 1157 1158 if key: 1159 value = expression.args.get(key) 1160 if value: 1161 return self.sql(value) 1162 return "" 1163 1164 handler = self._dispatch.get(expression.__class__) 1165 1166 if handler: 1167 sql = handler(self, expression) 1168 elif isinstance(expression, exp.Func): 1169 sql = self.function_fallback_sql(expression) 1170 elif isinstance(expression, exp.Property): 1171 sql = self.property_sql(expression) 1172 else: 1173 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1174 1175 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1176 1177 def uncache_sql(self, expression: exp.Uncache) -> str: 1178 table = self.sql(expression, "this") 1179 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1180 return f"UNCACHE TABLE{exists_sql} {table}" 1181 1182 def cache_sql(self, expression: exp.Cache) -> str: 1183 lazy = " LAZY" if expression.args.get("lazy") else "" 1184 table = self.sql(expression, "this") 1185 options = expression.args.get("options") 1186 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1187 sql = self.sql(expression, "expression") 1188 sql = f" AS{self.sep()}{sql}" if sql else "" 1189 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1190 return self.prepend_ctes(expression, sql) 1191 1192 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1193 default = "DEFAULT " if expression.args.get("default") else "" 1194 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1195 1196 def column_parts(self, expression: exp.Column) -> str: 1197 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1198 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1199 return self.sql(expression, "this") 1200 1201 return ".".join( 1202 self.sql(part) 1203 for part in ( 1204 expression.args.get("catalog"), 1205 expression.args.get("db"), 1206 expression.args.get("table"), 1207 expression.args.get("this"), 1208 ) 1209 if part 1210 ) 1211 1212 def column_sql(self, expression: exp.Column) -> str: 1213 join_mark = " (+)" if expression.args.get("join_mark") else "" 1214 1215 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1216 join_mark = "" 1217 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1218 1219 return f"{self.column_parts(expression)}{join_mark}" 1220 1221 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1222 return self.column_sql(expression) 1223 1224 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1225 this = self.sql(expression, "this") 1226 this = f" {this}" if this else "" 1227 position = self.sql(expression, "position") 1228 return f"{position}{this}" 1229 1230 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1231 column = self.sql(expression, "this") 1232 kind = self.sql(expression, "kind") 1233 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1234 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1235 kind = f"{sep}{kind}" if kind else "" 1236 constraints = f" {constraints}" if constraints else "" 1237 position = self.sql(expression, "position") 1238 position = f" {position}" if position else "" 1239 1240 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1241 kind = "" 1242 1243 return f"{exists}{column}{kind}{constraints}{position}" 1244 1245 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1246 this = self.sql(expression, "this") 1247 kind_sql = self.sql(expression, "kind").strip() 1248 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1249 1250 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1251 this = self.sql(expression, "this") 1252 if expression.args.get("not_null"): 1253 persisted = " PERSISTED NOT NULL" 1254 elif expression.args.get("persisted"): 1255 persisted = " PERSISTED" 1256 else: 1257 persisted = "" 1258 1259 return f"AS {this}{persisted}" 1260 1261 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1262 return self.token_sql(TokenType.AUTO_INCREMENT) 1263 1264 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1265 if isinstance(expression.this, list): 1266 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1267 else: 1268 this = self.sql(expression, "this") 1269 1270 return f"COMPRESS {this}" 1271 1272 def generatedasidentitycolumnconstraint_sql( 1273 self, expression: exp.GeneratedAsIdentityColumnConstraint 1274 ) -> str: 1275 this = "" 1276 if expression.this is not None: 1277 on_null = " ON NULL" if expression.args.get("on_null") else "" 1278 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1279 1280 start = expression.args.get("start") 1281 start = f"START WITH {start}" if start else "" 1282 increment = expression.args.get("increment") 1283 increment = f" INCREMENT BY {increment}" if increment else "" 1284 minvalue = expression.args.get("minvalue") 1285 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1286 maxvalue = expression.args.get("maxvalue") 1287 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1288 cycle = expression.args.get("cycle") 1289 cycle_sql = "" 1290 1291 if cycle is not None: 1292 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1293 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1294 1295 sequence_opts = "" 1296 if start or increment or cycle_sql: 1297 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1298 sequence_opts = f" ({sequence_opts.strip()})" 1299 1300 expr = self.sql(expression, "expression") 1301 expr = f"({expr})" if expr else "IDENTITY" 1302 1303 return f"GENERATED{this} AS {expr}{sequence_opts}" 1304 1305 def generatedasrowcolumnconstraint_sql( 1306 self, expression: exp.GeneratedAsRowColumnConstraint 1307 ) -> str: 1308 start = "START" if expression.args.get("start") else "END" 1309 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1310 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1311 1312 def periodforsystemtimeconstraint_sql( 1313 self, expression: exp.PeriodForSystemTimeConstraint 1314 ) -> str: 1315 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1316 1317 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1318 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1319 1320 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1321 desc = expression.args.get("desc") 1322 if desc is not None: 1323 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1324 options = self.expressions(expression, key="options", flat=True, sep=" ") 1325 options = f" {options}" if options else "" 1326 return f"PRIMARY KEY{options}" 1327 1328 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1329 this = self.sql(expression, "this") 1330 this = f" {this}" if this else "" 1331 index_type = expression.args.get("index_type") 1332 index_type = f" USING {index_type}" if index_type else "" 1333 on_conflict = self.sql(expression, "on_conflict") 1334 on_conflict = f" {on_conflict}" if on_conflict else "" 1335 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1336 options = self.expressions(expression, key="options", flat=True, sep=" ") 1337 options = f" {options}" if options else "" 1338 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1339 1340 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1341 input_ = expression.args.get("input_") 1342 output = expression.args.get("output") 1343 variadic = expression.args.get("variadic") 1344 1345 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1346 if variadic: 1347 return "VARIADIC" 1348 1349 if input_ and output: 1350 return f"IN{self.INOUT_SEPARATOR}OUT" 1351 if input_: 1352 return "IN" 1353 if output: 1354 return "OUT" 1355 1356 return "" 1357 1358 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1359 return self.sql(expression, "this") 1360 1361 def create_sql(self, expression: exp.Create) -> str: 1362 kind = self.sql(expression, "kind") 1363 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1364 1365 properties = expression.args.get("properties") 1366 1367 if ( 1368 kind == "TRIGGER" 1369 and properties 1370 and properties.expressions 1371 and isinstance(properties.expressions[0], exp.TriggerProperties) 1372 and properties.expressions[0].args.get("constraint") 1373 ): 1374 kind = f"CONSTRAINT {kind}" 1375 1376 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1377 1378 this = self.createable_sql(expression, properties_locs) 1379 1380 properties_sql = "" 1381 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1382 exp.Properties.Location.POST_WITH 1383 ): 1384 props_ast = exp.Properties( 1385 expressions=[ 1386 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1387 *properties_locs[exp.Properties.Location.POST_WITH], 1388 ] 1389 ) 1390 props_ast.parent = expression 1391 properties_sql = self.sql(props_ast) 1392 1393 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1394 properties_sql = self.sep() + properties_sql 1395 elif not self.pretty: 1396 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1397 properties_sql = f" {properties_sql}" 1398 1399 begin = " BEGIN" if expression.args.get("begin") else "" 1400 1401 expression_sql = self.sql(expression, "expression") 1402 if expression_sql: 1403 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1404 1405 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1406 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1407 ): 1408 postalias_props_sql = "" 1409 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1410 postalias_props_sql = self.properties( 1411 exp.Properties( 1412 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1413 ), 1414 wrapped=False, 1415 ) 1416 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1417 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1418 1419 postindex_props_sql = "" 1420 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1421 postindex_props_sql = self.properties( 1422 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1423 wrapped=False, 1424 prefix=" ", 1425 ) 1426 1427 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1428 indexes = f" {indexes}" if indexes else "" 1429 index_sql = indexes + postindex_props_sql 1430 1431 replace = " OR REPLACE" if expression.args.get("replace") else "" 1432 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1433 unique = " UNIQUE" if expression.args.get("unique") else "" 1434 1435 clustered = expression.args.get("clustered") 1436 if clustered is None: 1437 clustered_sql = "" 1438 elif clustered: 1439 clustered_sql = " CLUSTERED COLUMNSTORE" 1440 else: 1441 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1442 1443 postcreate_props_sql = "" 1444 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1445 postcreate_props_sql = self.properties( 1446 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1447 sep=" ", 1448 prefix=" ", 1449 wrapped=False, 1450 ) 1451 1452 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1453 1454 postexpression_props_sql = "" 1455 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1456 postexpression_props_sql = self.properties( 1457 exp.Properties( 1458 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1459 ), 1460 sep=" ", 1461 prefix=" ", 1462 wrapped=False, 1463 ) 1464 1465 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1466 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1467 no_schema_binding = ( 1468 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1469 ) 1470 1471 clone = self.sql(expression, "clone") 1472 clone = f" {clone}" if clone else "" 1473 1474 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1475 properties_expression = f"{expression_sql}{properties_sql}" 1476 else: 1477 properties_expression = f"{properties_sql}{expression_sql}" 1478 1479 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1480 return self.prepend_ctes(expression, expression_sql) 1481 1482 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1483 start = self.sql(expression, "start") 1484 start = f"START WITH {start}" if start else "" 1485 increment = self.sql(expression, "increment") 1486 increment = f" INCREMENT BY {increment}" if increment else "" 1487 minvalue = self.sql(expression, "minvalue") 1488 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1489 maxvalue = self.sql(expression, "maxvalue") 1490 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1491 owned = self.sql(expression, "owned") 1492 owned = f" OWNED BY {owned}" if owned else "" 1493 1494 cache = expression.args.get("cache") 1495 if cache is None: 1496 cache_str = "" 1497 elif cache is True: 1498 cache_str = " CACHE" 1499 else: 1500 cache_str = f" CACHE {cache}" 1501 1502 options = self.expressions(expression, key="options", flat=True, sep=" ") 1503 options = f" {options}" if options else "" 1504 1505 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1506 1507 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1508 timing = expression.args.get("timing", "") 1509 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1510 timing_events = f"{timing} {events}".strip() if timing or events else "" 1511 1512 parts = [timing_events, "ON", self.sql(expression, "table")] 1513 1514 if referenced_table := expression.args.get("referenced_table"): 1515 parts.extend(["FROM", self.sql(referenced_table)]) 1516 1517 if deferrable := expression.args.get("deferrable"): 1518 parts.append(deferrable) 1519 1520 if initially := expression.args.get("initially"): 1521 parts.append(f"INITIALLY {initially}") 1522 1523 if referencing := expression.args.get("referencing"): 1524 parts.append(self.sql(referencing)) 1525 1526 if for_each := expression.args.get("for_each"): 1527 parts.append(f"FOR EACH {for_each}") 1528 1529 if when := expression.args.get("when"): 1530 parts.append(f"WHEN ({self.sql(when)})") 1531 1532 parts.append(self.sql(expression, "execute")) 1533 1534 return self.sep().join(parts) 1535 1536 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1537 parts = [] 1538 1539 if old_alias := expression.args.get("old"): 1540 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1541 1542 if new_alias := expression.args.get("new"): 1543 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1544 1545 return f"REFERENCING {' '.join(parts)}" 1546 1547 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1548 columns = expression.args.get("columns") 1549 if columns: 1550 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1551 1552 return self.sql(expression, "this") 1553 1554 def clone_sql(self, expression: exp.Clone) -> str: 1555 this = self.sql(expression, "this") 1556 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1557 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1558 return f"{shallow}{keyword} {this}" 1559 1560 def describe_sql(self, expression: exp.Describe) -> str: 1561 style = expression.args.get("style") 1562 style = f" {style}" if style else "" 1563 partition = self.sql(expression, "partition") 1564 partition = f" {partition}" if partition else "" 1565 format = self.sql(expression, "format") 1566 format = f" {format}" if format else "" 1567 as_json = " AS JSON" if expression.args.get("as_json") else "" 1568 1569 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1570 1571 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1572 tag = self.sql(expression, "tag") 1573 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1574 1575 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1576 with_ = self.sql(expression, "with_") 1577 if with_: 1578 sql = f"{with_}{self.sep()}{sql}" 1579 return sql 1580 1581 def with_sql(self, expression: exp.With) -> str: 1582 udfs = self.expressions(expression, key="udfs", flat=True) 1583 udfs = f"WITH {udfs}" if udfs else "" 1584 1585 sql = self.expressions(expression, flat=True) 1586 1587 recursive = ( 1588 "RECURSIVE " 1589 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1590 else "" 1591 ) 1592 search = self.sql(expression, "search") 1593 search = f" {search}" if search else "" 1594 cycle = self.sql(expression, "cycle") 1595 cycle = f" {cycle}" if cycle else "" 1596 1597 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1598 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1599 1600 def cte_sql(self, expression: exp.CTE) -> str: 1601 alias = expression.args.get("alias") 1602 if alias: 1603 alias.add_comments(expression.pop_comments()) 1604 1605 alias_sql = self.sql(expression, "alias") 1606 1607 materialized = expression.args.get("materialized") 1608 if materialized is False: 1609 materialized = "NOT MATERIALIZED " 1610 elif materialized: 1611 materialized = "MATERIALIZED " 1612 1613 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1614 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1615 1616 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1617 1618 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1619 alias = self.sql(expression, "this") 1620 columns = self.expressions(expression, key="columns", flat=True) 1621 columns = f"({columns})" if columns else "" 1622 1623 if ( 1624 columns 1625 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1626 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1627 ): 1628 columns = "" 1629 self.unsupported("Named columns are not supported in table alias.") 1630 1631 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1632 alias = self._next_name() 1633 1634 return f"{alias}{columns}" 1635 1636 def bitstring_sql(self, expression: exp.BitString) -> str: 1637 this = self.sql(expression, "this") 1638 if self.dialect.BIT_START: 1639 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1640 return f"{int(this, 2)}" 1641 1642 def hexstring_sql( 1643 self, expression: exp.HexString, binary_function_repr: str | None = None 1644 ) -> str: 1645 this = self.sql(expression, "this") 1646 is_integer_type = expression.args.get("is_integer") 1647 1648 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1649 not self.dialect.HEX_START and not binary_function_repr 1650 ): 1651 # Integer representation will be returned if: 1652 # - The read dialect treats the hex value as integer literal but not the write 1653 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1654 return f"{int(this, 16)}" 1655 1656 if not is_integer_type: 1657 # Read dialect treats the hex value as BINARY/BLOB 1658 if binary_function_repr: 1659 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1660 return self.func(binary_function_repr, exp.Literal.string(this)) 1661 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1662 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1663 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1664 1665 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1666 1667 def bytestring_sql(self, expression: exp.ByteString) -> str: 1668 this = self.sql(expression, "this") 1669 is_bytes = bool(expression.args.get("is_bytes")) 1670 if is_bytes and self.BYTE_STRING_FUNCTION: 1671 # Hex digits can be quoted directly without escaping. 1672 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1673 1674 if self.dialect.BYTE_START: 1675 escaped_byte_string = self.escape_str( 1676 this, 1677 escape_backslash=bool( 1678 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1679 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1680 ), 1681 delimiter=self.dialect.BYTE_END, 1682 escaped_delimiter=self._escaped_byte_quote_end, 1683 is_byte_string=True, 1684 is_bytes=is_bytes, 1685 ) 1686 delimited_byte_string = ( 1687 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1688 ) 1689 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1690 return self.sql( 1691 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1692 ) 1693 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1694 return self.sql( 1695 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1696 ) 1697 1698 return delimited_byte_string 1699 1700 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1701 return self.sql(exp.Literal.string(this)) 1702 1703 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1704 return "" 1705 1706 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1707 this = self.sql(expression, "this") 1708 escape = expression.args.get("escape") 1709 unicode_start = self.dialect.UNICODE_START 1710 1711 if unicode_start: 1712 escape_substitute = r"\\\1" 1713 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1714 else: 1715 escape_substitute = r"\\u\1" 1716 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1717 1718 if escape: 1719 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1720 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1721 else: 1722 escape_pattern = ESCAPED_UNICODE_RE 1723 escape_sql = "" 1724 1725 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1726 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1727 1728 if unicode_start: 1729 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1730 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1731 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1732 else: 1733 this = self.escape_str(this, escape_backslash=False) 1734 1735 return f"{left_quote}{this}{right_quote}{escape_sql}" 1736 1737 def rawstring_sql(self, expression: exp.RawString) -> str: 1738 string = expression.this 1739 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1740 string = string.replace("\\", "\\\\") 1741 1742 string = self.escape_str(string, escape_backslash=False) 1743 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1744 1745 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1746 this = self.sql(expression, "this") 1747 specifier = self.sql(expression, "expression") 1748 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1749 return f"{this}{specifier}" 1750 1751 def datatype_param_bound_limiter( 1752 self, 1753 expression: exp.DataType, 1754 type_value: exp.DType, 1755 defaults: tuple[int, ...], 1756 bounds: tuple[int | None, ...], 1757 ) -> exp.DataType: 1758 params = expression.expressions 1759 1760 if not params: 1761 if defaults: 1762 expression.set( 1763 "expressions", 1764 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1765 ) 1766 return expression 1767 1768 if not bounds: 1769 return expression 1770 1771 for i, param in enumerate(params): 1772 bound = bounds[i] if i < len(bounds) else None 1773 if bound is None: 1774 continue 1775 1776 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1777 value = ( 1778 param_value.to_py() 1779 if isinstance(param_value, exp.Literal) and param_value.is_number 1780 else None 1781 ) 1782 if isinstance(value, (int, Decimal)) and value > bound: 1783 self.unsupported( 1784 f"{type_value.value} parameter {param_value.name} exceeds " 1785 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1786 ) 1787 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1788 1789 return expression 1790 1791 def datatype_sql(self, expression: exp.DataType) -> str: 1792 nested = "" 1793 values = "" 1794 1795 expr_nested = expression.args.get("nested") 1796 type_value = expression.this 1797 1798 if ( 1799 not expr_nested 1800 and isinstance(type_value, exp.DType) 1801 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1802 ): 1803 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1804 1805 interior = ( 1806 self.expressions( 1807 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1808 ) 1809 if expr_nested and self.pretty 1810 else self.expressions(expression, flat=True) 1811 ) 1812 1813 if type_value in self.UNSUPPORTED_TYPES: 1814 self.unsupported( 1815 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1816 ) 1817 1818 type_sql: t.Any = "" 1819 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1820 type_sql = self.sql(expression, "kind") 1821 elif type_value == exp.DType.CHARACTER_SET: 1822 char = f"CHAR({interior})" if interior else "CHAR" 1823 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1824 else: 1825 type_sql = ( 1826 self.TYPE_MAPPING.get(type_value, type_value.value) 1827 if isinstance(type_value, exp.DType) 1828 else type_value 1829 ) 1830 1831 if interior: 1832 if expr_nested: 1833 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1834 if expression.args.get("values") is not None: 1835 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1836 values = self.expressions(expression, key="values", flat=True) 1837 values = f"{delimiters[0]}{values}{delimiters[1]}" 1838 elif type_value == exp.DType.INTERVAL: 1839 nested = f" {interior}" 1840 else: 1841 nested = f"({interior})" 1842 1843 type_sql = f"{type_sql}{nested}{values}" 1844 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1845 exp.DType.TIMETZ, 1846 exp.DType.TIMESTAMPTZ, 1847 ): 1848 type_sql = f"{type_sql} WITH TIME ZONE" 1849 1850 collate = self.sql(expression, "collate") 1851 if collate: 1852 type_sql = f"{type_sql} COLLATE {collate}" 1853 1854 return type_sql 1855 1856 def directory_sql(self, expression: exp.Directory) -> str: 1857 local = "LOCAL " if expression.args.get("local") else "" 1858 row_format = self.sql(expression, "row_format") 1859 row_format = f" {row_format}" if row_format else "" 1860 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1861 1862 def delete_sql(self, expression: exp.Delete) -> str: 1863 hint = self.sql(expression, "hint") 1864 this = self.sql(expression, "this") 1865 this = f" FROM {this}" if this else "" 1866 using = self.expressions(expression, key="using") 1867 using = f" USING {using}" if using else "" 1868 cluster = self.sql(expression, "cluster") 1869 cluster = f" {cluster}" if cluster else "" 1870 where = self.sql(expression, "where") 1871 returning = self.sql(expression, "returning") 1872 order = self.sql(expression, "order") 1873 limit = self.sql(expression, "limit") 1874 tables = self.expressions(expression, key="tables") 1875 tables = f" {tables}" if tables else "" 1876 if self.RETURNING_END: 1877 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1878 else: 1879 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1880 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1881 1882 def drop_sql(self, expression: exp.Drop) -> str: 1883 tables = self.expressions(expression, key="tables", flat=True) 1884 expressions = self.expressions(expression, flat=True) 1885 expressions = f" ({expressions})" if expressions else "" 1886 kind = expression.args["kind"] 1887 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1888 iceberg = ( 1889 " ICEBERG" 1890 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1891 else "" 1892 ) 1893 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1894 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1895 on_cluster = self.sql(expression, "cluster") 1896 on_cluster = f" {on_cluster}" if on_cluster else "" 1897 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1898 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1899 cascade = " CASCADE" if expression.args.get("cascade") else "" 1900 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1901 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1902 purge = " PURGE" if expression.args.get("purge") else "" 1903 sync = " SYNC" if expression.args.get("sync") else "" 1904 force = " FORCE" if expression.args.get("force") else "" 1905 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1906 1907 def set_operation(self, expression: exp.SetOperation) -> str: 1908 op_type = type(expression) 1909 op_name = op_type.key.upper() 1910 1911 distinct = expression.args.get("distinct") 1912 if ( 1913 distinct is False 1914 and op_type in (exp.Except, exp.Intersect) 1915 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1916 ): 1917 self.unsupported(f"{op_name} ALL is not supported") 1918 1919 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1920 1921 if distinct is None: 1922 distinct = default_distinct 1923 if distinct is None: 1924 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1925 1926 if distinct is default_distinct: 1927 distinct_or_all = "" 1928 else: 1929 distinct_or_all = " DISTINCT" if distinct else " ALL" 1930 1931 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1932 side_kind = f"{side_kind} " if side_kind else "" 1933 1934 by_name = " BY NAME" if expression.args.get("by_name") else "" 1935 on = self.expressions(expression, key="on", flat=True) 1936 on = f" ON ({on})" if on else "" 1937 1938 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1939 1940 def set_operations(self, expression: exp.SetOperation) -> str: 1941 if not self.SET_OP_MODIFIERS: 1942 limit = expression.args.get("limit") 1943 order = expression.args.get("order") 1944 offset = expression.args.get("offset") 1945 1946 if limit or order or offset: 1947 select = self._move_ctes_to_top_level( 1948 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1949 ) 1950 1951 for arg in ("limit", "order", "offset"): 1952 if value := expression.args.get(arg): 1953 select.set(arg, value.pop()) 1954 return self.sql(select) 1955 1956 sqls: list[str] = [] 1957 stack: list[str | exp.Expr] = [expression] 1958 1959 while stack: 1960 node = stack.pop() 1961 1962 if isinstance(node, exp.SetOperation): 1963 stack.append(node.expression) 1964 stack.append( 1965 self.maybe_comment( 1966 self.set_operation(node), comments=node.comments, separated=True 1967 ) 1968 ) 1969 stack.append(node.this) 1970 else: 1971 if ( 1972 not self.SET_OP_LIMITS 1973 and isinstance(node, exp.Select) 1974 and node.args.get("limit") 1975 ): 1976 node = node.subquery(copy=False) 1977 if not self.SET_OP_PARENTHESIZED_OPERANDS: 1978 node = exp.select("*").from_(node, copy=False) 1979 sqls.append(self.sql(node)) 1980 1981 this = self.sep().join(sqls) 1982 this = self.query_modifiers(expression, this) 1983 return self.prepend_ctes(expression, this) 1984 1985 def fetch_sql(self, expression: exp.Fetch) -> str: 1986 direction = expression.args.get("direction") 1987 direction = f" {direction}" if direction else "" 1988 count = self.sql(expression, "count") 1989 count = f" {count}" if count else "" 1990 limit_options = self.sql(expression, "limit_options") 1991 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1992 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1993 1994 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1995 percent = " PERCENT" if expression.args.get("percent") else "" 1996 rows = " ROWS" if expression.args.get("rows") else "" 1997 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1998 if not with_ties and rows: 1999 with_ties = " ONLY" 2000 return f"{percent}{rows}{with_ties}" 2001 2002 def filter_sql(self, expression: exp.Filter) -> str: 2003 this = self.sql(expression, "this") 2004 where = self.sql(expression, "expression").strip() 2005 return f"{this} FILTER({where})" 2006 2007 def hint_sql(self, expression: exp.Hint) -> str: 2008 if not self.QUERY_HINTS: 2009 self.unsupported("Hints are not supported") 2010 return "" 2011 2012 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 2013 2014 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2015 using = self.sql(expression, "using") 2016 using = f" USING {using}" if using else "" 2017 columns = self.expressions(expression, key="columns", flat=True) 2018 columns = f"({columns})" if columns else "" 2019 partition_by = self.expressions(expression, key="partition_by", flat=True) 2020 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2021 where = self.sql(expression, "where") 2022 include = self.expressions(expression, key="include", flat=True) 2023 if include: 2024 include = f" INCLUDE ({include})" 2025 with_storage = self.expressions(expression, key="with_storage", flat=True) 2026 with_storage = f" WITH ({with_storage})" if with_storage else "" 2027 tablespace = self.sql(expression, "tablespace") 2028 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 2029 on = self.sql(expression, "on") 2030 on = f" ON {on}" if on else "" 2031 2032 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 2033 2034 def index_sql(self, expression: exp.Index) -> str: 2035 unique = "UNIQUE " if expression.args.get("unique") else "" 2036 primary = "PRIMARY " if expression.args.get("primary") else "" 2037 amp = "AMP " if expression.args.get("amp") else "" 2038 name = self.sql(expression, "this") 2039 name = f"{name} " if name else "" 2040 table = self.sql(expression, "table") 2041 table = f"{self.INDEX_ON} {table}" if table else "" 2042 2043 index = "INDEX " if not table else "" 2044 2045 params = self.sql(expression, "params") 2046 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 2047 2048 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2049 this = expression.this 2050 if this and this.is_string: 2051 resolved = maybe_parse(this.name).sql(self.dialect) 2052 if "expressions" in expression.args: 2053 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2054 # We can't safely emit the call to other dialects since name/arg semantics may differ 2055 self.unsupported( 2056 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2057 ) 2058 return resolved 2059 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2060 return self.func("IDENTIFIER", this) 2061 2062 def identifier_sql(self, expression: exp.Identifier) -> str: 2063 text = expression.name 2064 lower = text.lower() 2065 quoted = expression.quoted 2066 text = lower if self.normalize and not quoted else text 2067 if self._identifier_escaped_sequences: 2068 for char, escaped in self._identifier_escaped_sequences.items(): 2069 text = text.replace(char, escaped) 2070 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2071 if ( 2072 quoted 2073 or self.dialect.can_quote(expression, self.identify) 2074 or lower in self.RESERVED_KEYWORDS 2075 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2076 ): 2077 text = ( 2078 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2079 ) 2080 return text 2081 2082 def hex_sql(self, expression: exp.Hex) -> str: 2083 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2084 if self.dialect.HEX_LOWERCASE: 2085 text = self.func("LOWER", text) 2086 2087 return text 2088 2089 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2090 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2091 if not self.dialect.HEX_LOWERCASE: 2092 text = self.func("LOWER", text) 2093 return text 2094 2095 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2096 input_format = self.sql(expression, "input_format") 2097 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2098 output_format = self.sql(expression, "output_format") 2099 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2100 return self.sep().join((input_format, output_format)) 2101 2102 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2103 string = self.sql(exp.Literal.string(expression.name)) 2104 return f"{prefix}{string}" 2105 2106 def partition_sql(self, expression: exp.Partition) -> str: 2107 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2108 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2109 2110 def properties_sql(self, expression: exp.Properties) -> str: 2111 root_properties = [] 2112 with_properties = [] 2113 2114 for p in expression.expressions: 2115 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2116 if p_loc == exp.Properties.Location.POST_WITH: 2117 with_properties.append(p) 2118 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2119 root_properties.append(p) 2120 2121 root_props_ast = exp.Properties(expressions=root_properties) 2122 root_props_ast.parent = expression.parent 2123 2124 with_props_ast = exp.Properties(expressions=with_properties) 2125 with_props_ast.parent = expression.parent 2126 2127 root_props = self.root_properties(root_props_ast) 2128 with_props = self.with_properties(with_props_ast) 2129 2130 if root_props and with_props and not self.pretty: 2131 with_props = " " + with_props 2132 2133 return root_props + with_props 2134 2135 def root_properties(self, properties: exp.Properties) -> str: 2136 if properties.expressions: 2137 return self.expressions(properties, indent=False, sep=" ") 2138 return "" 2139 2140 def properties( 2141 self, 2142 properties: exp.Properties, 2143 prefix: str = "", 2144 sep: str = ", ", 2145 suffix: str = "", 2146 wrapped: bool = True, 2147 ) -> str: 2148 if properties.expressions: 2149 expressions = self.expressions(properties, sep=sep, indent=False) 2150 if expressions: 2151 expressions = self.wrap(expressions) if wrapped else expressions 2152 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2153 return "" 2154 2155 def with_properties(self, properties: exp.Properties) -> str: 2156 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2157 2158 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2159 properties_locs = defaultdict(list) 2160 for p in properties.expressions: 2161 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2162 if p_loc != exp.Properties.Location.UNSUPPORTED: 2163 properties_locs[p_loc].append(p) 2164 else: 2165 self.unsupported(f"Unsupported property {p.key}") 2166 2167 return properties_locs 2168 2169 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2170 if isinstance(expression.this, exp.Dot): 2171 return self.sql(expression, "this") 2172 return f"'{expression.name}'" if string_key else expression.name 2173 2174 def property_sql(self, expression: exp.Property) -> str: 2175 property_cls = expression.__class__ 2176 if property_cls == exp.Property: 2177 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2178 2179 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2180 if not property_name: 2181 self.unsupported(f"Unsupported property {expression.key}") 2182 2183 return f"{property_name}={self.sql(expression, 'this')}" 2184 2185 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2186 return f"UUID {self.sql(expression, 'this')}" 2187 2188 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2189 if self.SUPPORTS_CREATE_TABLE_LIKE: 2190 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2191 options = f" {options}" if options else "" 2192 2193 like = f"LIKE {self.sql(expression, 'this')}{options}" 2194 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2195 like = f"({like})" 2196 2197 return like 2198 2199 if expression.expressions: 2200 self.unsupported("Transpilation of LIKE property options is unsupported") 2201 2202 select = exp.select("*").from_(expression.this).limit(0) 2203 return f"AS {self.sql(select)}" 2204 2205 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2206 no = "NO " if expression.args.get("no") else "" 2207 protection = " PROTECTION" if expression.args.get("protection") else "" 2208 return f"{no}FALLBACK{protection}" 2209 2210 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2211 no = "NO " if expression.args.get("no") else "" 2212 local = expression.args.get("local") 2213 local = f"{local} " if local else "" 2214 dual = "DUAL " if expression.args.get("dual") else "" 2215 before = "BEFORE " if expression.args.get("before") else "" 2216 after = "AFTER " if expression.args.get("after") else "" 2217 return f"{no}{local}{dual}{before}{after}JOURNAL" 2218 2219 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2220 freespace = self.sql(expression, "this") 2221 percent = " PERCENT" if expression.args.get("percent") else "" 2222 return f"FREESPACE={freespace}{percent}" 2223 2224 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2225 if expression.args.get("default"): 2226 property = "DEFAULT" 2227 elif expression.args.get("on"): 2228 property = "ON" 2229 else: 2230 property = "OFF" 2231 return f"CHECKSUM={property}" 2232 2233 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2234 if expression.args.get("no"): 2235 return "NO MERGEBLOCKRATIO" 2236 if expression.args.get("default"): 2237 return "DEFAULT MERGEBLOCKRATIO" 2238 2239 percent = " PERCENT" if expression.args.get("percent") else "" 2240 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2241 2242 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2243 expressions = self.expressions(expression, flat=True) 2244 expressions = f"({expressions})" if expressions else "" 2245 return f"USING {self.sql(expression, 'this')}{expressions}" 2246 2247 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2248 default = expression.args.get("default") 2249 minimum = expression.args.get("minimum") 2250 maximum = expression.args.get("maximum") 2251 if default or minimum or maximum: 2252 if default: 2253 prop = "DEFAULT" 2254 elif minimum: 2255 prop = "MINIMUM" 2256 else: 2257 prop = "MAXIMUM" 2258 return f"{prop} DATABLOCKSIZE" 2259 units = expression.args.get("units") 2260 units = f" {units}" if units else "" 2261 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2262 2263 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2264 autotemp = expression.args.get("autotemp") 2265 always = expression.args.get("always") 2266 default = expression.args.get("default") 2267 manual = expression.args.get("manual") 2268 never = expression.args.get("never") 2269 2270 if autotemp is not None: 2271 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2272 elif always: 2273 prop = "ALWAYS" 2274 elif default: 2275 prop = "DEFAULT" 2276 elif manual: 2277 prop = "MANUAL" 2278 elif never: 2279 prop = "NEVER" 2280 return f"BLOCKCOMPRESSION={prop}" 2281 2282 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2283 no = expression.args.get("no") 2284 no = " NO" if no else "" 2285 concurrent = expression.args.get("concurrent") 2286 concurrent = " CONCURRENT" if concurrent else "" 2287 target = self.sql(expression, "target") 2288 target = f" {target}" if target else "" 2289 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2290 2291 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2292 if isinstance(expression.this, list): 2293 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2294 if expression.this: 2295 modulus = self.sql(expression, "this") 2296 remainder = self.sql(expression, "expression") 2297 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2298 2299 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2300 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2301 return f"FROM ({from_expressions}) TO ({to_expressions})" 2302 2303 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2304 this = self.sql(expression, "this") 2305 2306 for_values_or_default = expression.expression 2307 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2308 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2309 else: 2310 for_values_or_default = " DEFAULT" 2311 2312 return f"PARTITION OF {this}{for_values_or_default}" 2313 2314 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2315 kind = expression.args.get("kind") 2316 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2317 for_or_in = expression.args.get("for_or_in") 2318 for_or_in = f" {for_or_in}" if for_or_in else "" 2319 lock_type = expression.args.get("lock_type") 2320 override = " OVERRIDE" if expression.args.get("override") else "" 2321 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2322 2323 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2324 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2325 statistics = expression.args.get("statistics") 2326 statistics_sql = "" 2327 if statistics is not None: 2328 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2329 return f"{data_sql}{statistics_sql}" 2330 2331 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2332 this = self.sql(expression, "this") 2333 this = f"HISTORY_TABLE={this}" if this else "" 2334 data_consistency: str | None = self.sql(expression, "data_consistency") 2335 data_consistency = ( 2336 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2337 ) 2338 retention_period: str | None = self.sql(expression, "retention_period") 2339 retention_period = ( 2340 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2341 ) 2342 2343 if this: 2344 on_sql = self.func("ON", this, data_consistency, retention_period) 2345 else: 2346 on_sql = "ON" if expression.args.get("on") else "OFF" 2347 2348 sql = f"SYSTEM_VERSIONING={on_sql}" 2349 2350 return f"WITH({sql})" if expression.args.get("with_") else sql 2351 2352 def insert_sql(self, expression: exp.Insert) -> str: 2353 hint = self.sql(expression, "hint") 2354 overwrite = expression.args.get("overwrite") 2355 2356 if isinstance(expression.this, exp.Directory): 2357 this = " OVERWRITE" if overwrite else " INTO" 2358 else: 2359 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2360 2361 stored = self.sql(expression, "stored") 2362 stored = f" {stored}" if stored else "" 2363 alternative = expression.args.get("alternative") 2364 alternative = f" OR {alternative}" if alternative else "" 2365 ignore = " IGNORE" if expression.args.get("ignore") else "" 2366 is_function = expression.args.get("is_function") 2367 if is_function: 2368 this = f"{this} FUNCTION" 2369 this = f"{this} {self.sql(expression, 'this')}" 2370 2371 exists = " IF EXISTS" if expression.args.get("exists") else "" 2372 where = self.sql(expression, "where") 2373 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2374 using = self.expressions(expression, key="using", flat=True) 2375 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2376 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2377 on_conflict = self.sql(expression, "conflict") 2378 on_conflict = f" {on_conflict}" if on_conflict else "" 2379 by_name = " BY NAME" if expression.args.get("by_name") else "" 2380 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2381 returning = self.sql(expression, "returning") 2382 2383 if self.RETURNING_END: 2384 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2385 else: 2386 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2387 2388 partition_by = self.sql(expression, "partition") 2389 partition_by = f" {partition_by}" if partition_by else "" 2390 settings = self.sql(expression, "settings") 2391 settings = f" {settings}" if settings else "" 2392 2393 source = self.sql(expression, "source") 2394 source = f"TABLE {source}" if source else "" 2395 2396 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2397 return self.prepend_ctes(expression, sql) 2398 2399 def introducer_sql(self, expression: exp.Introducer) -> str: 2400 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2401 2402 def kill_sql(self, expression: exp.Kill) -> str: 2403 kind = self.sql(expression, "kind") 2404 kind = f" {kind}" if kind else "" 2405 this = self.sql(expression, "this") 2406 this = f" {this}" if this else "" 2407 return f"KILL{kind}{this}" 2408 2409 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2410 return expression.name 2411 2412 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2413 return expression.name 2414 2415 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2416 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2417 2418 constraint = self.sql(expression, "constraint") 2419 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2420 2421 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2422 if conflict_keys: 2423 conflict_keys = f"({conflict_keys})" 2424 2425 index_predicate = self.sql(expression, "index_predicate") 2426 conflict_keys = f"{conflict_keys}{index_predicate} " 2427 2428 action = self.sql(expression, "action") 2429 2430 expressions = self.expressions(expression, flat=True) 2431 if expressions: 2432 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2433 expressions = f" {set_keyword}{expressions}" 2434 2435 where = self.sql(expression, "where") 2436 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2437 2438 def returning_sql(self, expression: exp.Returning) -> str: 2439 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2440 2441 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2442 fields = self.sql(expression, "fields") 2443 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2444 escaped = self.sql(expression, "escaped") 2445 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2446 items = self.sql(expression, "collection_items") 2447 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2448 keys = self.sql(expression, "map_keys") 2449 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2450 lines = self.sql(expression, "lines") 2451 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2452 null = self.sql(expression, "null") 2453 null = f" NULL DEFINED AS {null}" if null else "" 2454 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2455 2456 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2457 return f"WITH ({self.expressions(expression, flat=True)})" 2458 2459 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2460 this = f"{self.sql(expression, 'this')} INDEX" 2461 target = self.sql(expression, "target") 2462 target = f" FOR {target}" if target else "" 2463 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2464 2465 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2466 this = self.sql(expression, "this") 2467 kind = self.sql(expression, "kind") 2468 expr = self.sql(expression, "expression") 2469 return f"{this} ({kind} => {expr})" 2470 2471 def table_parts(self, expression: exp.Table) -> str: 2472 return ".".join( 2473 self.sql(part) 2474 for part in ( 2475 expression.args.get("catalog"), 2476 expression.args.get("db"), 2477 expression.args.get("this"), 2478 ) 2479 if part is not None 2480 ) 2481 2482 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2483 table = self.table_parts(expression) 2484 only = "ONLY " if expression.args.get("only") else "" 2485 partition = self.sql(expression, "partition") 2486 partition = f" {partition}" if partition else "" 2487 version = self.sql(expression, "version") 2488 version = f" {version}" if version else "" 2489 alias = self.sql(expression, "alias") 2490 alias = f"{sep}{alias}" if alias else "" 2491 2492 sample = self.sql(expression, "sample") 2493 post_alias = "" 2494 pre_alias = "" 2495 2496 if self.dialect.ALIAS_POST_TABLESAMPLE: 2497 pre_alias = sample 2498 else: 2499 post_alias = sample 2500 2501 if self.dialect.ALIAS_POST_VERSION: 2502 pre_alias = f"{pre_alias}{version}" 2503 else: 2504 post_alias = f"{post_alias}{version}" 2505 2506 hints = self.expressions(expression, key="hints", sep=" ") 2507 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2508 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2509 joins = self.indent( 2510 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2511 ) 2512 laterals = self.expressions(expression, key="laterals", sep="") 2513 2514 file_format = self.sql(expression, "format") 2515 pattern = self.sql(expression, "pattern") 2516 if file_format: 2517 pattern = f", PATTERN => {pattern}" if pattern else "" 2518 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2519 elif pattern: 2520 file_format = f" (PATTERN => {pattern})" 2521 2522 ordinality = expression.args.get("ordinality") or "" 2523 if ordinality: 2524 ordinality = f" WITH ORDINALITY{alias}" 2525 alias = "" 2526 2527 when = self.sql(expression, "when") 2528 if when: 2529 if self.HISTORICAL_DATA_POST_ALIAS: 2530 alias = f"{alias} {when}" 2531 else: 2532 table = f"{table} {when}" 2533 2534 changes = self.sql(expression, "changes") 2535 changes = f" {changes}" if changes else "" 2536 2537 rows_from = self.expressions(expression, key="rows_from") 2538 if rows_from: 2539 table = f"ROWS FROM {self.wrap(rows_from)}" 2540 2541 indexed = expression.args.get("indexed") 2542 if indexed is not None: 2543 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2544 else: 2545 indexed = "" 2546 2547 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2548 2549 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2550 table = self.func("TABLE", expression.this) 2551 alias = self.sql(expression, "alias") 2552 alias = f" AS {alias}" if alias else "" 2553 sample = self.sql(expression, "sample") 2554 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2555 joins = self.indent( 2556 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2557 ) 2558 return f"{table}{alias}{pivots}{sample}{joins}" 2559 2560 def tablesample_sql( 2561 self, 2562 expression: exp.TableSample, 2563 tablesample_keyword: str | None = None, 2564 ) -> str: 2565 method = self.sql(expression, "method") 2566 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2567 numerator = self.sql(expression, "bucket_numerator") 2568 denominator = self.sql(expression, "bucket_denominator") 2569 field = self.sql(expression, "bucket_field") 2570 field = f" ON {field}" if field else "" 2571 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2572 seed = self.sql(expression, "seed") 2573 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2574 2575 size = self.sql(expression, "size") 2576 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2577 size = f"{size} ROWS" 2578 2579 percent = self.sql(expression, "percent") 2580 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2581 percent = f"{percent} PERCENT" 2582 2583 expr = f"{bucket}{percent}{size}" 2584 if self.TABLESAMPLE_REQUIRES_PARENS: 2585 expr = f"({expr})" 2586 2587 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2588 2589 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2590 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2591 # the stored column name differs from the target dialect's natural output. 2592 columns = expression.args.get("columns") 2593 if not columns or len(expression.fields) != 1: 2594 return None 2595 2596 args = expression.args 2597 parser_cls = self.dialect.parser_class 2598 2599 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2600 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2601 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2602 2603 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2604 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2605 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2606 2607 if ( 2608 src_identify_pivot_strings == tgt_identify_pivot_strings 2609 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2610 and src_pivot_column_naming == tgt_pivot_column_naming 2611 ): 2612 return None 2613 2614 in_exprs = expression.fields[0].expressions 2615 step = len(columns) // len(in_exprs) 2616 2617 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2618 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2619 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2620 first_stored = columns[0].name 2621 2622 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2623 if not first_stored.startswith(first_base): 2624 return None 2625 2626 suffix = first_stored[len(first_base) :] 2627 2628 # Whether the target dialect would append an agg-name suffix for this pivot. 2629 # Spark single-agg uniquely drops the agg alias entirely. 2630 target_has_suffix = ( 2631 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2632 ) and any(a.alias for a in expression.expressions) 2633 source_has_suffix = suffix != "" 2634 2635 new_exprs: list[exp.Expression] = [] 2636 modified = False 2637 for val_idx, e in enumerate(in_exprs): 2638 if isinstance(e, exp.PivotAlias): 2639 new_exprs.append(e) 2640 continue 2641 2642 i = val_idx * step 2643 stored_full = columns[i].name 2644 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2645 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2646 2647 # Source had a suffix, but target won't apply one 2648 if source_has_suffix and not target_has_suffix: 2649 new_exprs.append( 2650 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2651 ) 2652 modified = True 2653 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2654 elif stored_value != target_value: 2655 new_exprs.append( 2656 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2657 ) 2658 modified = True 2659 else: 2660 new_exprs.append(e) 2661 2662 return new_exprs if modified else None 2663 2664 def pivot_sql(self, expression: exp.Pivot) -> str: 2665 expressions = self.expressions(expression, flat=True) 2666 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2667 2668 group = self.sql(expression, "group") 2669 2670 if expression.this: 2671 this = self.sql(expression, "this") 2672 if not expressions: 2673 sql = f"UNPIVOT {this}" 2674 else: 2675 on = f"{self.seg('ON')} {expressions}" 2676 into = self.sql(expression, "into") 2677 into = f"{self.seg('INTO')} {into}" if into else "" 2678 using = self.expressions(expression, key="using", flat=True) 2679 using = f"{self.seg('USING')} {using}" if using else "" 2680 sql = f"{direction} {this}{on}{into}{using}{group}" 2681 return self.prepend_ctes(expression, sql) 2682 2683 if not expression.unpivot: 2684 # Wrap IN-list values with explicit aliases where the target dialect would differ 2685 new_field_exprs = self._pivot_in_value_aliases(expression) 2686 if new_field_exprs is not None: 2687 expression.fields[0].set("expressions", new_field_exprs) 2688 2689 alias = self.sql(expression, "alias") 2690 if alias: 2691 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2692 2693 fields = self.expressions( 2694 expression, 2695 "fields", 2696 sep=" ", 2697 dynamic=True, 2698 new_line=True, 2699 skip_first=True, 2700 skip_last=True, 2701 ) 2702 2703 include_nulls = expression.args.get("include_nulls") 2704 if include_nulls is not None: 2705 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2706 else: 2707 nulls = "" 2708 2709 default_on_null = self.sql(expression, "default_on_null") 2710 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2711 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2712 return self.prepend_ctes(expression, sql) 2713 2714 def version_sql(self, expression: exp.Version) -> str: 2715 this = f"FOR {expression.name}" 2716 kind = expression.text("kind") 2717 expr = self.sql(expression, "expression") 2718 return f"{this} {kind} {expr}" 2719 2720 def tuple_sql(self, expression: exp.Tuple) -> str: 2721 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2722 2723 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2724 """ 2725 Returns (join_sql, from_sql) for UPDATE statements. 2726 - join_sql: placed after UPDATE table, before SET 2727 - from_sql: placed after SET clause (standard position) 2728 Dialects like MySQL need to convert FROM to JOIN syntax. 2729 """ 2730 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2731 return ("", self.sql(expression, "from_")) 2732 2733 # Qualify unqualified columns in SET clause with the target table 2734 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2735 target_table = expression.this 2736 if isinstance(target_table, exp.Table): 2737 target_name = exp.to_identifier(target_table.alias_or_name) 2738 for eq in expression.expressions: 2739 col = eq.this 2740 if isinstance(col, exp.Column) and not col.table: 2741 col.set("table", target_name) 2742 2743 table = from_expr.this 2744 if nested_joins := table.args.get("joins", []): 2745 table.set("joins", None) 2746 2747 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2748 for nested in nested_joins: 2749 if not nested.args.get("on") and not nested.args.get("using"): 2750 nested.set("on", exp.true()) 2751 join_sql += self.sql(nested) 2752 2753 return (join_sql, "") 2754 2755 def update_sql(self, expression: exp.Update) -> str: 2756 hint = self.sql(expression, "hint") 2757 this = self.sql(expression, "this") 2758 join_sql, from_sql = self._update_from_joins_sql(expression) 2759 set_sql = self.expressions(expression, flat=True) 2760 where_sql = self.sql(expression, "where") 2761 returning = self.sql(expression, "returning") 2762 order = self.sql(expression, "order") 2763 limit = self.sql(expression, "limit") 2764 if self.RETURNING_END: 2765 expression_sql = f"{from_sql}{where_sql}{returning}" 2766 else: 2767 expression_sql = f"{returning}{from_sql}{where_sql}" 2768 options = self.expressions(expression, key="options") 2769 options = f" OPTION({options})" if options else "" 2770 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2771 return self.prepend_ctes(expression, sql) 2772 2773 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2774 values_as_table = values_as_table and self.VALUES_AS_TABLE 2775 2776 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2777 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2778 args = self.expressions(expression) 2779 alias = self.sql(expression, "alias") 2780 values = f"VALUES{self.seg('')}{args}" 2781 values = ( 2782 f"({values})" 2783 if self.WRAP_DERIVED_VALUES 2784 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2785 else values 2786 ) 2787 values = self.query_modifiers(expression, values) 2788 return f"{values} AS {alias}" if alias else values 2789 2790 # Converts `VALUES...` expression into a series of select unions. 2791 alias_node = expression.args.get("alias") 2792 column_names = alias_node and alias_node.columns 2793 2794 selects: list[exp.Query] = [] 2795 2796 for i, tup in enumerate(expression.expressions): 2797 row = tup.expressions 2798 2799 if i == 0 and column_names: 2800 row = [ 2801 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2802 ] 2803 2804 selects.append(exp.Select(expressions=row)) 2805 2806 if self.pretty: 2807 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2808 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2809 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2810 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2811 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2812 2813 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2814 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2815 return f"({unions}){alias}" 2816 2817 def var_sql(self, expression: exp.Var) -> str: 2818 return self.sql(expression, "this") 2819 2820 @unsupported_args("expressions") 2821 def into_sql(self, expression: exp.Into) -> str: 2822 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2823 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2824 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2825 2826 def from_sql(self, expression: exp.From) -> str: 2827 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2828 2829 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2830 grouping_sets = self.expressions(expression, indent=False) 2831 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2832 2833 def rollup_sql(self, expression: exp.Rollup) -> str: 2834 expressions = self.expressions(expression, indent=False) 2835 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2836 2837 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2838 this = self.sql(expression, "this") 2839 2840 columns = self.expressions(expression, flat=True) 2841 2842 from_sql = self.sql(expression, "from_index") 2843 from_sql = f" FROM {from_sql}" if from_sql else "" 2844 2845 properties = expression.args.get("properties") 2846 properties_sql = ( 2847 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2848 ) 2849 2850 return f"{this}({columns}){from_sql}{properties_sql}" 2851 2852 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2853 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2854 2855 def cube_sql(self, expression: exp.Cube) -> str: 2856 expressions = self.expressions(expression, indent=False) 2857 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2858 2859 def group_sql(self, expression: exp.Group) -> str: 2860 group_by_all = expression.args.get("all") 2861 if group_by_all is True: 2862 modifier = " ALL" 2863 elif group_by_all is False: 2864 modifier = " DISTINCT" 2865 else: 2866 modifier = "" 2867 2868 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2869 2870 grouping_sets = self.expressions(expression, key="grouping_sets") 2871 cube = self.expressions(expression, key="cube") 2872 rollup = self.expressions(expression, key="rollup") 2873 2874 groupings = csv( 2875 self.seg(grouping_sets) if grouping_sets else "", 2876 self.seg(cube) if cube else "", 2877 self.seg(rollup) if rollup else "", 2878 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2879 sep=self.GROUPINGS_SEP, 2880 ) 2881 2882 if ( 2883 expression.expressions 2884 and groupings 2885 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2886 ): 2887 add_separator = True 2888 2889 if grouping_sets: 2890 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2891 add_separator = False 2892 else: 2893 self.unsupported( 2894 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2895 ) 2896 2897 if add_separator: 2898 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2899 2900 return f"{group_by}{groupings}" 2901 2902 def having_sql(self, expression: exp.Having) -> str: 2903 this = self.indent(self.sql(expression, "this")) 2904 return f"{self.seg('HAVING')}{self.sep()}{this}" 2905 2906 def connect_sql(self, expression: exp.Connect) -> str: 2907 start = self.sql(expression, "start") 2908 start = self.seg(f"START WITH {start}") if start else "" 2909 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2910 connect = self.sql(expression, "connect") 2911 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2912 return start + connect 2913 2914 def prior_sql(self, expression: exp.Prior) -> str: 2915 return f"PRIOR {self.sql(expression, 'this')}" 2916 2917 def join_sql(self, expression: exp.Join) -> str: 2918 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2919 side = None 2920 else: 2921 side = expression.side 2922 2923 op_sql = " ".join( 2924 op 2925 for op in ( 2926 expression.method, 2927 "GLOBAL" if expression.args.get("global_") else None, 2928 side, 2929 expression.kind, 2930 expression.hint if self.JOIN_HINTS else None, 2931 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2932 ) 2933 if op 2934 ) 2935 match_cond = self.sql(expression, "match_condition") 2936 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2937 on_sql = self.sql(expression, "on") 2938 using = expression.args.get("using") 2939 2940 if not on_sql and using: 2941 on_sql = csv(*(self.sql(column) for column in using)) 2942 2943 this = expression.this 2944 this_sql = self.sql(this) 2945 2946 exprs = self.expressions(expression) 2947 if exprs: 2948 this_sql = f"{this_sql},{self.seg(exprs)}" 2949 2950 if on_sql: 2951 on_sql = self.indent(on_sql, skip_first=True) 2952 space = self.seg(" " * self.pad) if self.pretty else " " 2953 if using: 2954 on_sql = f"{space}USING ({on_sql})" 2955 else: 2956 on_sql = f"{space}ON {on_sql}" 2957 elif not op_sql: 2958 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2959 return f" {this_sql}" 2960 2961 return f", {this_sql}" 2962 2963 if op_sql != "STRAIGHT_JOIN": 2964 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2965 2966 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2967 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2968 2969 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2970 args = self.expressions(expression, flat=True) 2971 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2972 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2973 2974 def lateral_op(self, expression: exp.Lateral) -> str: 2975 cross_apply = expression.args.get("cross_apply") 2976 2977 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2978 if cross_apply is True: 2979 op = "INNER JOIN " 2980 elif cross_apply is False: 2981 op = "LEFT JOIN " 2982 else: 2983 op = "" 2984 2985 return f"{op}LATERAL" 2986 2987 def lateral_sql(self, expression: exp.Lateral) -> str: 2988 this = self.sql(expression, "this") 2989 2990 if expression.args.get("view"): 2991 alias = expression.args["alias"] 2992 columns = self.expressions(alias, key="columns", flat=True) 2993 table = f" {alias.name}" if alias.name else "" 2994 columns = f" AS {columns}" if columns else "" 2995 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2996 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2997 2998 table_alias = expression.args.get("alias") 2999 offset = expression.this.args.get("offset") 3000 3001 if ( 3002 self.UNNEST_WITH_ORDINALITY 3003 and table_alias 3004 and isinstance(expression.this, exp.Unnest) 3005 and isinstance(offset, exp.Identifier) 3006 ): 3007 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3008 table_alias = table_alias.copy() 3009 table_alias.append("columns", offset.copy()) 3010 3011 alias = self.sql(table_alias) 3012 alias = f" AS {alias}" if alias else "" 3013 3014 ordinality = expression.args.get("ordinality") or "" 3015 if ordinality: 3016 ordinality = f" WITH ORDINALITY{alias}" 3017 alias = "" 3018 3019 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 3020 3021 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3022 this = self.sql(expression, "this") 3023 3024 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3025 return this 3026 3027 args = [ 3028 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3029 for e in (expression.args.get(k) for k in ("offset", "expression")) 3030 if e 3031 ] 3032 3033 args_sql = ", ".join(self.sql(e) for e in args) 3034 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3035 expressions = self.expressions(expression, flat=True) 3036 limit_options = self.sql(expression, "limit_options") 3037 expressions = f" BY {expressions}" if expressions else "" 3038 3039 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 3040 3041 def offset_sql(self, expression: exp.Offset) -> str: 3042 this = self.sql(expression, "this") 3043 value = expression.expression 3044 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3045 expressions = self.expressions(expression, flat=True) 3046 expressions = f" BY {expressions}" if expressions else "" 3047 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 3048 3049 def setitem_sql(self, expression: exp.SetItem) -> str: 3050 kind = self.sql(expression, "kind") 3051 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3052 kind = "" 3053 else: 3054 kind = f"{kind} " if kind else "" 3055 this = self.sql(expression, "this") 3056 expressions = self.expressions(expression) 3057 collate = self.sql(expression, "collate") 3058 collate = f" COLLATE {collate}" if collate else "" 3059 global_ = "GLOBAL " if expression.args.get("global_") else "" 3060 return f"{global_}{kind}{this}{expressions}{collate}" 3061 3062 def set_sql(self, expression: exp.Set) -> str: 3063 expressions = f" {self.expressions(expression, flat=True)}" 3064 tag = " TAG" if expression.args.get("tag") else "" 3065 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3066 3067 def queryband_sql(self, expression: exp.QueryBand) -> str: 3068 this = self.sql(expression, "this") 3069 update = " UPDATE" if expression.args.get("update") else "" 3070 scope = self.sql(expression, "scope") 3071 scope = f" FOR {scope}" if scope else "" 3072 3073 return f"QUERY_BAND = {this}{update}{scope}" 3074 3075 def pragma_sql(self, expression: exp.Pragma) -> str: 3076 return f"PRAGMA {self.sql(expression, 'this')}" 3077 3078 def lock_sql(self, expression: exp.Lock) -> str: 3079 if not self.LOCKING_READS_SUPPORTED: 3080 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3081 return "" 3082 3083 update = expression.args["update"] 3084 key = expression.args.get("key") 3085 if update: 3086 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3087 else: 3088 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3089 expressions = self.expressions(expression, flat=True) 3090 expressions = f" OF {expressions}" if expressions else "" 3091 wait = expression.args.get("wait") 3092 3093 if wait is not None: 3094 if isinstance(wait, exp.Literal): 3095 wait = f" WAIT {self.sql(wait)}" 3096 else: 3097 wait = " NOWAIT" if wait else " SKIP LOCKED" 3098 3099 return f"{lock_type}{expressions}{wait or ''}" 3100 3101 def literal_sql(self, expression: exp.Literal) -> str: 3102 text = expression.this or "" 3103 if expression.is_string: 3104 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3105 return text 3106 3107 def escape_str( 3108 self, 3109 text: str, 3110 escape_backslash: bool = True, 3111 delimiter: str | None = None, 3112 escaped_delimiter: str | None = None, 3113 is_byte_string: bool = False, 3114 is_bytes: bool = False, 3115 ) -> str: 3116 if is_byte_string: 3117 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3118 else: 3119 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3120 3121 if supports_escape_sequences: 3122 text = "".join( 3123 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3124 for ch in text 3125 ) 3126 3127 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3128 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3129 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3130 3131 delimiter = delimiter or self.dialect.QUOTE_END 3132 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3133 3134 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3135 3136 def loaddata_sql(self, expression: exp.LoadData) -> str: 3137 is_overwrite = expression.args.get("overwrite") 3138 overwrite = " OVERWRITE" if is_overwrite else "" 3139 this = self.sql(expression, "this") 3140 3141 files = expression.args.get("files") 3142 if files: 3143 files_sql = self.expressions(files, flat=True) 3144 files_sql = f"FILES{self.wrap(files_sql)}" 3145 if is_overwrite: 3146 this = f" {this}" 3147 elif expression.args.get("temp"): 3148 this = f" INTO TEMP TABLE {this}" 3149 else: 3150 this = f" INTO TABLE {this}" 3151 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3152 3153 local = " LOCAL" if expression.args.get("local") else "" 3154 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3155 this = f" INTO TABLE {this}" 3156 partition = self.sql(expression, "partition") 3157 partition = f" {partition}" if partition else "" 3158 input_format = self.sql(expression, "input_format") 3159 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3160 serde = self.sql(expression, "serde") 3161 serde = f" SERDE {serde}" if serde else "" 3162 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3163 3164 def null_sql(self, *_) -> str: 3165 return "NULL" 3166 3167 def boolean_sql(self, expression: exp.Boolean) -> str: 3168 return "TRUE" if expression.this else "FALSE" 3169 3170 def booland_sql(self, expression: exp.Booland) -> str: 3171 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3172 3173 def boolor_sql(self, expression: exp.Boolor) -> str: 3174 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3175 3176 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3177 this = self.sql(expression, "this") 3178 this = f"{this} " if this else this 3179 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3180 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3181 3182 def withfill_sql(self, expression: exp.WithFill) -> str: 3183 from_sql = self.sql(expression, "from_") 3184 from_sql = f" FROM {from_sql}" if from_sql else "" 3185 to_sql = self.sql(expression, "to") 3186 to_sql = f" TO {to_sql}" if to_sql else "" 3187 step_sql = self.sql(expression, "step") 3188 step_sql = f" STEP {step_sql}" if step_sql else "" 3189 interpolated_values = [ 3190 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3191 if isinstance(e, exp.Alias) 3192 else self.sql(e, "this") 3193 for e in expression.args.get("interpolate") or [] 3194 ] 3195 interpolate = ( 3196 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3197 ) 3198 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3199 3200 def cluster_sql(self, expression: exp.Cluster) -> str: 3201 return self.op_expressions("CLUSTER BY", expression) 3202 3203 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3204 if expression.this: 3205 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3206 return "" 3207 expressions = self.expressions(expression, flat=True) 3208 return f"CLUSTER BY ({expressions})" 3209 3210 def distribute_sql(self, expression: exp.Distribute) -> str: 3211 return self.op_expressions("DISTRIBUTE BY", expression) 3212 3213 def sort_sql(self, expression: exp.Sort) -> str: 3214 return self.op_expressions("SORT BY", expression) 3215 3216 def _resolve_ordered_for_null_ordering_simulation( 3217 self, expression: exp.Ordered 3218 ) -> exp.Expr | None: 3219 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3220 3221 Returns the underlying expression of the uniquely-matching projection 3222 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3223 simulation, since the CASE is evaluated in FROM-clause scope rather 3224 than alias scope (MySQL error 1052). Returns None if no safe 3225 substitution applies, leaving the original behaviour unchanged. 3226 """ 3227 this = expression.this 3228 if not (isinstance(this, exp.Column) and not this.table): 3229 return None 3230 3231 # Only a query's own ORDER BY can reference its projection aliases 3232 order = expression.parent 3233 ancestor = order.parent if isinstance(order, exp.Order) else None 3234 if not isinstance(ancestor, exp.Select): 3235 return None 3236 3237 column_name = this.name 3238 matched: list[exp.Expr] = [ 3239 p.this if isinstance(p, exp.Alias) else p 3240 for p in ancestor.selects 3241 if p.output_name == column_name 3242 ] 3243 match = matched[0] if len(matched) == 1 else None 3244 3245 # Skip the substitution when it would be identical to the existing 3246 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3247 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3248 return None 3249 3250 return match 3251 3252 def ordered_sql(self, expression: exp.Ordered) -> str: 3253 desc = expression.args.get("desc") 3254 asc = not desc 3255 3256 nulls_first = expression.args.get("nulls_first") 3257 nulls_last = not nulls_first 3258 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3259 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3260 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3261 3262 this = self.sql(expression, "this") 3263 3264 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3265 nulls_sort_change = "" 3266 if nulls_first and ( 3267 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3268 ): 3269 nulls_sort_change = " NULLS FIRST" 3270 elif ( 3271 nulls_last 3272 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3273 and not nulls_are_last 3274 ): 3275 nulls_sort_change = " NULLS LAST" 3276 3277 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3278 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3279 window = expression.find_ancestor(exp.Window, exp.Select) 3280 3281 if isinstance(window, exp.Window): 3282 window_this = window.this 3283 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3284 window_this = window_this.this 3285 spec = window.args.get("spec") 3286 else: 3287 window_this = None 3288 spec = None 3289 3290 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3291 # without a spec or with a ROWS spec, but not with RANGE 3292 if not ( 3293 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3294 and (not spec or spec.text("kind").upper() == "ROWS") 3295 ): 3296 if window_this and spec: 3297 self.unsupported( 3298 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3299 ) 3300 nulls_sort_change = "" 3301 elif self.NULL_ORDERING_SUPPORTED is False and ( 3302 (asc and nulls_sort_change == " NULLS LAST") 3303 or (desc and nulls_sort_change == " NULLS FIRST") 3304 ): 3305 # BigQuery does not allow these ordering/nulls combinations when used under 3306 # an aggregation func or under a window containing one 3307 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3308 3309 if isinstance(ancestor, exp.Window): 3310 ancestor = ancestor.this 3311 if isinstance(ancestor, exp.AggFunc): 3312 self.unsupported( 3313 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3314 ) 3315 nulls_sort_change = "" 3316 elif self.NULL_ORDERING_SUPPORTED is None: 3317 if expression.this.is_int: 3318 self.unsupported( 3319 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3320 ) 3321 elif not isinstance(expression.this, exp.Rand): 3322 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3323 target = self.sql(resolved) if resolved is not None else this 3324 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3325 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3326 nulls_sort_change = "" 3327 3328 with_fill = self.sql(expression, "with_fill") 3329 with_fill = f" {with_fill}" if with_fill else "" 3330 3331 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3332 3333 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3334 window_frame = self.sql(expression, "window_frame") 3335 window_frame = f"{window_frame} " if window_frame else "" 3336 3337 this = self.sql(expression, "this") 3338 3339 return f"{window_frame}{this}" 3340 3341 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3342 partition = self.partition_by_sql(expression) 3343 order = self.sql(expression, "order") 3344 measures = self.expressions(expression, key="measures") 3345 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3346 rows = self.sql(expression, "rows") 3347 rows = self.seg(rows) if rows else "" 3348 after = self.sql(expression, "after") 3349 after = self.seg(after) if after else "" 3350 pattern = self.sql(expression, "pattern") 3351 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3352 definition_sqls = [ 3353 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3354 for definition in expression.args.get("define", []) 3355 ] 3356 definitions = self.expressions(sqls=definition_sqls) 3357 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3358 body = "".join( 3359 ( 3360 partition, 3361 order, 3362 measures, 3363 rows, 3364 after, 3365 pattern, 3366 define, 3367 ) 3368 ) 3369 alias = self.sql(expression, "alias") 3370 alias = f" {alias}" if alias else "" 3371 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3372 3373 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3374 limit = expression.args.get("limit") 3375 3376 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3377 count = limit.args.get("count") 3378 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3379 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3380 limit = exp.Limit( 3381 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3382 ) 3383 elif ( 3384 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3385 ): 3386 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3387 3388 return csv( 3389 *sqls, 3390 *[self.sql(join) for join in expression.args.get("joins") or []], 3391 self.sql(expression, "match"), 3392 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3393 self.sql(expression, "prewhere"), 3394 self.sql(expression, "where"), 3395 self.sql(expression, "connect"), 3396 self.sql(expression, "group"), 3397 self.sql(expression, "having"), 3398 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3399 self.sql(expression, "order"), 3400 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3401 *self.after_limit_modifiers(expression), 3402 self.sql(expression, "for_"), 3403 self.options_modifier(expression), 3404 sep="", 3405 ) 3406 3407 def options_modifier(self, expression: exp.Expr) -> str: 3408 options = self.expressions(expression, key="options") 3409 return f" {options}" if options else "" 3410 3411 def forclause_sql(self, expression: exp.ForClause) -> str: 3412 kind = expression.args["kind"] 3413 if kind == "BROWSE": 3414 return f"{self.sep()}FOR BROWSE" 3415 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3416 # the target dialect doesn't support QueryOption, so we drop the clause. 3417 options = self.expressions(expression, key="expressions") 3418 if not options: 3419 return "" 3420 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3421 3422 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3423 self.unsupported("Unsupported query option.") 3424 return "" 3425 3426 def offset_limit_modifiers( 3427 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3428 ) -> list[str]: 3429 return [ 3430 self.sql(expression, "offset") if fetch else self.sql(limit), 3431 self.sql(limit) if fetch else self.sql(expression, "offset"), 3432 ] 3433 3434 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3435 locks = self.expressions(expression, key="locks", sep=" ") 3436 locks = f" {locks}" if locks else "" 3437 return [locks, self.sql(expression, "sample")] 3438 3439 def select_sql(self, expression: exp.Select) -> str: 3440 into = expression.args.get("into") 3441 if not self.SUPPORTS_SELECT_INTO and into: 3442 into.pop() 3443 3444 hint = self.sql(expression, "hint") 3445 distinct = self.sql(expression, "distinct") 3446 distinct = f" {distinct}" if distinct else "" 3447 kind = self.sql(expression, "kind") 3448 3449 limit = expression.args.get("limit") 3450 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3451 top = self.limit_sql(limit, top=True) 3452 limit.pop() 3453 else: 3454 top = "" 3455 3456 expressions = self.expressions(expression) 3457 3458 if kind: 3459 if kind in self.SELECT_KINDS: 3460 kind = f" AS {kind}" 3461 else: 3462 if kind == "STRUCT": 3463 expressions = self.expressions( 3464 sqls=[ 3465 self.sql( 3466 exp.Struct( 3467 expressions=[ 3468 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3469 if isinstance(e, exp.Alias) 3470 else e 3471 for e in expression.expressions 3472 ] 3473 ) 3474 ) 3475 ] 3476 ) 3477 kind = "" 3478 3479 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3480 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3481 3482 exclude = expression.args.get("exclude") 3483 3484 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3485 exclude_sql = self.expressions(sqls=exclude, flat=True) 3486 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3487 3488 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3489 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3490 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3491 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3492 sql = self.query_modifiers( 3493 expression, 3494 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3495 self.sql(expression, "into", comment=False), 3496 self.sql(expression, "from_", comment=False), 3497 ) 3498 3499 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3500 if expression.args.get("with_"): 3501 sql = self.maybe_comment(sql, expression) 3502 expression.pop_comments() 3503 3504 sql = self.prepend_ctes(expression, sql) 3505 3506 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3507 expression.set("exclude", None) 3508 subquery = expression.subquery(copy=False) 3509 star = exp.Star(except_=exclude) 3510 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3511 3512 if not self.SUPPORTS_SELECT_INTO and into: 3513 if into.args.get("temporary"): 3514 table_kind = " TEMPORARY" 3515 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3516 table_kind = " UNLOGGED" 3517 else: 3518 table_kind = "" 3519 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3520 3521 return sql 3522 3523 def schema_sql(self, expression: exp.Schema) -> str: 3524 this = self.sql(expression, "this") 3525 sql = self.schema_columns_sql(expression) 3526 return f"{this} {sql}" if this and sql else this or sql 3527 3528 def schema_columns_sql(self, expression: exp.Expr) -> str: 3529 if expression.expressions: 3530 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3531 return "" 3532 3533 def star_sql(self, expression: exp.Star) -> str: 3534 except_ = self.expressions(expression, key="except_", flat=True) 3535 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3536 replace = self.expressions(expression, key="replace", flat=True) 3537 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3538 rename = self.expressions(expression, key="rename", flat=True) 3539 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3540 ilike = self.sql(expression, "ilike") 3541 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3542 return f"*{ilike}{except_}{replace}{rename}" 3543 3544 def parameter_sql(self, expression: exp.Parameter) -> str: 3545 this = self.sql(expression, "this") 3546 return f"{self.PARAMETER_TOKEN}{this}" 3547 3548 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3549 this = self.sql(expression, "this") 3550 kind = expression.text("kind") 3551 if kind: 3552 kind = f"{kind}." 3553 return f"@@{kind}{this}" 3554 3555 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3556 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3557 3558 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3559 alias = self.sql(expression, "alias") 3560 alias = f"{sep}{alias}" if alias else "" 3561 sample = self.sql(expression, "sample") 3562 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3563 alias = f"{sample}{alias}" 3564 3565 # Set to None so it's not generated again by self.query_modifiers() 3566 expression.set("sample", None) 3567 3568 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3569 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3570 return self.prepend_ctes(expression, sql) 3571 3572 def qualify_sql(self, expression: exp.Qualify) -> str: 3573 this = self.indent(self.sql(expression, "this")) 3574 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3575 3576 def unnest_sql(self, expression: exp.Unnest) -> str: 3577 args = self.expressions(expression, flat=True) 3578 3579 alias = expression.args.get("alias") 3580 offset = expression.args.get("offset") 3581 3582 if self.UNNEST_WITH_ORDINALITY: 3583 if alias and isinstance(offset, exp.Expr): 3584 alias.append("columns", offset) 3585 expression.set("offset", None) 3586 3587 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3588 columns = alias.columns 3589 alias = self.sql(columns[0]) if columns else "" 3590 else: 3591 alias = self.sql(alias) 3592 3593 alias = f" AS {alias}" if alias else alias 3594 if self.UNNEST_WITH_ORDINALITY: 3595 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3596 else: 3597 if isinstance(offset, exp.Expr): 3598 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3599 elif offset: 3600 suffix = f"{alias} WITH OFFSET" 3601 else: 3602 suffix = alias 3603 3604 return f"UNNEST({args}){suffix}" 3605 3606 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3607 return "" 3608 3609 def where_sql(self, expression: exp.Where) -> str: 3610 this = self.indent(self.sql(expression, "this")) 3611 return f"{self.seg('WHERE')}{self.sep()}{this}" 3612 3613 def window_sql(self, expression: exp.Window) -> str: 3614 this = self.sql(expression, "this") 3615 partition = self.partition_by_sql(expression) 3616 order = expression.args.get("order") 3617 order = self.order_sql(order, flat=True) if order else "" 3618 spec = self.sql(expression, "spec") 3619 alias = self.sql(expression, "alias") 3620 over = self.sql(expression, "over") or "OVER" 3621 3622 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3623 3624 first = expression.args.get("first") 3625 if first is None: 3626 first = "" 3627 else: 3628 first = "FIRST" if first else "LAST" 3629 3630 if not partition and not order and not spec and alias: 3631 return f"{this} {alias}" 3632 3633 args = self.format_args( 3634 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3635 ) 3636 return f"{this} ({args})" 3637 3638 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3639 partition = self.expressions(expression, key="partition_by", flat=True) 3640 return f"PARTITION BY {partition}" if partition else "" 3641 3642 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3643 kind = self.sql(expression, "kind") 3644 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3645 end = ( 3646 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3647 or "CURRENT ROW" 3648 ) 3649 3650 window_spec = f"{kind} BETWEEN {start} AND {end}" 3651 3652 exclude = self.sql(expression, "exclude") 3653 if exclude: 3654 if self.SUPPORTS_WINDOW_EXCLUDE: 3655 window_spec += f" EXCLUDE {exclude}" 3656 else: 3657 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3658 3659 return window_spec 3660 3661 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3662 this = self.sql(expression, "this") 3663 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3664 return f"{this} WITHIN GROUP ({expression_sql})" 3665 3666 def between_sql(self, expression: exp.Between) -> str: 3667 this = self.sql(expression, "this") 3668 low = self.sql(expression, "low") 3669 high = self.sql(expression, "high") 3670 symmetric = expression.args.get("symmetric") 3671 3672 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3673 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3674 3675 flag = ( 3676 " SYMMETRIC" 3677 if symmetric 3678 else " ASYMMETRIC" 3679 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3680 else "" # silently drop ASYMMETRIC – semantics identical 3681 ) 3682 return f"{this} BETWEEN{flag} {low} AND {high}" 3683 3684 def bracket_offset_expressions( 3685 self, expression: exp.Bracket, index_offset: int | None = None 3686 ) -> list[exp.Expr]: 3687 if expression.args.get("json_access"): 3688 return expression.expressions 3689 3690 return apply_index_offset( 3691 expression.this, 3692 expression.expressions, 3693 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3694 dialect=self.dialect, 3695 ) 3696 3697 def bracket_sql(self, expression: exp.Bracket) -> str: 3698 expressions = self.bracket_offset_expressions(expression) 3699 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3700 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3701 3702 def all_sql(self, expression: exp.All) -> str: 3703 this = self.sql(expression, "this") 3704 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3705 this = self.wrap(this) 3706 return f"ALL {this}" 3707 3708 def any_sql(self, expression: exp.Any) -> str: 3709 this = self.sql(expression, "this") 3710 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3711 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3712 this = self.wrap(this) 3713 return f"ANY{this}" 3714 return f"ANY {this}" 3715 3716 def exists_sql(self, expression: exp.Exists) -> str: 3717 return f"EXISTS{self.wrap(expression)}" 3718 3719 def case_sql(self, expression: exp.Case) -> str: 3720 this = self.sql(expression, "this") 3721 statements = [f"CASE {this}" if this else "CASE"] 3722 3723 for e in expression.args["ifs"]: 3724 statements.append(f"WHEN {self.sql(e, 'this')}") 3725 statements.append(f"THEN {self.sql(e, 'true')}") 3726 3727 default = self.sql(expression, "default") 3728 3729 if default: 3730 statements.append(f"ELSE {default}") 3731 3732 statements.append("END") 3733 3734 if self.pretty and self.too_wide(statements): 3735 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3736 3737 return " ".join(statements) 3738 3739 def constraint_sql(self, expression: exp.Constraint) -> str: 3740 this = self.sql(expression, "this") 3741 expressions = self.expressions(expression, flat=True) 3742 return f"CONSTRAINT {this} {expressions}" 3743 3744 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3745 order = expression.args.get("order") 3746 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3747 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3748 3749 def extract_sql(self, expression: exp.Extract) -> str: 3750 import sqlglot.dialects.dialect 3751 3752 this = ( 3753 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3754 if self.NORMALIZE_EXTRACT_DATE_PARTS 3755 else expression.this 3756 ) 3757 if self.EXTRACT_ALLOWS_QUOTES: 3758 this_sql = self.sql(this) 3759 elif isinstance(this, exp.WeekStart): 3760 this_sql = self.weekstart_name(this) 3761 else: 3762 this_sql = this.name 3763 expression_sql = self.sql(expression, "expression") 3764 3765 return f"EXTRACT({this_sql} FROM {expression_sql})" 3766 3767 def trim_sql(self, expression: exp.Trim) -> str: 3768 trim_type = self.sql(expression, "position") 3769 3770 if trim_type == "LEADING": 3771 func_name = "LTRIM" 3772 elif trim_type == "TRAILING": 3773 func_name = "RTRIM" 3774 else: 3775 func_name = "TRIM" 3776 3777 return self.func(func_name, expression.this, expression.expression) 3778 3779 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3780 args = expression.expressions 3781 if isinstance(expression, exp.ConcatWs): 3782 args = args[1:] # Skip the delimiter 3783 3784 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3785 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3786 3787 concat_coalesce = ( 3788 self.dialect.CONCAT_WS_COALESCE 3789 if isinstance(expression, exp.ConcatWs) 3790 else self.dialect.CONCAT_COALESCE 3791 ) 3792 3793 if not concat_coalesce and expression.args.get("coalesce"): 3794 3795 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3796 if not e.type: 3797 import sqlglot.optimizer.annotate_types 3798 3799 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3800 3801 if e.is_string or e.is_type(exp.DType.ARRAY): 3802 return e 3803 3804 return exp.func("coalesce", e, exp.Literal.string("")) 3805 3806 args = [_wrap_with_coalesce(e) for e in args] 3807 3808 return args 3809 3810 def concat_sql(self, expression: exp.Concat) -> str: 3811 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3812 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3813 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3814 # instead of coalescing them to empty string. 3815 import sqlglot.dialects.dialect 3816 3817 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3818 3819 expressions = self.convert_concat_args(expression) 3820 3821 # Some dialects don't allow a single-argument CONCAT call 3822 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3823 return self.sql(expressions[0]) 3824 3825 return self.func("CONCAT", *expressions) 3826 3827 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3828 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3829 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3830 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3831 all_args = expression.expressions 3832 expression.set("coalesce", True) 3833 return self.sql( 3834 exp.case() 3835 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3836 .else_(expression) 3837 ) 3838 3839 return self.func( 3840 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3841 ) 3842 3843 def check_sql(self, expression: exp.Check) -> str: 3844 this = self.sql(expression, key="this") 3845 return f"CHECK ({this})" 3846 3847 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3848 this = self.sql(expression, "this") 3849 this = f" {this}" if this else "" 3850 expressions = self.expressions(expression, flat=True) 3851 expressions = f" ({expressions})" if expressions else "" 3852 reference = self.sql(expression, "reference") 3853 reference = f" {reference}" if reference else "" 3854 delete = self.sql(expression, "delete") 3855 delete = f" ON DELETE {delete}" if delete else "" 3856 update = self.sql(expression, "update") 3857 update = f" ON UPDATE {update}" if update else "" 3858 options = self.expressions(expression, key="options", flat=True, sep=" ") 3859 options = f" {options}" if options else "" 3860 return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}" 3861 3862 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3863 this = self.sql(expression, "this") 3864 this = f" {this}" if this else "" 3865 expressions = self.expressions(expression, flat=True) 3866 include = self.sql(expression, "include") 3867 options = self.expressions(expression, key="options", flat=True, sep=" ") 3868 options = f" {options}" if options else "" 3869 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3870 3871 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3872 self.unsupported("TIMESERIES primary key columns are not supported") 3873 return self.sql(expression, "this") 3874 3875 def if_sql(self, expression: exp.If) -> str: 3876 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3877 3878 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3879 if self.MATCH_AGAINST_TABLE_PREFIX: 3880 expressions = [] 3881 for expr in expression.expressions: 3882 if isinstance(expr, exp.Table): 3883 expressions.append(f"TABLE {self.sql(expr)}") 3884 else: 3885 expressions.append(expr) 3886 else: 3887 expressions = expression.expressions 3888 3889 modifier = expression.args.get("modifier") 3890 modifier = f" {modifier}" if modifier else "" 3891 return ( 3892 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3893 ) 3894 3895 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3896 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3897 3898 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3899 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3900 3901 if self.QUOTE_JSON_PATH: 3902 path = self.escape_str(path) 3903 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3904 3905 return path 3906 3907 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3908 if isinstance(expression, exp.JSONPathPart): 3909 transform = self.TRANSFORMS.get(expression.__class__) 3910 if not callable(transform): 3911 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3912 return "" 3913 3914 return transform(self, expression) 3915 3916 if isinstance(expression, int): 3917 return str(expression) 3918 3919 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3920 escaped = expression.replace("'", "\\'") 3921 escaped = f"'{escaped}'" 3922 else: 3923 escaped = expression.replace('"', '\\"') 3924 escaped = f'"{escaped}"' 3925 3926 return escaped 3927 3928 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3929 return f"{self.sql(expression, 'this')} FORMAT JSON" 3930 3931 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3932 # Output the Teradata column FORMAT override. 3933 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3934 this = self.sql(expression, "this") 3935 fmt = self.sql(expression, "format") 3936 return f"{this} (FORMAT {fmt})" 3937 3938 def _jsonobject_sql( 3939 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3940 ) -> str: 3941 null_handling = expression.args.get("null_handling") 3942 null_handling = f" {null_handling}" if null_handling else "" 3943 3944 unique_keys = expression.args.get("unique_keys") 3945 if unique_keys is not None: 3946 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3947 else: 3948 unique_keys = "" 3949 3950 return_type = self.sql(expression, "return_type") 3951 return_type = f" RETURNING {return_type}" if return_type else "" 3952 encoding = self.sql(expression, "encoding") 3953 encoding = f" ENCODING {encoding}" if encoding else "" 3954 3955 if not name: 3956 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3957 3958 return self.func( 3959 name, 3960 *expression.expressions, 3961 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3962 ) 3963 3964 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3965 null_handling = expression.args.get("null_handling") 3966 null_handling = f" {null_handling}" if null_handling else "" 3967 return_type = self.sql(expression, "return_type") 3968 return_type = f" RETURNING {return_type}" if return_type else "" 3969 strict = " STRICT" if expression.args.get("strict") else "" 3970 return self.func( 3971 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3972 ) 3973 3974 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3975 this = self.sql(expression, "this") 3976 order = self.sql(expression, "order") 3977 null_handling = expression.args.get("null_handling") 3978 null_handling = f" {null_handling}" if null_handling else "" 3979 return_type = self.sql(expression, "return_type") 3980 return_type = f" RETURNING {return_type}" if return_type else "" 3981 strict = " STRICT" if expression.args.get("strict") else "" 3982 return self.func( 3983 "JSON_ARRAYAGG", 3984 this, 3985 suffix=f"{order}{null_handling}{return_type}{strict})", 3986 ) 3987 3988 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3989 path = self.sql(expression, "path") 3990 path = f" PATH {path}" if path else "" 3991 nested_schema = self.sql(expression, "nested_schema") 3992 3993 if nested_schema: 3994 return f"NESTED{path} {nested_schema}" 3995 3996 this = self.sql(expression, "this") 3997 kind = self.sql(expression, "kind") 3998 kind = f" {kind}" if kind else "" 3999 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 4000 4001 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 4002 return f"{this}{kind}{format_json}{path}{ordinality}" 4003 4004 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 4005 return self.func("COLUMNS", *expression.expressions) 4006 4007 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4008 this = self.sql(expression, "this") 4009 path = self.sql(expression, "path") 4010 path = f", {path}" if path else "" 4011 error_handling = expression.args.get("error_handling") 4012 error_handling = f" {error_handling}" if error_handling else "" 4013 empty_handling = expression.args.get("empty_handling") 4014 empty_handling = f" {empty_handling}" if empty_handling else "" 4015 schema = self.sql(expression, "schema") 4016 return self.func( 4017 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4018 ) 4019 4020 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4021 this = self.sql(expression, "this") 4022 kind = self.sql(expression, "kind") 4023 path = self.sql(expression, "path") 4024 path = f" {path}" if path else "" 4025 as_json = " AS JSON" if expression.args.get("as_json") else "" 4026 return f"{this} {kind}{path}{as_json}" 4027 4028 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4029 this = self.sql(expression, "this") 4030 path = self.sql(expression, "path") 4031 path = f", {path}" if path else "" 4032 expressions = self.expressions(expression) 4033 with_ = ( 4034 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4035 if expressions 4036 else "" 4037 ) 4038 return f"OPENJSON({this}{path}){with_}" 4039 4040 def in_sql(self, expression: exp.In) -> str: 4041 query = expression.args.get("query") 4042 unnest = expression.args.get("unnest") 4043 field = expression.args.get("field") 4044 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4045 4046 if query: 4047 in_sql = self.sql(query) 4048 elif unnest: 4049 in_sql = self.in_unnest_op(unnest) 4050 elif field: 4051 in_sql = self.sql(field) 4052 else: 4053 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4054 4055 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 4056 4057 def in_unnest_op(self, unnest: exp.Unnest) -> str: 4058 return f"(SELECT {self.sql(unnest)})" 4059 4060 def interval_sql(self, expression: exp.Interval) -> str: 4061 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4062 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4063 exp.AutoRefreshProperty, 4064 ) 4065 interval_keyword = "INTERVAL" if include_keyword else "" 4066 unit_expression = expression.args.get("unit") 4067 unit = self.sql(unit_expression) if unit_expression else "" 4068 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4069 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4070 unit = f" {unit}" if unit else "" 4071 4072 if self.SINGLE_STRING_INTERVAL: 4073 this = expression.this.name if expression.this else "" 4074 if this: 4075 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4076 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4077 return f"{interval_keyword}'{this}'{unit}" 4078 return f"{interval_keyword}'{this}{unit}'" 4079 return f"{interval_keyword}{unit}" 4080 4081 this = self.sql(expression, "this") 4082 if this: 4083 if not include_keyword and expression.this.is_string: 4084 this = expression.this.name 4085 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4086 this = f"({this})" 4087 if include_keyword: 4088 this = f" {this}" 4089 4090 return f"{interval_keyword}{this}{unit}" 4091 4092 def return_sql(self, expression: exp.Return) -> str: 4093 return f"RETURN {self.sql(expression, 'this')}" 4094 4095 def reference_sql(self, expression: exp.Reference) -> str: 4096 this = self.sql(expression, "this") 4097 expressions = self.expressions(expression, flat=True) 4098 expressions = f"({expressions})" if expressions else "" 4099 options = self.expressions(expression, key="options", flat=True, sep=" ") 4100 options = f" {options}" if options else "" 4101 return f"REFERENCES {this}{expressions}{options}" 4102 4103 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4104 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4105 parent = expression.parent 4106 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4107 4108 return self.func( 4109 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4110 ) 4111 4112 def paren_sql(self, expression: exp.Paren) -> str: 4113 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4114 return f"({sql}{self.seg(')', sep='')}" 4115 4116 def neg_sql(self, expression: exp.Neg) -> str: 4117 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4118 this_sql = self.sql(expression, "this") 4119 sep = " " if this_sql[0] == "-" else "" 4120 return f"-{sep}{this_sql}" 4121 4122 def not_sql(self, expression: exp.Not) -> str: 4123 return f"NOT {self.sql(expression, 'this')}" 4124 4125 def alias_sql(self, expression: exp.Alias) -> str: 4126 alias = self.sql(expression, "alias") 4127 alias = f" AS {alias}" if alias else "" 4128 return f"{self.sql(expression, 'this')}{alias}" 4129 4130 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4131 alias = expression.args["alias"] 4132 4133 parent = expression.parent 4134 pivot = parent and parent.parent 4135 4136 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4137 identifier_alias = isinstance(alias, exp.Identifier) 4138 literal_alias = isinstance(alias, exp.Literal) 4139 4140 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4141 alias.replace(exp.Literal.string(alias.output_name)) 4142 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4143 alias.replace(exp.to_identifier(alias.output_name)) 4144 4145 return self.alias_sql(expression) 4146 4147 def aliases_sql(self, expression: exp.Aliases) -> str: 4148 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4149 4150 def atindex_sql(self, expression: exp.AtIndex) -> str: 4151 this = self.sql(expression, "this") 4152 index = self.sql(expression, "expression") 4153 return f"{this} AT {index}" 4154 4155 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4156 this = self.sql(expression, "this") 4157 zone = self.sql(expression, "zone") 4158 return f"{this} AT TIME ZONE {zone}" 4159 4160 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4161 this = self.sql(expression, "this") 4162 zone = self.sql(expression, "zone") 4163 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4164 4165 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4166 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4167 4168 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4169 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4170 4171 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4172 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4173 4174 def add_sql(self, expression: exp.Add) -> str: 4175 return self.binary(expression, "+") 4176 4177 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4178 return self.connector_sql(expression, "AND", stack) 4179 4180 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4181 return self.connector_sql(expression, "OR", stack) 4182 4183 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4184 return self.connector_sql(expression, "XOR", stack) 4185 4186 def connector_sql( 4187 self, 4188 expression: exp.Connector, 4189 op: str, 4190 stack: list[str | exp.Expr] | None = None, 4191 ) -> str: 4192 if stack is not None: 4193 stack.append(expression.right) 4194 if expression.comments and self.comments: 4195 op = self.maybe_comment(op, comments=expression.comments) 4196 4197 stack.extend((op, expression.left)) 4198 return op 4199 4200 stack = [expression] 4201 sqls: list[str] = [] 4202 ops = set() 4203 4204 while stack: 4205 node = stack.pop() 4206 if isinstance(node, exp.Connector): 4207 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4208 else: 4209 sql = self.sql(node) 4210 if sqls and sqls[-1] in ops: 4211 sqls[-1] += f" {sql}" 4212 else: 4213 sqls.append(sql) 4214 4215 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4216 return sep.join(sqls) 4217 4218 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4219 return self.binary(expression, "&") 4220 4221 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4222 return self.binary(expression, "<<") 4223 4224 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4225 return f"~{self.sql(expression, 'this')}" 4226 4227 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4228 return self.binary(expression, "|") 4229 4230 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4231 return self.binary(expression, ">>") 4232 4233 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4234 return self.binary(expression, "^") 4235 4236 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4237 format_sql = self.sql(expression, "format") 4238 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4239 to_sql = self.sql(expression, "to") 4240 to_sql = f" {to_sql}" if to_sql else "" 4241 action = self.sql(expression, "action") 4242 action = f" {action}" if action else "" 4243 default = self.sql(expression, "default") 4244 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4245 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4246 4247 # Base implementation that excludes safe, zone, and target_type metadata args 4248 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4249 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4250 4251 # Base implementation that excludes the safe and default_year metadata args 4252 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4253 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4254 4255 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4256 return self.func( 4257 "PARSE_DATETIME", 4258 expression.this, 4259 expression.args.get("format"), 4260 expression.args.get("zone"), 4261 ) 4262 4263 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4264 zone = self.sql(expression, "this") 4265 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4266 4267 def collate_sql(self, expression: exp.Collate) -> str: 4268 if self.COLLATE_IS_FUNC: 4269 return self.function_fallback_sql(expression) 4270 return self.binary(expression, "COLLATE") 4271 4272 def command_sql(self, expression: exp.Command) -> str: 4273 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4274 4275 def comment_sql(self, expression: exp.Comment) -> str: 4276 this = self.sql(expression, "this") 4277 kind = expression.args["kind"] 4278 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4279 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4280 expression_sql = self.sql(expression, "expression") 4281 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4282 4283 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4284 this = self.sql(expression, "this") 4285 delete = " DELETE" if expression.args.get("delete") else "" 4286 recompress = self.sql(expression, "recompress") 4287 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4288 to_disk = self.sql(expression, "to_disk") 4289 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4290 to_volume = self.sql(expression, "to_volume") 4291 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4292 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4293 4294 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4295 where = self.sql(expression, "where") 4296 group = self.sql(expression, "group") 4297 aggregates = self.expressions(expression, key="aggregates") 4298 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4299 4300 if not (where or group or aggregates) and len(expression.expressions) == 1: 4301 return f"TTL {self.expressions(expression, flat=True)}" 4302 4303 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4304 4305 def transaction_sql(self, expression: exp.Transaction) -> str: 4306 modes = self.expressions(expression, key="modes") 4307 modes = f" {modes}" if modes else "" 4308 return f"BEGIN{modes}" 4309 4310 def commit_sql(self, expression: exp.Commit) -> str: 4311 chain = expression.args.get("chain") 4312 if chain is not None: 4313 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4314 4315 return f"COMMIT{chain or ''}" 4316 4317 def rollback_sql(self, expression: exp.Rollback) -> str: 4318 savepoint = expression.args.get("savepoint") 4319 savepoint = f" TO {savepoint}" if savepoint else "" 4320 return f"ROLLBACK{savepoint}" 4321 4322 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4323 this = self.sql(expression, "this") 4324 4325 exists = "" 4326 if expression.args.get("exists"): 4327 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4328 exists = " IF EXISTS" 4329 else: 4330 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4331 4332 dtype = self.sql(expression, "dtype") 4333 if dtype: 4334 collate = self.sql(expression, "collate") 4335 collate = f" COLLATE {collate}" if collate else "" 4336 using = self.sql(expression, "using") 4337 using = f" USING {using}" if using else "" 4338 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4339 null_constraint = self._alter_column_null_constraint_sql(expression) 4340 4341 return ( 4342 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4343 f"{collate}{using}{null_constraint}" 4344 ) 4345 4346 default = self.sql(expression, "default") 4347 if default: 4348 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4349 4350 comment = self.sql(expression, "comment") 4351 if comment: 4352 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4353 4354 visible = expression.args.get("visible") 4355 if visible: 4356 return f"ALTER COLUMN{exists} {this} SET {visible}" 4357 4358 allow_null = expression.args.get("allow_null") 4359 drop = expression.args.get("drop") 4360 4361 if not drop and not allow_null: 4362 self.unsupported("Unsupported ALTER COLUMN syntax") 4363 4364 if allow_null is not None: 4365 keyword = "DROP" if drop else "SET" 4366 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4367 4368 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4369 4370 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4371 allow_null = expression.args.get("allow_null") 4372 if allow_null is None: 4373 return "" 4374 4375 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4376 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4377 return "" 4378 4379 return " NULL" if allow_null else " NOT NULL" 4380 4381 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4382 this = self.sql(expression, "this") 4383 rename_from = self.sql(expression, "rename_from") 4384 if rename_from: 4385 if not self.SUPPORTS_CHANGE_COLUMN: 4386 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4387 return f"CHANGE COLUMN {rename_from} {this}" 4388 if not self.SUPPORTS_MODIFY_COLUMN: 4389 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4390 return f"MODIFY COLUMN {this}" 4391 4392 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4393 this = self.sql(expression, "this") 4394 4395 visible = expression.args.get("visible") 4396 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4397 4398 return f"ALTER INDEX {this} {visible_sql}" 4399 4400 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4401 this = self.sql(expression, "this") 4402 if not isinstance(expression.this, exp.Var): 4403 this = f"KEY DISTKEY {this}" 4404 return f"ALTER DISTSTYLE {this}" 4405 4406 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4407 compound = " COMPOUND" if expression.args.get("compound") else "" 4408 this = self.sql(expression, "this") 4409 expressions = self.expressions(expression, flat=True) 4410 expressions = f"({expressions})" if expressions else "" 4411 return f"ALTER{compound} SORTKEY {this or expressions}" 4412 4413 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4414 if not self.RENAME_TABLE_WITH_DB: 4415 # Remove db from tables 4416 expression = expression.transform( 4417 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4418 ).assert_is(exp.AlterRename) 4419 this = self.sql(expression, "this") 4420 to_kw = " TO" if include_to else "" 4421 return f"RENAME{to_kw} {this}" 4422 4423 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4424 exists = " IF EXISTS" if expression.args.get("exists") else "" 4425 old_column = self.sql(expression, "this") 4426 new_column = self.sql(expression, "to") 4427 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4428 4429 def alterset_sql(self, expression: exp.AlterSet) -> str: 4430 exprs = self.expressions(expression, flat=True) 4431 if self.ALTER_SET_WRAPPED: 4432 exprs = f"({exprs})" 4433 4434 return f"SET {exprs}" 4435 4436 def alter_sql(self, expression: exp.Alter) -> str: 4437 actions = expression.actions 4438 4439 if ( 4440 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4441 and actions 4442 and isinstance(actions[0], exp.ColumnDef) 4443 ): 4444 actions_sql = self.expressions(expression, key="actions", flat=True) 4445 actions_sql = f"ADD {actions_sql}" 4446 else: 4447 actions_list = [] 4448 for action in actions: 4449 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4450 action_sql = self.add_column_sql(action) 4451 else: 4452 action_sql = self.sql(action) 4453 if isinstance(action, exp.Query): 4454 action_sql = f"AS {action_sql}" 4455 4456 actions_list.append(action_sql) 4457 4458 actions_sql = self.format_args(*actions_list).lstrip("\n") 4459 4460 iceberg = ( 4461 "ICEBERG " 4462 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4463 else "" 4464 ) 4465 exists = " IF EXISTS" if expression.args.get("exists") else "" 4466 on_cluster = self.sql(expression, "cluster") 4467 on_cluster = f" {on_cluster}" if on_cluster else "" 4468 only = " ONLY" if expression.args.get("only") else "" 4469 options = expression.args.get("options") or [] 4470 options_sql = self.expressions( 4471 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4472 ) 4473 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4474 partitions_sql = self.expressions( 4475 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4476 sep=" ", 4477 flat=True, 4478 ) 4479 if partitions_sql and (actions_sql or options_sql): 4480 partitions_sql = f" {partitions_sql}" 4481 kind = self.sql(expression, "kind") 4482 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4483 check = " WITH CHECK" if expression.args.get("check") else "" 4484 cascade = ( 4485 " CASCADE" 4486 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4487 else "" 4488 ) 4489 this = self.sql(expression, "this") 4490 this = f" {this}" if this else "" 4491 4492 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}" 4493 4494 def altersession_sql(self, expression: exp.AlterSession) -> str: 4495 items_sql = self.expressions(expression, flat=True) 4496 keyword = "UNSET" if expression.args.get("unset") else "SET" 4497 return f"{keyword} {items_sql}" 4498 4499 def add_column_sql(self, expression: exp.Expr) -> str: 4500 sql = self.sql(expression) 4501 if isinstance(expression, exp.Schema): 4502 column_text = " COLUMNS" 4503 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4504 column_text = " COLUMN" 4505 else: 4506 column_text = "" 4507 4508 return f"ADD{column_text} {sql}" 4509 4510 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4511 expressions = self.expressions(expression) 4512 exists = " IF EXISTS " if expression.args.get("exists") else " " 4513 return f"DROP{exists}{expressions}" 4514 4515 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4516 return "DROP PRIMARY KEY" 4517 4518 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4519 return f"ADD {self.expressions(expression, indent=False)}" 4520 4521 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4522 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4523 location = self.sql(expression, "location") 4524 location = f" {location}" if location else "" 4525 return f"ADD {exists}{self.sql(expression.this)}{location}" 4526 4527 def distinct_sql(self, expression: exp.Distinct) -> str: 4528 this = self.expressions(expression, flat=True) 4529 4530 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4531 case = exp.case() 4532 for arg in expression.expressions: 4533 case = case.when(arg.is_(exp.null()), exp.null()) 4534 this = self.sql(case.else_(f"({this})")) 4535 4536 this = f" {this}" if this else "" 4537 4538 on = self.sql(expression, "on") 4539 on = f" ON {on}" if on else "" 4540 return f"DISTINCT{this}{on}" 4541 4542 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4543 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4544 4545 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4546 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4547 4548 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4549 this_sql = self.sql(expression, "this") 4550 expression_sql = self.sql(expression, "expression") 4551 kind = "MAX" if expression.args.get("max") else "MIN" 4552 return f"{this_sql} HAVING {kind} {expression_sql}" 4553 4554 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4555 return self._truncated_div_sql(expression.this, expression.expression) 4556 4557 def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str: 4558 div: exp.Expr = exp.Div(this=this, expression=divisor) 4559 if self.CAST_TO_INT_ROUNDS: 4560 div = exp.Trunc(this=div) 4561 4562 return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT))) 4563 4564 def dpipe_sql(self, expression: exp.DPipe) -> str: 4565 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4566 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4567 return self.binary(expression, "||") 4568 4569 def div_sql(self, expression: exp.Div) -> str: 4570 l, r = expression.left, expression.right 4571 4572 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4573 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4574 4575 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4576 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4577 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4578 4579 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4580 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4581 # Native integer division depends on the target's operand types, which may differ 4582 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4583 # integer literals or casts 4584 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4585 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4586 ): 4587 return self.sql(exp.IntDiv(this=l, expression=r)) 4588 return self._truncated_div_sql(l, r) 4589 4590 return self.binary(expression, "/") 4591 4592 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4593 n = exp._wrap(expression.this, exp.Binary) 4594 d = exp._wrap(expression.expression, exp.Binary) 4595 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4596 4597 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4598 return self.binary(expression, "OVERLAPS") 4599 4600 def distance_sql(self, expression: exp.Distance) -> str: 4601 return self.binary(expression, "<->") 4602 4603 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4604 return self.binary(expression, "<<->>") 4605 4606 def dot_sql(self, expression: exp.Dot) -> str: 4607 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4608 4609 def eq_sql(self, expression: exp.EQ) -> str: 4610 return self.binary(expression, "=") 4611 4612 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4613 return self.binary(expression, ":=") 4614 4615 def escape_sql(self, expression: exp.Escape) -> str: 4616 this = expression.this 4617 if ( 4618 isinstance(this, (exp.Like, exp.ILike)) 4619 and isinstance(this.expression, (exp.All, exp.Any)) 4620 and not self.SUPPORTS_LIKE_QUANTIFIERS 4621 ): 4622 return self._like_sql(this, escape=expression) 4623 return self.binary(expression, "ESCAPE") 4624 4625 def glob_sql(self, expression: exp.Glob) -> str: 4626 return self.binary(expression, "GLOB") 4627 4628 def gt_sql(self, expression: exp.GT) -> str: 4629 return self.binary(expression, ">") 4630 4631 def gte_sql(self, expression: exp.GTE) -> str: 4632 return self.binary(expression, ">=") 4633 4634 def is_sql(self, expression: exp.Is) -> str: 4635 negate = expression.args.get("negate") 4636 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4637 positive = bool(expression.expression.this) != bool(negate) 4638 return self.sql(expression.this if positive else exp.not_(expression.this)) 4639 return self.binary(expression, "IS NOT" if negate else "IS") 4640 4641 def _like_sql( 4642 self, 4643 expression: exp.Like | exp.ILike, 4644 escape: exp.Escape | None = None, 4645 ) -> str: 4646 this = expression.this 4647 rhs = expression.expression 4648 4649 if isinstance(expression, exp.Like): 4650 exp_class: type[exp.Like | exp.ILike] = exp.Like 4651 op = "LIKE" 4652 else: 4653 exp_class = exp.ILike 4654 op = "ILIKE" 4655 4656 if expression.args.get("negate"): 4657 op = f"NOT {op}" 4658 4659 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4660 exprs = rhs.this.unnest() 4661 4662 if isinstance(exprs, exp.Tuple): 4663 exprs = exprs.expressions 4664 else: 4665 exprs = [exprs] 4666 4667 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4668 4669 def _make_like(expr: exp.Expression) -> exp.Expression: 4670 like: exp.Expression = exp_class( 4671 this=this, expression=expr, negate=expression.args.get("negate") 4672 ) 4673 if escape: 4674 like = exp.Escape(this=like, expression=escape.expression.copy()) 4675 return like 4676 4677 like_expr: exp.Expr = _make_like(exprs[0]) 4678 for expr in exprs[1:]: 4679 like_expr = connective(like_expr, _make_like(expr), copy=False) 4680 4681 parent = escape.parent if escape else expression.parent 4682 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4683 parent, exp.Condition 4684 ): 4685 like_expr = exp.paren(like_expr, copy=False) 4686 4687 return self.sql(like_expr) 4688 4689 return self.binary(expression, op) 4690 4691 def like_sql(self, expression: exp.Like) -> str: 4692 return self._like_sql(expression) 4693 4694 def ilike_sql(self, expression: exp.ILike) -> str: 4695 return self._like_sql(expression) 4696 4697 def match_sql(self, expression: exp.Match) -> str: 4698 if expression.args.get("kind") or expression.args.get("analyzer"): 4699 self.unsupported("MATCH variants and USING ANALYZER are not supported in this dialect") 4700 return self.binary(expression, "MATCH") 4701 4702 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4703 return self.binary(expression, "SIMILAR TO") 4704 4705 def lt_sql(self, expression: exp.LT) -> str: 4706 return self.binary(expression, "<") 4707 4708 def lte_sql(self, expression: exp.LTE) -> str: 4709 return self.binary(expression, "<=") 4710 4711 def mod_sql(self, expression: exp.Mod) -> str: 4712 this = self.sql(expression, "this") 4713 expr = self.sql(expression, "expression") 4714 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4715 4716 parent = expression.parent 4717 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4718 return f"({sql})" 4719 4720 return sql 4721 4722 def mul_sql(self, expression: exp.Mul) -> str: 4723 return self.binary(expression, "*") 4724 4725 def neq_sql(self, expression: exp.NEQ) -> str: 4726 return self.binary(expression, "<>") 4727 4728 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4729 return self.binary(expression, "IS NOT DISTINCT FROM") 4730 4731 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4732 return self.binary(expression, "IS DISTINCT FROM") 4733 4734 def sub_sql(self, expression: exp.Sub) -> str: 4735 return self.binary(expression, "-") 4736 4737 def trycast_sql(self, expression: exp.TryCast) -> str: 4738 return self.cast_sql(expression, safe_prefix="TRY_") 4739 4740 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4741 return self.cast_sql(expression) 4742 4743 def try_sql(self, expression: exp.Try) -> str: 4744 if not self.TRY_SUPPORTED: 4745 self.unsupported("Unsupported TRY function") 4746 return self.sql(expression, "this") 4747 4748 return self.func("TRY", expression.this) 4749 4750 def log_sql(self, expression: exp.Log) -> str: 4751 this = expression.this 4752 expr = expression.expression 4753 4754 if self.dialect.LOG_BASE_FIRST is False: 4755 this, expr = expr, this 4756 elif self.dialect.LOG_BASE_FIRST is None and expr: 4757 if this.name in ("2", "10"): 4758 return self.func(f"LOG{this.name}", expr) 4759 4760 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4761 4762 return self.func("LOG", this, expr) 4763 4764 def use_sql(self, expression: exp.Use) -> str: 4765 kind = self.sql(expression, "kind") 4766 kind = f" {kind}" if kind else "" 4767 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4768 this = f" {this}" if this else "" 4769 return f"USE{kind}{this}" 4770 4771 def binary(self, expression: exp.Binary, op: str) -> str: 4772 sqls: list[str] = [] 4773 stack: list[None | str | exp.Expr] = [expression] 4774 binary_type = type(expression) 4775 4776 while stack: 4777 node = stack.pop() 4778 4779 if type(node) is binary_type: 4780 op_func = node.args.get("operator") 4781 if op_func: 4782 op = f"OPERATOR({self.sql(op_func)})" 4783 4784 stack.append(node.args.get("expression")) 4785 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4786 stack.append(node.args.get("this")) 4787 else: 4788 sqls.append(self.sql(node)) 4789 4790 return "".join(sqls) 4791 4792 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4793 to_clause = self.sql(expression, "to") 4794 if to_clause: 4795 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4796 4797 return self.function_fallback_sql(expression) 4798 4799 def function_fallback_sql(self, expression: exp.Func) -> str: 4800 args = [] 4801 4802 for key in expression.arg_types: 4803 arg_value = expression.args.get(key) 4804 4805 if isinstance(arg_value, list): 4806 for value in arg_value: 4807 args.append(value) 4808 elif arg_value is not None: 4809 args.append(arg_value) 4810 4811 return self.func(self.function_name(expression), *args) 4812 4813 def function_name(self, expression: exp.Func) -> str: 4814 name_key = self.dialect.ORIGINAL_NAME_META_KEY 4815 return (expression.meta_get(name_key) if name_key else None) or expression.sql_name() 4816 4817 def func( 4818 self, 4819 name: str, 4820 *args: t.Any, 4821 prefix: str = "(", 4822 suffix: str = ")", 4823 normalize: bool = True, 4824 ) -> str: 4825 name = self.normalize_func(name) if normalize else name 4826 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4827 4828 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4829 arg_sqls = tuple( 4830 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4831 ) 4832 if self.pretty and self.too_wide(arg_sqls): 4833 return self.indent( 4834 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4835 ) 4836 return sep.join(arg_sqls) 4837 4838 def too_wide(self, args: t.Iterable) -> bool: 4839 return sum(len(arg) for arg in args) > self.max_text_width 4840 4841 def format_time( 4842 self, 4843 expression: exp.Expr, 4844 inverse_time_mapping: dict[str, str] | None = None, 4845 inverse_time_trie: dict | None = None, 4846 ) -> str | None: 4847 return format_time( 4848 self.sql(expression, "format"), 4849 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4850 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4851 ) 4852 4853 def expressions( 4854 self, 4855 expression: exp.Expr | None = None, 4856 key: str | None = None, 4857 sqls: t.Collection[str | exp.Expr] | None = None, 4858 flat: bool = False, 4859 indent: bool = True, 4860 skip_first: bool = False, 4861 skip_last: bool = False, 4862 sep: str = ", ", 4863 prefix: str = "", 4864 dynamic: bool = False, 4865 new_line: bool = False, 4866 ) -> str: 4867 expressions = expression.args.get(key or "expressions") if expression else sqls 4868 4869 if not expressions: 4870 return "" 4871 4872 if flat: 4873 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4874 4875 num_sqls = len(expressions) 4876 result_sqls = [] 4877 4878 for i, e in enumerate(expressions): 4879 sql = self.sql(e, comment=False) 4880 if not sql: 4881 continue 4882 4883 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4884 4885 if self.pretty: 4886 if self.leading_comma: 4887 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4888 else: 4889 result_sqls.append( 4890 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4891 ) 4892 else: 4893 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4894 4895 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4896 if new_line: 4897 result_sqls.insert(0, "") 4898 result_sqls.append("") 4899 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4900 else: 4901 result_sql = "".join(result_sqls) 4902 4903 return ( 4904 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4905 if indent 4906 else result_sql 4907 ) 4908 4909 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4910 flat = flat or isinstance(expression.parent, exp.Properties) 4911 expressions_sql = self.expressions(expression, flat=flat) 4912 if flat: 4913 return f"{op} {expressions_sql}" 4914 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4915 4916 def naked_property(self, expression: exp.Property) -> str: 4917 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4918 if not property_name: 4919 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4920 return f"{property_name} {self.sql(expression, 'this')}" 4921 4922 def tag_sql(self, expression: exp.Tag) -> str: 4923 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4924 4925 def token_sql(self, token_type: TokenType) -> str: 4926 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4927 4928 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4929 this = self.sql(expression, "this") 4930 expressions = self.no_identify(self.expressions, expression) 4931 expressions = ( 4932 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4933 ) 4934 return f"{this}{expressions}" if expressions.strip() != "" else this 4935 4936 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4937 return self.expressions(expression, flat=True) 4938 4939 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4940 params = self.no_identify(self.expressions, expression, flat=True) 4941 body = self.sql(expression, "this") 4942 prefix = "TABLE " if expression.args.get("is_table") else "" 4943 return f"({params}) AS {prefix}{body}" 4944 4945 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4946 this = self.sql(expression, "this") 4947 expressions = self.expressions(expression, flat=True) 4948 return f"{this}({expressions})" 4949 4950 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4951 return self.binary(expression, "=>") 4952 4953 def when_sql(self, expression: exp.When) -> str: 4954 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4955 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4956 condition = self.sql(expression, "condition") 4957 condition = f" AND {condition}" if condition else "" 4958 4959 then_expression = expression.args.get("then") 4960 if isinstance(then_expression, exp.Insert): 4961 this = self.sql(then_expression, "this") 4962 this = f"INSERT {this}" if this else "INSERT" 4963 then = self.sql(then_expression, "expression") 4964 then = f"{this} VALUES {then}" if then else this 4965 elif isinstance(then_expression, exp.Update): 4966 if isinstance(then_expression.args.get("expressions"), exp.Star): 4967 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4968 else: 4969 expressions_sql = self.expressions(then_expression) 4970 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4971 else: 4972 then = self.sql(then_expression) 4973 4974 if isinstance(then_expression, (exp.Insert, exp.Update)): 4975 where = self.sql(then_expression, "where") 4976 if where and not self.SUPPORTS_MERGE_WHERE: 4977 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4978 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4979 where = "" 4980 then = f"{then}{where}" 4981 return f"WHEN {matched}{source}{condition} THEN {then}" 4982 4983 def whens_sql(self, expression: exp.Whens) -> str: 4984 return self.expressions(expression, sep=" ", indent=False) 4985 4986 def merge_sql(self, expression: exp.Merge) -> str: 4987 table = expression.this 4988 table_alias = "" 4989 4990 hints = table.args.get("hints") 4991 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4992 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4993 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4994 4995 this = self.sql(table) 4996 using = f"USING {self.sql(expression, 'using')}" 4997 whens = self.sql(expression, "whens") 4998 4999 on = self.sql(expression, "on") 5000 on = f"ON {on}" if on else "" 5001 5002 if not on: 5003 on = self.expressions(expression, key="using_cond") 5004 on = f"USING ({on})" if on else "" 5005 5006 returning = self.sql(expression, "returning") 5007 if returning: 5008 whens = f"{whens}{returning}" 5009 5010 sep = self.sep() 5011 5012 return self.prepend_ctes( 5013 expression, 5014 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5015 ) 5016 5017 @unsupported_args("format") 5018 def tochar_sql(self, expression: exp.ToChar) -> str: 5019 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 5020 5021 @unsupported_args("default") 5022 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5023 if not self.SUPPORTS_TO_NUMBER: 5024 self.unsupported("Unsupported TO_NUMBER function") 5025 return self._tonumber_cast_sql(expression) 5026 5027 fmt = expression.args.get("format") 5028 if not fmt: 5029 self.unsupported("Conversion format is required for TO_NUMBER") 5030 return self._tonumber_cast_sql(expression) 5031 5032 return self.func("TO_NUMBER", expression.this, fmt) 5033 5034 def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str: 5035 if expression.args.get("safe"): 5036 return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE"))) 5037 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 5038 5039 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5040 this = self.sql(expression, "this") 5041 kind = self.sql(expression, "kind") 5042 settings_sql = self.expressions(expression, key="settings", sep=" ") 5043 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5044 return f"{this}({kind}{args})" 5045 5046 def dictrange_sql(self, expression: exp.DictRange) -> str: 5047 this = self.sql(expression, "this") 5048 max = self.sql(expression, "max") 5049 min = self.sql(expression, "min") 5050 return f"{this}(MIN {min} MAX {max})" 5051 5052 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 5053 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 5054 5055 def aggregatekeyproperty_sql(self, expression: exp.AggregateKeyProperty) -> str: 5056 return f"AGGREGATE KEY ({self.expressions(expression, flat=True)})" 5057 5058 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 5059 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 5060 5061 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 5062 def uniquekeyproperty_sql( 5063 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 5064 ) -> str: 5065 return f"{prefix} ({self.expressions(expression, flat=True)})" 5066 5067 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 5068 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5069 expressions = self.expressions(expression, flat=True) 5070 expressions = f" {self.wrap(expressions)}" if expressions else "" 5071 buckets = self.sql(expression, "buckets") 5072 kind = self.sql(expression, "kind") 5073 buckets = f" BUCKETS {buckets}" if buckets else "" 5074 order = self.sql(expression, "order") 5075 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 5076 5077 def oncluster_sql(self, expression: exp.OnCluster) -> str: 5078 return "" 5079 5080 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5081 expressions = self.expressions(expression, key="expressions", flat=True) 5082 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5083 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5084 buckets = self.sql(expression, "buckets") 5085 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 5086 5087 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5088 this = self.sql(expression, "this") 5089 having = self.sql(expression, "having") 5090 5091 if having: 5092 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5093 5094 return self.func("ANY_VALUE", this) 5095 5096 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5097 transform = self.func("TRANSFORM", *expression.expressions) 5098 row_format_before = self.sql(expression, "row_format_before") 5099 row_format_before = f" {row_format_before}" if row_format_before else "" 5100 record_writer = self.sql(expression, "record_writer") 5101 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5102 using = f" USING {self.sql(expression, 'command_script')}" 5103 schema = self.sql(expression, "schema") 5104 schema = f" AS {schema}" if schema else "" 5105 row_format_after = self.sql(expression, "row_format_after") 5106 row_format_after = f" {row_format_after}" if row_format_after else "" 5107 record_reader = self.sql(expression, "record_reader") 5108 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5109 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5110 5111 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5112 key_block_size = self.sql(expression, "key_block_size") 5113 if key_block_size: 5114 return f"KEY_BLOCK_SIZE = {key_block_size}" 5115 5116 using = self.sql(expression, "using") 5117 if using: 5118 return f"USING {using}" 5119 5120 parser = self.sql(expression, "parser") 5121 if parser: 5122 return f"WITH PARSER {parser}" 5123 5124 comment = self.sql(expression, "comment") 5125 if comment: 5126 return f"COMMENT {comment}" 5127 5128 visible = expression.args.get("visible") 5129 if visible is not None: 5130 return "VISIBLE" if visible else "INVISIBLE" 5131 5132 engine_attr = self.sql(expression, "engine_attr") 5133 if engine_attr: 5134 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5135 5136 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5137 if secondary_engine_attr: 5138 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5139 5140 self.unsupported("Unsupported index constraint option.") 5141 return "" 5142 5143 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5144 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5145 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5146 5147 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5148 kind = self.sql(expression, "kind") 5149 kind = f"{kind} INDEX" if kind else "INDEX" 5150 this = self.sql(expression, "this") 5151 this = f" {this}" if this else "" 5152 index_type = self.sql(expression, "index_type") 5153 index_type = f" USING {index_type}" if index_type else "" 5154 expressions = self.expressions(expression, flat=True) 5155 expressions = f" ({expressions})" if expressions else "" 5156 options = self.expressions(expression, key="options", sep=" ") 5157 options = f" {options}" if options else "" 5158 return f"{kind}{this}{index_type}{expressions}{options}" 5159 5160 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5161 if self.NVL2_SUPPORTED: 5162 return self.function_fallback_sql(expression) 5163 5164 case = exp.Case().when( 5165 expression.this.is_(exp.null()).not_(copy=False), 5166 expression.args["true"], 5167 copy=False, 5168 ) 5169 else_cond = expression.args.get("false") 5170 if else_cond: 5171 case.else_(else_cond, copy=False) 5172 5173 return self.sql(case) 5174 5175 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5176 if expression.args.get("from_first") is False: 5177 self.unsupported("NTH_VALUE FROM LAST is not supported") 5178 5179 return self.function_fallback_sql(expression) 5180 5181 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5182 this = self.sql(expression, "this") 5183 expr = self.sql(expression, "expression") 5184 position = self.sql(expression, "position") 5185 position = f", {position}" if position else "" 5186 iterator = self.sql(expression, "iterator") 5187 condition = self.sql(expression, "condition") 5188 condition = f" IF {condition}" if condition else "" 5189 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5190 5191 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5192 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5193 5194 def opclass_sql(self, expression: exp.Opclass) -> str: 5195 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5196 5197 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5198 model = self.sql(expression, "this") 5199 model = f"MODEL {model}" 5200 expr = expression.expression 5201 if expr: 5202 expr_sql = self.sql(expression, "expression") 5203 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5204 else: 5205 expr_sql = None 5206 5207 parameters = self.sql(expression, "params_struct") or None 5208 5209 return self.func(name, model, expr_sql, parameters) 5210 5211 def predict_sql(self, expression: exp.Predict) -> str: 5212 return self._ml_sql(expression, "PREDICT") 5213 5214 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5215 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5216 return self._ml_sql(expression, name) 5217 5218 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5219 return self._ml_sql(expression, "GENERATE_TEXT") 5220 5221 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5222 return self._ml_sql(expression, "GENERATE_TABLE") 5223 5224 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5225 return self._ml_sql(expression, "GENERATE_BOOL") 5226 5227 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5228 return self._ml_sql(expression, "GENERATE_INT") 5229 5230 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5231 return self._ml_sql(expression, "GENERATE_DOUBLE") 5232 5233 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5234 return self._ml_sql(expression, "TRANSLATE") 5235 5236 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5237 return self._ml_sql(expression, "FORECAST") 5238 5239 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5240 this_sql = self.sql(expression, "this") 5241 if isinstance(expression.this, exp.Table): 5242 this_sql = f"TABLE {this_sql}" 5243 5244 return self.func( 5245 "FORECAST", 5246 this_sql, 5247 expression.args.get("data_col"), 5248 expression.args.get("timestamp_col"), 5249 expression.args.get("model"), 5250 expression.args.get("id_cols"), 5251 expression.args.get("horizon"), 5252 expression.args.get("forecast_end_timestamp"), 5253 expression.args.get("confidence_level"), 5254 expression.args.get("output_historical_time_series"), 5255 expression.args.get("context_window"), 5256 ) 5257 5258 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5259 this_sql = self.sql(expression, "this") 5260 if isinstance(expression.this, exp.Table): 5261 this_sql = f"TABLE {this_sql}" 5262 5263 return self.func( 5264 "FEATURES_AT_TIME", 5265 this_sql, 5266 expression.args.get("time"), 5267 expression.args.get("num_rows"), 5268 expression.args.get("ignore_feature_nulls"), 5269 ) 5270 5271 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5272 this_sql = self.sql(expression, "this") 5273 if isinstance(expression.this, exp.Table): 5274 this_sql = f"TABLE {this_sql}" 5275 5276 query_table = self.sql(expression, "query_table") 5277 if isinstance(expression.args["query_table"], exp.Table): 5278 query_table = f"TABLE {query_table}" 5279 5280 return self.func( 5281 "VECTOR_SEARCH", 5282 this_sql, 5283 expression.args.get("column_to_search"), 5284 query_table, 5285 expression.args.get("query_column_to_search"), 5286 expression.args.get("top_k"), 5287 expression.args.get("distance_type"), 5288 expression.args.get("options"), 5289 ) 5290 5291 def forin_sql(self, expression: exp.ForIn) -> str: 5292 this = self.sql(expression, "this") 5293 expression_sql = self.sql(expression, "expression") 5294 return f"FOR {this} DO {expression_sql}" 5295 5296 @unsupported_args("partition_start", "partition_end", "force", "mode") 5297 def refresh_sql(self, expression: exp.Refresh) -> str: 5298 this = self.sql(expression, "this") 5299 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5300 return f"REFRESH {kind}{this}" 5301 5302 def toarray_sql(self, expression: exp.ToArray) -> str: 5303 arg = expression.this 5304 if not arg.type: 5305 import sqlglot.optimizer.annotate_types 5306 5307 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5308 5309 if arg.is_type(exp.DType.ARRAY): 5310 return self.sql(arg) 5311 5312 cond_for_null = arg.is_(exp.null()) 5313 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5314 5315 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5316 this = expression.this 5317 time_format = self.format_time(expression) 5318 5319 if time_format: 5320 return self.sql( 5321 exp.cast( 5322 exp.StrToTime(this=this, format=expression.args["format"]), 5323 exp.DType.TIME, 5324 ) 5325 ) 5326 5327 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5328 return self.sql(this) 5329 5330 return self.sql(exp.cast(this, exp.DType.TIME)) 5331 5332 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5333 this = expression.this 5334 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5335 return self.sql(this) 5336 5337 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5338 5339 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5340 this = expression.this 5341 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5342 return self.sql(this) 5343 5344 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5345 5346 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5347 this = expression.this 5348 time_format = self.format_time(expression) 5349 safe = expression.args.get("safe") 5350 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5351 return self.sql( 5352 exp.cast( 5353 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5354 exp.DType.DATE, 5355 ) 5356 ) 5357 5358 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5359 return self.sql(this) 5360 5361 if safe: 5362 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5363 5364 return self.sql(exp.cast(this, exp.DType.DATE)) 5365 5366 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5367 return self.sql( 5368 exp.func( 5369 "DATEDIFF", 5370 expression.this, 5371 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5372 "day", 5373 ) 5374 ) 5375 5376 def lastday_sql(self, expression: exp.LastDay) -> str: 5377 if self.LAST_DAY_SUPPORTS_DATE_PART: 5378 return self.function_fallback_sql(expression) 5379 5380 unit = expression.args.get("unit") 5381 if unit and unit.name.upper() != "MONTH": 5382 self.unsupported("Date parts are not supported in LAST_DAY.") 5383 5384 return self.func("LAST_DAY", expression.this) 5385 5386 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5387 import sqlglot.dialects.dialect 5388 5389 return self.func( 5390 "DATE_ADD", 5391 expression.this, 5392 expression.expression, 5393 sqlglot.dialects.dialect.unit_to_str(expression), 5394 ) 5395 5396 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5397 this = expression.this 5398 position = expression.args["position"] 5399 offset = index_offset - (expression.args.get("offset") or 0) 5400 5401 if offset: 5402 if position.is_int: 5403 value = position.to_py() 5404 if value >= 0: 5405 position = exp.Literal.number(value + offset) 5406 elif offset < 0 and value == -1: 5407 # 1-based -1 appends, which a 0-based position can only express as the size 5408 position = exp.ArraySize(this=this.copy()) 5409 else: 5410 # Negative positions count from the end, so they shift in the opposite 5411 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5412 position = exp.Literal.number(value - offset) 5413 else: 5414 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5415 5416 return self.func("ARRAY_INSERT", this, position, expression.expression) 5417 5418 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5419 if self.CAN_IMPLEMENT_ARRAY_ANY: 5420 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5421 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5422 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5423 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5424 5425 import sqlglot.dialects.dialect 5426 5427 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5428 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5429 self.unsupported("ARRAY_ANY is unsupported") 5430 5431 return self.function_fallback_sql(expression) 5432 5433 def struct_sql(self, expression: exp.Struct) -> str: 5434 expression.set( 5435 "expressions", 5436 [ 5437 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5438 if isinstance(e, exp.PropertyEQ) 5439 else e 5440 for e in expression.expressions 5441 ], 5442 ) 5443 5444 return self.function_fallback_sql(expression) 5445 5446 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5447 low = self.sql(expression, "this") 5448 high = self.sql(expression, "expression") 5449 5450 return f"{low} TO {high}" 5451 5452 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5453 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5454 tables = f" {self.expressions(expression)}" 5455 5456 exists = " IF EXISTS" if expression.args.get("exists") else "" 5457 5458 on_cluster = self.sql(expression, "cluster") 5459 on_cluster = f" {on_cluster}" if on_cluster else "" 5460 5461 identity = self.sql(expression, "identity") 5462 identity = f" {identity} IDENTITY" if identity else "" 5463 5464 option = self.sql(expression, "option") 5465 option = f" {option}" if option else "" 5466 5467 partition = self.sql(expression, "partition") 5468 partition = f" {partition}" if partition else "" 5469 5470 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5471 5472 # This transpiles T-SQL's CONVERT function 5473 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5474 def convert_sql(self, expression: exp.Convert) -> str: 5475 to = expression.this 5476 value = expression.expression 5477 style = expression.args.get("style") 5478 safe = expression.args.get("safe") 5479 strict = expression.args.get("strict") 5480 5481 if not to or not value: 5482 return "" 5483 5484 # Retrieve length of datatype and override to default if not specified 5485 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5486 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5487 5488 transformed: exp.Expr | None = None 5489 cast = exp.Cast if strict else exp.TryCast 5490 5491 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5492 if isinstance(style, exp.Literal) and style.is_int: 5493 import sqlglot.dialects.tsql 5494 5495 style_value = style.name 5496 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5497 if not converted_style: 5498 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5499 5500 fmt = exp.Literal.string(converted_style) 5501 5502 if to.this == exp.DType.DATE: 5503 transformed = exp.StrToDate(this=value, format=fmt) 5504 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5505 transformed = exp.StrToTime(this=value, format=fmt) 5506 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5507 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5508 elif to.this == exp.DType.TEXT: 5509 transformed = exp.TimeToStr(this=value, format=fmt) 5510 5511 if not transformed: 5512 transformed = cast(this=value, to=to, safe=safe) 5513 5514 return self.sql(transformed) 5515 5516 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5517 this = expression.this 5518 if isinstance(this, exp.JSONPathWildcard): 5519 this = self.json_path_part(this) 5520 return f".{this}" if this else "" 5521 5522 quoted = expression.args.get("quoted") 5523 if not ( 5524 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5525 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5526 return f".{this}" 5527 5528 this = self.json_path_part(this) 5529 5530 return ( 5531 f"[{this}]" 5532 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5533 else f".{this}" 5534 ) 5535 5536 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5537 this = self.json_path_part(expression.this) 5538 return f"[{this}]" if this else "" 5539 5540 def _simplify_unless_literal(self, expression: E) -> E: 5541 if not isinstance(expression, exp.Literal): 5542 import sqlglot.optimizer.simplify 5543 5544 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5545 5546 return expression 5547 5548 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5549 this = expression.this 5550 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5551 self.unsupported( 5552 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5553 ) 5554 return self.sql(this) 5555 5556 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5557 if self.IGNORE_NULLS_BEFORE_ORDER: 5558 from sqlglot.optimizer.scope import find_all_in_scope 5559 5560 # The first modifier here will be the one closest to the AggFunc's arg 5561 mods = sorted( 5562 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5563 key=lambda x: ( 5564 0 5565 if isinstance(x, exp.HavingMax) 5566 else (1 if isinstance(x, exp.Order) else 2) 5567 ), 5568 ) 5569 5570 if mods: 5571 mod = mods[0] 5572 this = expression.__class__(this=mod.this.copy()) 5573 this.meta["inline"] = True 5574 mod.this.replace(this) 5575 return self.sql(expression.this) 5576 5577 agg_func = expression.find(exp.AggFunc) 5578 5579 if agg_func: 5580 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5581 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5582 5583 return f"{self.sql(expression, 'this')} {text}" 5584 5585 def _replace_line_breaks(self, string: str) -> str: 5586 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5587 if self.pretty: 5588 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5589 return string 5590 5591 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5592 option = self.sql(expression, "this") 5593 5594 if expression.expressions: 5595 upper = option.upper() 5596 5597 # Snowflake FILE_FORMAT options are separated by whitespace 5598 sep = " " if upper == "FILE_FORMAT" else ", " 5599 5600 # Databricks copy/format options do not set their list of values with EQ 5601 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5602 values = self.expressions(expression, flat=True, sep=sep) 5603 return f"{option}{op}({values})" 5604 5605 value = self.sql(expression, "expression") 5606 5607 if not value: 5608 return option 5609 5610 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5611 5612 return f"{option}{op}{value}" 5613 5614 def credentials_sql(self, expression: exp.Credentials) -> str: 5615 cred_expr = expression.args.get("credentials") 5616 if isinstance(cred_expr, exp.Literal): 5617 # Redshift case: CREDENTIALS <string> 5618 credentials = self.sql(expression, "credentials") 5619 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5620 else: 5621 # Snowflake case: CREDENTIALS = (...) 5622 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5623 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5624 5625 storage = self.sql(expression, "storage") 5626 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5627 5628 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5629 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5630 5631 iam_role = self.sql(expression, "iam_role") 5632 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5633 5634 region = self.sql(expression, "region") 5635 region = f" REGION {region}" if region else "" 5636 5637 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5638 5639 def copy_sql(self, expression: exp.Copy) -> str: 5640 this = self.sql(expression, "this") 5641 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5642 5643 credentials = self.sql(expression, "credentials") 5644 credentials = self.seg(credentials) if credentials else "" 5645 files = self.expressions(expression, key="files", flat=True) 5646 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5647 5648 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5649 params = self.expressions( 5650 expression, 5651 key="params", 5652 sep=sep, 5653 new_line=True, 5654 skip_last=True, 5655 skip_first=True, 5656 indent=self.COPY_PARAMS_ARE_WRAPPED, 5657 ) 5658 5659 if params: 5660 if self.COPY_PARAMS_ARE_WRAPPED: 5661 params = f" WITH ({params})" 5662 elif not self.pretty and (files or credentials): 5663 params = f" {params}" 5664 5665 return f"COPY{this}{kind} {files}{credentials}{params}" 5666 5667 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5668 return "" 5669 5670 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5671 on_sql = "ON" if expression.args.get("on") else "OFF" 5672 filter_col: str | None = self.sql(expression, "filter_column") 5673 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5674 retention_period: str | None = self.sql(expression, "retention_period") 5675 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5676 5677 if filter_col or retention_period: 5678 on_sql = self.func("ON", filter_col, retention_period) 5679 5680 return f"DATA_DELETION={on_sql}" 5681 5682 def maskingpolicycolumnconstraint_sql( 5683 self, expression: exp.MaskingPolicyColumnConstraint 5684 ) -> str: 5685 this = self.sql(expression, "this") 5686 expressions = self.expressions(expression, flat=True) 5687 expressions = f" USING ({expressions})" if expressions else "" 5688 return f"MASKING POLICY {this}{expressions}" 5689 5690 def gapfill_sql(self, expression: exp.GapFill) -> str: 5691 this = self.sql(expression, "this") 5692 this = f"TABLE {this}" 5693 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5694 5695 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5696 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5697 5698 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5699 this = self.sql(expression, "this") 5700 expr = expression.expression 5701 5702 if isinstance(expr, exp.Func): 5703 # T-SQL's CLR functions are case sensitive 5704 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5705 else: 5706 expr = self.sql(expression, "expression") 5707 5708 return self.scope_resolution(expr, this) 5709 5710 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5711 if self.PARSE_JSON_NAME is None: 5712 return self.sql(expression.this) 5713 5714 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5715 5716 def rand_sql(self, expression: exp.Rand) -> str: 5717 lower = self.sql(expression, "lower") 5718 upper = self.sql(expression, "upper") 5719 5720 if lower and upper: 5721 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5722 return self.func("RAND", expression.this) 5723 5724 def changes_sql(self, expression: exp.Changes) -> str: 5725 information = self.sql(expression, "information") 5726 information = f"INFORMATION => {information}" 5727 at_before = self.sql(expression, "at_before") 5728 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5729 end = self.sql(expression, "end") 5730 end = f"{self.seg('')}{end}" if end else "" 5731 5732 return f"CHANGES ({information}){at_before}{end}" 5733 5734 def pad_sql(self, expression: exp.Pad) -> str: 5735 prefix = "L" if expression.args.get("is_left") else "R" 5736 5737 fill_pattern = self.sql(expression, "fill_pattern") or None 5738 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5739 fill_pattern = "' '" 5740 5741 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5742 5743 def summarize_sql(self, expression: exp.Summarize) -> str: 5744 table = " TABLE" if expression.args.get("table") else "" 5745 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5746 5747 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5748 generate_series = exp.GenerateSeries(**expression.args) 5749 5750 parent = expression.parent 5751 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5752 parent = parent.parent 5753 5754 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5755 return self.sql(exp.Unnest(expressions=[generate_series])) 5756 5757 if isinstance(parent, exp.Select): 5758 self.unsupported("GenerateSeries projection unnesting is not supported.") 5759 5760 return self.sql(generate_series) 5761 5762 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5763 if self.SUPPORTS_CONVERT_TIMEZONE: 5764 return self.function_fallback_sql(expression) 5765 5766 source_tz = expression.args.get("source_tz") 5767 target_tz = expression.args.get("target_tz") 5768 timestamp = expression.args.get("timestamp") 5769 5770 if source_tz and timestamp: 5771 timestamp = exp.AtTimeZone( 5772 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5773 ) 5774 5775 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5776 5777 return self.sql(expr) 5778 5779 def json_sql(self, expression: exp.JSON) -> str: 5780 this = self.sql(expression, "this") 5781 this = f" {this}" if this else "" 5782 5783 _with = expression.args.get("with_") 5784 5785 if _with is None: 5786 with_sql = "" 5787 elif not _with: 5788 with_sql = " WITHOUT" 5789 else: 5790 with_sql = " WITH" 5791 5792 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5793 5794 return f"JSON{this}{with_sql}{unique_sql}" 5795 5796 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5797 path = self.sql(expression, "path") 5798 returning = self.sql(expression, "returning") 5799 returning = f" RETURNING {returning}" if returning else "" 5800 5801 on_condition = self.sql(expression, "on_condition") 5802 on_condition = f" {on_condition}" if on_condition else "" 5803 5804 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5805 5806 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5807 regexp = " REGEXP" if expression.args.get("regexp") else "" 5808 return f"SKIP{regexp} {self.sql(expression.expression)}" 5809 5810 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5811 else_ = "ELSE " if expression.args.get("else_") else "" 5812 condition = self.sql(expression, "expression") 5813 condition = f"WHEN {condition} THEN " if condition else else_ 5814 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5815 return f"{condition}{insert}" 5816 5817 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5818 kind = self.sql(expression, "kind") 5819 source = self.sql(expression, "source") 5820 5821 if kind: 5822 expressions = self.seg(self.expressions(expression, sep=" ")) 5823 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5824 else: 5825 # If kind isn't present, we have Hive's FROM-first form: 5826 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5827 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5828 sql = f"FROM {source}{self.seg(inserts)}" 5829 5830 return self.prepend_ctes(expression, sql) 5831 5832 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5833 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5834 empty = expression.args.get("empty") 5835 empty = ( 5836 f"DEFAULT {empty} ON EMPTY" 5837 if isinstance(empty, exp.Expr) 5838 else self.sql(expression, "empty") 5839 ) 5840 5841 error = expression.args.get("error") 5842 error = ( 5843 f"DEFAULT {error} ON ERROR" 5844 if isinstance(error, exp.Expr) 5845 else self.sql(expression, "error") 5846 ) 5847 5848 if error and empty: 5849 error = ( 5850 f"{empty} {error}" 5851 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5852 else f"{error} {empty}" 5853 ) 5854 empty = "" 5855 5856 null = self.sql(expression, "null") 5857 5858 return f"{empty}{error}{null}" 5859 5860 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5861 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5862 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5863 5864 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5865 this = self.sql(expression, "this") 5866 path = self.sql(expression, "path") 5867 5868 passing = self.expressions(expression, "passing") 5869 passing = f" PASSING {passing}" if passing else "" 5870 5871 on_condition = self.sql(expression, "on_condition") 5872 on_condition = f" {on_condition}" if on_condition else "" 5873 5874 path = f"{path}{passing}{on_condition}" 5875 5876 return self.func("JSON_EXISTS", this, path) 5877 5878 def _add_arrayagg_null_filter( 5879 self, 5880 array_agg_sql: str, 5881 array_agg_expr: exp.ArrayAgg, 5882 column_expr: exp.Expr, 5883 ) -> str: 5884 """ 5885 Add NULL filter to ARRAY_AGG if dialect requires it. 5886 5887 Args: 5888 array_agg_sql: The generated ARRAY_AGG SQL string 5889 array_agg_expr: The ArrayAgg expression node 5890 column_expr: The column/expression to filter (before ORDER BY wrapping) 5891 5892 Returns: 5893 SQL string with FILTER clause added if needed 5894 """ 5895 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5896 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5897 if not ( 5898 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5899 ): 5900 return array_agg_sql 5901 5902 parent = array_agg_expr.parent 5903 if isinstance(parent, exp.Filter): 5904 parent_cond = parent.expression.this 5905 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5906 elif column_expr.find(exp.Column): 5907 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5908 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5909 this_sql = ( 5910 self.expressions(column_expr) 5911 if isinstance(column_expr, exp.Distinct) 5912 else self.sql(column_expr) 5913 ) 5914 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5915 5916 return array_agg_sql 5917 5918 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5919 array_agg = self.function_fallback_sql(expression) 5920 column_expr = expression.this 5921 if isinstance(column_expr, exp.Order): 5922 column_expr = column_expr.this 5923 5924 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5925 5926 def slice_sql(self, expression: exp.Slice) -> str: 5927 step = self.sql(expression, "step") 5928 end = self.sql(expression.expression) 5929 begin = self.sql(expression.this) 5930 5931 sql = f"{end}:{step}" if step else end 5932 return f"{begin}:{sql}" if sql else f"{begin}:" 5933 5934 def apply_sql(self, expression: exp.Apply) -> str: 5935 this = self.sql(expression, "this") 5936 expr = self.sql(expression, "expression") 5937 5938 return f"{this} APPLY({expr})" 5939 5940 def _grant_or_revoke_sql( 5941 self, 5942 expression: exp.Grant | exp.Revoke, 5943 keyword: str, 5944 preposition: str, 5945 grant_option_prefix: str = "", 5946 grant_option_suffix: str = "", 5947 ) -> str: 5948 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5949 5950 kind = self.sql(expression, "kind") 5951 kind = f" {kind}" if kind else "" 5952 5953 securable = self.sql(expression, "securable") 5954 securable = f" {securable}" if securable else "" 5955 5956 principals = self.expressions(expression, key="principals", flat=True) 5957 5958 if not expression.args.get("grant_option"): 5959 grant_option_prefix = grant_option_suffix = "" 5960 5961 # cascade for revoke only 5962 cascade = self.sql(expression, "cascade") 5963 cascade = f" {cascade}" if cascade else "" 5964 5965 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5966 5967 def grant_sql(self, expression: exp.Grant) -> str: 5968 return self._grant_or_revoke_sql( 5969 expression, 5970 keyword="GRANT", 5971 preposition="TO", 5972 grant_option_suffix=" WITH GRANT OPTION", 5973 ) 5974 5975 def revoke_sql(self, expression: exp.Revoke) -> str: 5976 return self._grant_or_revoke_sql( 5977 expression, 5978 keyword="REVOKE", 5979 preposition="FROM", 5980 grant_option_prefix="GRANT OPTION FOR ", 5981 ) 5982 5983 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5984 this = self.sql(expression, "this") 5985 columns = self.expressions(expression, flat=True) 5986 columns = f"({columns})" if columns else "" 5987 5988 return f"{this}{columns}" 5989 5990 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5991 this = self.sql(expression, "this") 5992 5993 kind = self.sql(expression, "kind") 5994 kind = f"{kind} " if kind else "" 5995 5996 return f"{kind}{this}" 5997 5998 def columns_sql(self, expression: exp.Columns) -> str: 5999 func = self.function_fallback_sql(expression) 6000 if expression.args.get("unpack"): 6001 func = f"*{func}" 6002 6003 return func 6004 6005 def overlay_sql(self, expression: exp.Overlay) -> str: 6006 this = self.sql(expression, "this") 6007 expr = self.sql(expression, "expression") 6008 from_sql = self.sql(expression, "from_") 6009 for_sql = self.sql(expression, "for_") 6010 for_sql = f" FOR {for_sql}" if for_sql else "" 6011 6012 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 6013 6014 @unsupported_args("format") 6015 def todouble_sql(self, expression: exp.ToDouble) -> str: 6016 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 6017 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 6018 6019 def string_sql(self, expression: exp.String) -> str: 6020 this = expression.this 6021 zone = expression.args.get("zone") 6022 6023 if zone: 6024 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6025 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6026 # set for source_tz to transpile the time conversion before the STRING cast 6027 this = exp.ConvertTimezone( 6028 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6029 ) 6030 6031 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 6032 6033 def median_sql(self, expression: exp.Median) -> str: 6034 if not self.SUPPORTS_MEDIAN: 6035 return self.sql( 6036 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 6037 ) 6038 6039 return self.function_fallback_sql(expression) 6040 6041 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6042 filler = self.sql(expression, "this") 6043 filler = f" {filler}" if filler else "" 6044 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6045 return f"TRUNCATE{filler} {with_count}" 6046 6047 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6048 if self.SUPPORTS_UNIX_SECONDS: 6049 return self.function_fallback_sql(expression) 6050 6051 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6052 6053 return self.sql( 6054 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6055 ) 6056 6057 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6058 dim = expression.expression 6059 6060 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6061 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6062 if not (dim.is_int and dim.name == "1"): 6063 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6064 dim = None 6065 6066 # If dimension is required but not specified, default initialize it 6067 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6068 dim = exp.Literal.number(1) 6069 6070 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 6071 6072 def attach_sql(self, expression: exp.Attach) -> str: 6073 this = self.sql(expression, "this") 6074 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6075 expressions = self.expressions(expression) 6076 expressions = f" ({expressions})" if expressions else "" 6077 6078 return f"ATTACH{exists_sql} {this}{expressions}" 6079 6080 def detach_sql(self, expression: exp.Detach) -> str: 6081 kind = self.sql(expression, "kind") 6082 kind = f" {kind}" if kind else "" 6083 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6084 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6085 exists = " IF EXISTS" if expression.args.get("exists") else "" 6086 if exists: 6087 kind = kind or " DATABASE" 6088 6089 this = self.sql(expression, "this") 6090 this = f" {this}" if this else "" 6091 cluster = self.sql(expression, "cluster") 6092 cluster = f" {cluster}" if cluster else "" 6093 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6094 sync = " SYNC" if expression.args.get("sync") else "" 6095 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 6096 6097 def attachoption_sql(self, expression: exp.AttachOption) -> str: 6098 this = self.sql(expression, "this") 6099 value = self.sql(expression, "expression") 6100 value = f" {value}" if value else "" 6101 return f"{this}{value}" 6102 6103 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 6104 return ( 6105 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 6106 ) 6107 6108 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6109 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6110 encode = f"{encode} {self.sql(expression, 'this')}" 6111 6112 properties = expression.args.get("properties") 6113 if properties: 6114 encode = f"{encode} {self.properties(properties)}" 6115 6116 return encode 6117 6118 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6119 this = self.sql(expression, "this") 6120 include = f"INCLUDE {this}" 6121 6122 column_def = self.sql(expression, "column_def") 6123 if column_def: 6124 include = f"{include} {column_def}" 6125 6126 alias = self.sql(expression, "alias") 6127 if alias: 6128 include = f"{include} AS {alias}" 6129 6130 return include 6131 6132 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6133 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6134 name = f"{prefix} {self.sql(expression, 'this')}" 6135 return self.func("XMLELEMENT", name, *expression.expressions) 6136 6137 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6138 this = self.sql(expression, "this") 6139 expr = self.sql(expression, "expression") 6140 expr = f"({expr})" if expr else "" 6141 return f"{this}{expr}" 6142 6143 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6144 partitions = self.expressions(expression, "partition_expressions") 6145 create = self.expressions(expression, "create_expressions") 6146 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6147 6148 def partitionbyrangepropertydynamic_sql( 6149 self, expression: exp.PartitionByRangePropertyDynamic 6150 ) -> str: 6151 start = self.sql(expression, "start") 6152 end = self.sql(expression, "end") 6153 6154 every = expression.args["every"] 6155 if isinstance(every, exp.Interval) and every.this.is_string: 6156 every.this.replace(exp.Literal.number(every.name)) 6157 6158 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6159 6160 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6161 name = self.sql(expression, "this") 6162 values = self.expressions(expression, flat=True) 6163 6164 return f"NAME {name} VALUE {values}" 6165 6166 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6167 kind = self.sql(expression, "kind") 6168 sample = self.sql(expression, "sample") 6169 return f"SAMPLE {sample} {kind}" 6170 6171 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6172 kind = self.sql(expression, "kind") 6173 option = self.sql(expression, "option") 6174 option = f" {option}" if option else "" 6175 this = self.sql(expression, "this") 6176 this = f" {this}" if this else "" 6177 columns = self.expressions(expression) 6178 columns = f" {columns}" if columns else "" 6179 return f"{kind}{option} STATISTICS{this}{columns}" 6180 6181 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6182 this = self.sql(expression, "this") 6183 columns = self.expressions(expression) 6184 inner_expression = self.sql(expression, "expression") 6185 inner_expression = f" {inner_expression}" if inner_expression else "" 6186 update_options = self.sql(expression, "update_options") 6187 update_options = f" {update_options} UPDATE" if update_options else "" 6188 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6189 6190 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6191 kind = self.sql(expression, "kind") 6192 kind = f" {kind}" if kind else "" 6193 return f"DELETE{kind} STATISTICS" 6194 6195 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6196 inner_expression = self.sql(expression, "expression") 6197 return f"LIST CHAINED ROWS{inner_expression}" 6198 6199 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6200 kind = self.sql(expression, "kind") 6201 this = self.sql(expression, "this") 6202 this = f" {this}" if this else "" 6203 inner_expression = self.sql(expression, "expression") 6204 return f"VALIDATE {kind}{this}{inner_expression}" 6205 6206 def analyze_sql(self, expression: exp.Analyze) -> str: 6207 options = self.expressions(expression, key="options", sep=" ") 6208 options = f" {options}" if options else "" 6209 kind = self.sql(expression, "kind") 6210 kind = f" {kind}" if kind else "" 6211 tables = self.expressions(expression, key="tables", flat=True) 6212 tables = f" {tables}" if tables else "" 6213 mode = self.sql(expression, "mode") 6214 mode = f" {mode}" if mode else "" 6215 properties = self.sql(expression, "properties") 6216 properties = f" {properties}" if properties else "" 6217 partition = self.sql(expression, "partition") 6218 partition = f" {partition}" if partition else "" 6219 inner_expression = self.sql(expression, "expression") 6220 inner_expression = f" {inner_expression}" if inner_expression else "" 6221 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6222 6223 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6224 this = self.sql(expression, "this") 6225 namespaces = self.expressions(expression, key="namespaces") 6226 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6227 passing = self.expressions(expression, key="passing") 6228 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6229 columns = self.expressions(expression, key="columns") 6230 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6231 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6232 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6233 6234 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6235 this = self.sql(expression, "this") 6236 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6237 6238 def export_sql(self, expression: exp.Export) -> str: 6239 this = self.sql(expression, "this") 6240 connection = self.sql(expression, "connection") 6241 connection = f"WITH CONNECTION {connection} " if connection else "" 6242 options = self.sql(expression, "options") 6243 return f"EXPORT DATA {connection}{options} AS {this}" 6244 6245 def declare_sql(self, expression: exp.Declare) -> str: 6246 replace = "OR REPLACE " if expression.args.get("replace") else "" 6247 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6248 6249 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6250 variables = self.expressions(expression, "this") 6251 default = self.sql(expression, "default") 6252 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6253 6254 kind = self.sql(expression, "kind") 6255 if isinstance(expression.args.get("kind"), exp.Schema): 6256 kind = f"TABLE {kind}" 6257 6258 kind = f" {kind}" if kind else "" 6259 6260 return f"{variables}{kind}{default}" 6261 6262 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6263 kind = self.sql(expression, "kind") 6264 this = self.sql(expression, "this") 6265 set = self.sql(expression, "expression") 6266 to = self.sql(expression, "to") 6267 to = f" TO {to}" if to else "" 6268 default = self.sql(expression, "default") 6269 default = f" DEFAULT {default}" if default else "" 6270 using = self.sql(expression, "using") 6271 using = f" USING {using}" if using else "" 6272 6273 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6274 6275 return f"{kind_sql} {this} SET {set}{to}{default}{using}" 6276 6277 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6278 params = self.expressions(expression, key="params", flat=True) 6279 return self.func(expression.name, *expression.expressions) + f"({params})" 6280 6281 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6282 return self.func(expression.name, *expression.expressions) 6283 6284 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6285 return self.anonymousaggfunc_sql(expression) 6286 6287 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6288 return self.parameterizedagg_sql(expression) 6289 6290 def show_sql(self, expression: exp.Show) -> str: 6291 self.unsupported("Unsupported SHOW statement") 6292 return "" 6293 6294 def install_sql(self, expression: exp.Install) -> str: 6295 self.unsupported("Unsupported INSTALL statement") 6296 return "" 6297 6298 def liststage_sql(self, expression: exp.ListStage) -> str: 6299 pattern = self.sql(expression, "pattern") 6300 pattern = f" PATTERN = {pattern}" if pattern else "" 6301 return f"LIST {self.sql(expression, 'this')}{pattern}" 6302 6303 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6304 # Snowflake GET/PUT statements: 6305 # PUT <file> <internalStage> <properties> 6306 # GET <internalStage> <file> <properties> 6307 props = expression.args.get("properties") 6308 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6309 this = self.sql(expression, "this") 6310 target = self.sql(expression, "target") 6311 6312 if isinstance(expression, exp.Put): 6313 return f"PUT {this} {target}{props_sql}" 6314 else: 6315 return f"GET {target} {this}{props_sql}" 6316 6317 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6318 this = self.sql(expression, "this") 6319 expr = self.sql(expression, "expression") 6320 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6321 return f"TRANSLATE({this} USING {expr}{with_error})" 6322 6323 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6324 if self.SUPPORTS_DECODE_CASE: 6325 return self.func("DECODE", *expression.expressions) 6326 6327 decode_expr, *expressions = expression.expressions 6328 6329 ifs = [] 6330 for search, result in zip(expressions[::2], expressions[1::2]): 6331 if isinstance(search, exp.Literal): 6332 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6333 elif isinstance(search, exp.Null): 6334 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6335 else: 6336 if isinstance(search, exp.Binary): 6337 search = exp.paren(search) 6338 6339 cond = exp.or_( 6340 decode_expr.eq(search), 6341 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6342 copy=False, 6343 ) 6344 ifs.append(exp.If(this=cond, true=result)) 6345 6346 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6347 return self.sql(case) 6348 6349 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6350 this = self.sql(expression, "this") 6351 this = self.seg(this, sep="") 6352 dimensions = self.expressions( 6353 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6354 ) 6355 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6356 metrics = self.expressions( 6357 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6358 ) 6359 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6360 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6361 facts = self.seg(f"FACTS {facts}") if facts else "" 6362 where = self.sql(expression, "where") 6363 where = self.seg(f"WHERE {where}") if where else "" 6364 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6365 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6366 6367 def getextract_sql(self, expression: exp.GetExtract) -> str: 6368 this = expression.this 6369 expr = expression.expression 6370 6371 if not this.type or not expression.type: 6372 import sqlglot.optimizer.annotate_types 6373 6374 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6375 6376 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6377 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6378 6379 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6380 6381 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6382 return self.sql( 6383 exp.DateAdd( 6384 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6385 expression=expression.this, 6386 unit=exp.var("DAY"), 6387 ) 6388 ) 6389 6390 def space_sql(self: Generator, expression: exp.Space) -> str: 6391 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6392 6393 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6394 return f"BUILD {self.sql(expression, 'this')}" 6395 6396 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6397 method = self.sql(expression, "method") 6398 kind = expression.args.get("kind") 6399 if not kind: 6400 return f"REFRESH {method}" 6401 6402 every = self.sql(expression, "every") 6403 unit = self.sql(expression, "unit") 6404 every = f" EVERY {every} {unit}" if every else "" 6405 starts = self.sql(expression, "starts") 6406 starts = f" STARTS {starts}" if starts else "" 6407 6408 return f"REFRESH {method} ON {kind}{every}{starts}" 6409 6410 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6411 self.unsupported("The model!attribute syntax is not supported") 6412 return "" 6413 6414 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6415 return self.func("DIRECTORY", expression.this) 6416 6417 def uuid_sql(self, expression: exp.Uuid) -> str: 6418 is_string = expression.args.get("is_string", False) 6419 uuid_func_sql = self.func("UUID") 6420 6421 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6422 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6423 6424 return uuid_func_sql 6425 6426 def initcap_sql(self, expression: exp.Initcap) -> str: 6427 delimiters = expression.expression 6428 6429 if delimiters: 6430 # do not generate delimiters arg if we are round-tripping from default delimiters 6431 if ( 6432 delimiters.is_string 6433 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6434 ): 6435 delimiters = None 6436 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6437 self.unsupported("INITCAP does not support custom delimiters") 6438 delimiters = None 6439 6440 return self.func("INITCAP", expression.this, delimiters) 6441 6442 def localtime_sql(self, expression: exp.Localtime) -> str: 6443 this = expression.this 6444 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6445 6446 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6447 this = expression.this 6448 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6449 6450 def weekstart_name(self, expression: exp.WeekStart) -> str: 6451 import sqlglot.dialects.dialect 6452 6453 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6454 this = expression.this.name.upper() 6455 6456 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6457 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6458 6459 if dow_from_week_start_day != dow_from_week_offset: 6460 self.unsupported( 6461 f"WEEK({this}) is not supported; falling back to the default week start day" 6462 ) 6463 6464 return "WEEK" 6465 6466 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6467 name = self.weekstart_name(expression) 6468 6469 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6470 if isinstance(expression.parent, exp.DateTrunc): 6471 return self.sql(exp.Literal.string(name)) 6472 6473 return name 6474 6475 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6476 this = self.expressions(expression) 6477 charset = self.sql(expression, "charset") 6478 using = f" USING {charset}" if charset else "" 6479 return self.func(name, this + using) 6480 6481 def weightstring_sql(self, expression: exp.WeightString) -> str: 6482 to = self.sql(expression, "to") 6483 to = f" AS {to}" if to else "" 6484 return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}") 6485 6486 def block_sql(self, expression: exp.Block) -> str: 6487 expressions = self.expressions(expression, sep="; ", flat=True) 6488 begin = "BEGIN " if expression.args.get("begin") else "" 6489 return f"{begin}{expressions}" if expressions else "" 6490 6491 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6492 self.unsupported("Unsupported Inline UDFs syntax") 6493 return "" 6494 6495 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6496 self.unsupported("Unsupported Stored Procedure syntax") 6497 return "" 6498 6499 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6500 self.unsupported("Unsupported If block syntax") 6501 return "" 6502 6503 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6504 self.unsupported("Unsupported Case statement syntax") 6505 return "" 6506 6507 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6508 self.unsupported("Unsupported While block syntax") 6509 return "" 6510 6511 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6512 self.unsupported("Unsupported Loop block syntax") 6513 return "" 6514 6515 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6516 self.unsupported("Unsupported Repeat block syntax") 6517 return "" 6518 6519 def leave_sql(self, expression: exp.Leave) -> str: 6520 self.unsupported("Unsupported Leave syntax") 6521 return "" 6522 6523 def iterate_sql(self, expression: exp.Iterate) -> str: 6524 self.unsupported("Unsupported Iterate syntax") 6525 return "" 6526 6527 def execute_sql(self, expression: exp.Execute) -> str: 6528 self.unsupported("Unsupported Execute syntax") 6529 return "" 6530 6531 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6532 self.unsupported("Unsupported Execute syntax") 6533 return "" 6534 6535 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6536 props = self.expressions(expression, sep=" ") 6537 return f"MODIFY {props}" 6538 6539 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6540 kind = expression.args.get("kind") 6541 return f"USING {kind} {self.sql(expression, 'this')}" 6542 6543 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6544 this = self.sql(expression, "this") 6545 to = self.sql(expression, "to") 6546 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)
923 def __init__( 924 self, 925 pretty: bool | int | None = None, 926 identify: str | bool = False, 927 normalize: bool = False, 928 pad: int = 2, 929 indent: int = 2, 930 normalize_functions: str | bool | None = None, 931 unsupported_level: ErrorLevel = ErrorLevel.WARN, 932 max_unsupported: int = 3, 933 leading_comma: bool = False, 934 max_text_width: int = 80, 935 comments: bool = True, 936 dialect: DialectType = None, 937 ): 938 import sqlglot 939 import sqlglot.dialects.dialect 940 941 self.pretty = pretty if pretty is not None else sqlglot.pretty 942 self.identify = identify 943 self.normalize = normalize 944 self.pad = pad 945 self._indent = indent 946 self.unsupported_level = unsupported_level 947 self.max_unsupported = max_unsupported 948 self.leading_comma = leading_comma 949 self.max_text_width = max_text_width 950 self.comments = comments 951 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 952 953 # This is both a Dialect property and a Generator argument, so we prioritize the latter 954 self.normalize_functions = ( 955 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 956 ) 957 958 self.unsupported_messages: list[str] = [] 959 self._escaped_quote_end: str = ( 960 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 961 ) 962 self._escaped_byte_quote_end: str = ( 963 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 964 if self.dialect.BYTE_END 965 else "" 966 ) 967 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 968 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 969 970 self._next_name = name_sequence("_t") 971 972 self._identifier_start = self.dialect.IDENTIFIER_START 973 self._identifier_end = self.dialect.IDENTIFIER_END 974 975 self._quote_json_path_key_using_brackets = True 976 977 cls = type(self) 978 dispatch = _DISPATCH_CACHE.get(cls) 979 if dispatch is None: 980 dispatch = _build_dispatch(cls) 981 _DISPATCH_CACHE[cls] = dispatch 982 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.JSONPathSubscript'>, <class 'sqlglot.expressions.query.JSONPathSelector'>, <class 'sqlglot.expressions.query.JSONPathSlice'>, <class 'sqlglot.expressions.query.JSONPathFilter'>, <class 'sqlglot.expressions.query.JSONPathScript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>, <class 'sqlglot.expressions.query.JSONPathRecursive'>, <class 'sqlglot.expressions.query.JSONPathWildcard'>, <class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathUnion'>}
TYPE_MAPPING: ClassVar =
{<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TYPE_PARAM_SETTINGS: ClassVar[dict[sqlglot.expressions.datatypes.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]] =
{}
TIME_PART_SINGULARS: ClassVar =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS: ClassVar =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function <lambda>>, 'qualify': <function <lambda>>}
PROPERTIES_LOCATION: ClassVar =
{<class 'sqlglot.expressions.properties.AggregateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusterProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PhysicalProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TableSpaceProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.ddl.Command'>, <class 'sqlglot.expressions.ddl.Create'>, <class 'sqlglot.expressions.ddl.Describe'>, <class 'sqlglot.expressions.dml.Delete'>, <class 'sqlglot.expressions.ddl.Drop'>, <class 'sqlglot.expressions.query.From'>, <class 'sqlglot.expressions.dml.Insert'>, <class 'sqlglot.expressions.query.Join'>, <class 'sqlglot.expressions.query.MultitableInserts'>, <class 'sqlglot.expressions.query.Order'>, <class 'sqlglot.expressions.query.Group'>, <class 'sqlglot.expressions.query.Having'>, <class 'sqlglot.expressions.query.Select'>, <class 'sqlglot.expressions.query.SetOperation'>, <class 'sqlglot.expressions.dml.Update'>, <class 'sqlglot.expressions.query.Where'>, <class 'sqlglot.expressions.query.With'>)
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar =
{<DType.VARCHAR: 'VARCHAR'>, <DType.NVARCHAR: 'NVARCHAR'>, <DType.NCHAR: 'NCHAR'>, <DType.CHAR: 'CHAR'>}
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'>)
984 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 985 """ 986 Generates the SQL string corresponding to the given syntax tree. 987 988 Args: 989 expression: The syntax tree. 990 copy: Whether to copy the expression. The generator performs mutations so 991 it is safer to copy. 992 993 Returns: 994 The SQL string corresponding to `expression`. 995 """ 996 if copy: 997 expression = expression.copy() 998 999 expression = self.preprocess(expression) 1000 1001 self.unsupported_messages = [] 1002 sql = self.sql(expression).strip() 1003 1004 if self.pretty: 1005 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1006 1007 if self.unsupported_level == ErrorLevel.IGNORE: 1008 return sql 1009 1010 if self.unsupported_level == ErrorLevel.WARN: 1011 for msg in self.unsupported_messages: 1012 logger.warning(msg) 1013 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1014 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1015 1016 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.
1018 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1019 """Apply generic preprocessing transformations to a given expression.""" 1020 expression = self._move_ctes_to_top_level(expression) 1021 1022 if self.ENSURE_BOOLS: 1023 import sqlglot.transforms 1024 1025 expression = sqlglot.transforms.ensure_bools(expression) 1026 1027 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
1051 def sanitize_comment(self, comment: str) -> str: 1052 comment = " " + comment if comment[0].strip() else comment 1053 comment = comment + " " if comment[-1].strip() else comment 1054 1055 # Escape block comment markers to prevent premature closure or unintended nesting. 1056 # This is necessary because single-line comments (--) are converted to block comments 1057 # (/* */) on output, and any */ in the original text would close the comment early. 1058 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1059 1060 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
1062 def maybe_comment( 1063 self, 1064 sql: str, 1065 expression: exp.Expr | None = None, 1066 comments: list[str] | None = None, 1067 separated: bool = False, 1068 ) -> str: 1069 comments = ( 1070 ((expression and expression.comments) if comments is None else comments) # type: ignore 1071 if self.comments 1072 else None 1073 ) 1074 1075 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1076 return sql 1077 1078 comments_list = [ 1079 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1080 for comment in comments 1081 if comment 1082 ] 1083 1084 if not comments_list: 1085 return sql 1086 1087 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1088 comments_sql = self.sep().join(comments_list) 1089 return ( 1090 f"{self.sep()}{comments_sql}{sql}" 1091 if not sql or sql[0].isspace() 1092 else f"{comments_sql}{self.sep()}{sql}" 1093 ) 1094 1095 return f"{sql} {' '.join(comments_list)}"
1097 def wrap(self, expression: exp.Expr | str) -> str: 1098 this_sql = ( 1099 self.sql(expression) 1100 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1101 else self.sql(expression, "this") 1102 ) 1103 if not this_sql: 1104 return "()" 1105 1106 this_sql = self.indent(this_sql, level=1, pad=0) 1107 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:
1123 def indent( 1124 self, 1125 sql: str, 1126 level: int = 0, 1127 pad: int | None = None, 1128 skip_first: bool = False, 1129 skip_last: bool = False, 1130 ) -> str: 1131 if not self.pretty or not sql: 1132 return sql 1133 1134 pad = self.pad if pad is None else pad 1135 lines = sql.split("\n") 1136 1137 return "\n".join( 1138 ( 1139 line 1140 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1141 else f"{' ' * (level * self._indent + pad)}{line}" 1142 ) 1143 for i, line in enumerate(lines) 1144 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1146 def sql( 1147 self, 1148 expression: str | exp.Expr | None, 1149 key: str | None = None, 1150 comment: bool = True, 1151 ) -> str: 1152 if not expression: 1153 return "" 1154 1155 if isinstance(expression, str): 1156 return expression 1157 1158 if key: 1159 value = expression.args.get(key) 1160 if value: 1161 return self.sql(value) 1162 return "" 1163 1164 handler = self._dispatch.get(expression.__class__) 1165 1166 if handler: 1167 sql = handler(self, expression) 1168 elif isinstance(expression, exp.Func): 1169 sql = self.function_fallback_sql(expression) 1170 elif isinstance(expression, exp.Property): 1171 sql = self.property_sql(expression) 1172 else: 1173 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1174 1175 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1182 def cache_sql(self, expression: exp.Cache) -> str: 1183 lazy = " LAZY" if expression.args.get("lazy") else "" 1184 table = self.sql(expression, "this") 1185 options = expression.args.get("options") 1186 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1187 sql = self.sql(expression, "expression") 1188 sql = f" AS{self.sep()}{sql}" if sql else "" 1189 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1190 return self.prepend_ctes(expression, sql)
1196 def column_parts(self, expression: exp.Column) -> str: 1197 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1198 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1199 return self.sql(expression, "this") 1200 1201 return ".".join( 1202 self.sql(part) 1203 for part in ( 1204 expression.args.get("catalog"), 1205 expression.args.get("db"), 1206 expression.args.get("table"), 1207 expression.args.get("this"), 1208 ) 1209 if part 1210 )
1212 def column_sql(self, expression: exp.Column) -> str: 1213 join_mark = " (+)" if expression.args.get("join_mark") else "" 1214 1215 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1216 join_mark = "" 1217 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1218 1219 return f"{self.column_parts(expression)}{join_mark}"
1230 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1231 column = self.sql(expression, "this") 1232 kind = self.sql(expression, "kind") 1233 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1234 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1235 kind = f"{sep}{kind}" if kind else "" 1236 constraints = f" {constraints}" if constraints else "" 1237 position = self.sql(expression, "position") 1238 position = f" {position}" if position else "" 1239 1240 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1241 kind = "" 1242 1243 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:
1250 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1251 this = self.sql(expression, "this") 1252 if expression.args.get("not_null"): 1253 persisted = " PERSISTED NOT NULL" 1254 elif expression.args.get("persisted"): 1255 persisted = " PERSISTED" 1256 else: 1257 persisted = "" 1258 1259 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:
1272 def generatedasidentitycolumnconstraint_sql( 1273 self, expression: exp.GeneratedAsIdentityColumnConstraint 1274 ) -> str: 1275 this = "" 1276 if expression.this is not None: 1277 on_null = " ON NULL" if expression.args.get("on_null") else "" 1278 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1279 1280 start = expression.args.get("start") 1281 start = f"START WITH {start}" if start else "" 1282 increment = expression.args.get("increment") 1283 increment = f" INCREMENT BY {increment}" if increment else "" 1284 minvalue = expression.args.get("minvalue") 1285 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1286 maxvalue = expression.args.get("maxvalue") 1287 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1288 cycle = expression.args.get("cycle") 1289 cycle_sql = "" 1290 1291 if cycle is not None: 1292 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1293 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1294 1295 sequence_opts = "" 1296 if start or increment or cycle_sql: 1297 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1298 sequence_opts = f" ({sequence_opts.strip()})" 1299 1300 expr = self.sql(expression, "expression") 1301 expr = f"({expr})" if expr else "IDENTITY" 1302 1303 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1305 def generatedasrowcolumnconstraint_sql( 1306 self, expression: exp.GeneratedAsRowColumnConstraint 1307 ) -> str: 1308 start = "START" if expression.args.get("start") else "END" 1309 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1310 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:
1320 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1321 desc = expression.args.get("desc") 1322 if desc is not None: 1323 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1324 options = self.expressions(expression, key="options", flat=True, sep=" ") 1325 options = f" {options}" if options else "" 1326 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1328 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1329 this = self.sql(expression, "this") 1330 this = f" {this}" if this else "" 1331 index_type = expression.args.get("index_type") 1332 index_type = f" USING {index_type}" if index_type else "" 1333 on_conflict = self.sql(expression, "on_conflict") 1334 on_conflict = f" {on_conflict}" if on_conflict else "" 1335 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1336 options = self.expressions(expression, key="options", flat=True, sep=" ") 1337 options = f" {options}" if options else "" 1338 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1340 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1341 input_ = expression.args.get("input_") 1342 output = expression.args.get("output") 1343 variadic = expression.args.get("variadic") 1344 1345 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1346 if variadic: 1347 return "VARIADIC" 1348 1349 if input_ and output: 1350 return f"IN{self.INOUT_SEPARATOR}OUT" 1351 if input_: 1352 return "IN" 1353 if output: 1354 return "OUT" 1355 1356 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1361 def create_sql(self, expression: exp.Create) -> str: 1362 kind = self.sql(expression, "kind") 1363 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1364 1365 properties = expression.args.get("properties") 1366 1367 if ( 1368 kind == "TRIGGER" 1369 and properties 1370 and properties.expressions 1371 and isinstance(properties.expressions[0], exp.TriggerProperties) 1372 and properties.expressions[0].args.get("constraint") 1373 ): 1374 kind = f"CONSTRAINT {kind}" 1375 1376 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1377 1378 this = self.createable_sql(expression, properties_locs) 1379 1380 properties_sql = "" 1381 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1382 exp.Properties.Location.POST_WITH 1383 ): 1384 props_ast = exp.Properties( 1385 expressions=[ 1386 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1387 *properties_locs[exp.Properties.Location.POST_WITH], 1388 ] 1389 ) 1390 props_ast.parent = expression 1391 properties_sql = self.sql(props_ast) 1392 1393 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1394 properties_sql = self.sep() + properties_sql 1395 elif not self.pretty: 1396 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1397 properties_sql = f" {properties_sql}" 1398 1399 begin = " BEGIN" if expression.args.get("begin") else "" 1400 1401 expression_sql = self.sql(expression, "expression") 1402 if expression_sql: 1403 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1404 1405 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1406 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1407 ): 1408 postalias_props_sql = "" 1409 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1410 postalias_props_sql = self.properties( 1411 exp.Properties( 1412 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1413 ), 1414 wrapped=False, 1415 ) 1416 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1417 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1418 1419 postindex_props_sql = "" 1420 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1421 postindex_props_sql = self.properties( 1422 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1423 wrapped=False, 1424 prefix=" ", 1425 ) 1426 1427 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1428 indexes = f" {indexes}" if indexes else "" 1429 index_sql = indexes + postindex_props_sql 1430 1431 replace = " OR REPLACE" if expression.args.get("replace") else "" 1432 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1433 unique = " UNIQUE" if expression.args.get("unique") else "" 1434 1435 clustered = expression.args.get("clustered") 1436 if clustered is None: 1437 clustered_sql = "" 1438 elif clustered: 1439 clustered_sql = " CLUSTERED COLUMNSTORE" 1440 else: 1441 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1442 1443 postcreate_props_sql = "" 1444 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1445 postcreate_props_sql = self.properties( 1446 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1447 sep=" ", 1448 prefix=" ", 1449 wrapped=False, 1450 ) 1451 1452 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1453 1454 postexpression_props_sql = "" 1455 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1456 postexpression_props_sql = self.properties( 1457 exp.Properties( 1458 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1459 ), 1460 sep=" ", 1461 prefix=" ", 1462 wrapped=False, 1463 ) 1464 1465 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1466 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1467 no_schema_binding = ( 1468 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1469 ) 1470 1471 clone = self.sql(expression, "clone") 1472 clone = f" {clone}" if clone else "" 1473 1474 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1475 properties_expression = f"{expression_sql}{properties_sql}" 1476 else: 1477 properties_expression = f"{properties_sql}{expression_sql}" 1478 1479 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1480 return self.prepend_ctes(expression, expression_sql)
1482 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1483 start = self.sql(expression, "start") 1484 start = f"START WITH {start}" if start else "" 1485 increment = self.sql(expression, "increment") 1486 increment = f" INCREMENT BY {increment}" if increment else "" 1487 minvalue = self.sql(expression, "minvalue") 1488 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1489 maxvalue = self.sql(expression, "maxvalue") 1490 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1491 owned = self.sql(expression, "owned") 1492 owned = f" OWNED BY {owned}" if owned else "" 1493 1494 cache = expression.args.get("cache") 1495 if cache is None: 1496 cache_str = "" 1497 elif cache is True: 1498 cache_str = " CACHE" 1499 else: 1500 cache_str = f" CACHE {cache}" 1501 1502 options = self.expressions(expression, key="options", flat=True, sep=" ") 1503 options = f" {options}" if options else "" 1504 1505 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1507 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1508 timing = expression.args.get("timing", "") 1509 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1510 timing_events = f"{timing} {events}".strip() if timing or events else "" 1511 1512 parts = [timing_events, "ON", self.sql(expression, "table")] 1513 1514 if referenced_table := expression.args.get("referenced_table"): 1515 parts.extend(["FROM", self.sql(referenced_table)]) 1516 1517 if deferrable := expression.args.get("deferrable"): 1518 parts.append(deferrable) 1519 1520 if initially := expression.args.get("initially"): 1521 parts.append(f"INITIALLY {initially}") 1522 1523 if referencing := expression.args.get("referencing"): 1524 parts.append(self.sql(referencing)) 1525 1526 if for_each := expression.args.get("for_each"): 1527 parts.append(f"FOR EACH {for_each}") 1528 1529 if when := expression.args.get("when"): 1530 parts.append(f"WHEN ({self.sql(when)})") 1531 1532 parts.append(self.sql(expression, "execute")) 1533 1534 return self.sep().join(parts)
1536 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1537 parts = [] 1538 1539 if old_alias := expression.args.get("old"): 1540 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1541 1542 if new_alias := expression.args.get("new"): 1543 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1544 1545 return f"REFERENCING {' '.join(parts)}"
1554 def clone_sql(self, expression: exp.Clone) -> str: 1555 this = self.sql(expression, "this") 1556 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1557 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1558 return f"{shallow}{keyword} {this}"
1560 def describe_sql(self, expression: exp.Describe) -> str: 1561 style = expression.args.get("style") 1562 style = f" {style}" if style else "" 1563 partition = self.sql(expression, "partition") 1564 partition = f" {partition}" if partition else "" 1565 format = self.sql(expression, "format") 1566 format = f" {format}" if format else "" 1567 as_json = " AS JSON" if expression.args.get("as_json") else "" 1568 1569 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1581 def with_sql(self, expression: exp.With) -> str: 1582 udfs = self.expressions(expression, key="udfs", flat=True) 1583 udfs = f"WITH {udfs}" if udfs else "" 1584 1585 sql = self.expressions(expression, flat=True) 1586 1587 recursive = ( 1588 "RECURSIVE " 1589 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1590 else "" 1591 ) 1592 search = self.sql(expression, "search") 1593 search = f" {search}" if search else "" 1594 cycle = self.sql(expression, "cycle") 1595 cycle = f" {cycle}" if cycle else "" 1596 1597 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1598 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1600 def cte_sql(self, expression: exp.CTE) -> str: 1601 alias = expression.args.get("alias") 1602 if alias: 1603 alias.add_comments(expression.pop_comments()) 1604 1605 alias_sql = self.sql(expression, "alias") 1606 1607 materialized = expression.args.get("materialized") 1608 if materialized is False: 1609 materialized = "NOT MATERIALIZED " 1610 elif materialized: 1611 materialized = "MATERIALIZED " 1612 1613 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1614 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1615 1616 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1618 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1619 alias = self.sql(expression, "this") 1620 columns = self.expressions(expression, key="columns", flat=True) 1621 columns = f"({columns})" if columns else "" 1622 1623 if ( 1624 columns 1625 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1626 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1627 ): 1628 columns = "" 1629 self.unsupported("Named columns are not supported in table alias.") 1630 1631 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1632 alias = self._next_name() 1633 1634 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1642 def hexstring_sql( 1643 self, expression: exp.HexString, binary_function_repr: str | None = None 1644 ) -> str: 1645 this = self.sql(expression, "this") 1646 is_integer_type = expression.args.get("is_integer") 1647 1648 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1649 not self.dialect.HEX_START and not binary_function_repr 1650 ): 1651 # Integer representation will be returned if: 1652 # - The read dialect treats the hex value as integer literal but not the write 1653 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1654 return f"{int(this, 16)}" 1655 1656 if not is_integer_type: 1657 # Read dialect treats the hex value as BINARY/BLOB 1658 if binary_function_repr: 1659 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1660 return self.func(binary_function_repr, exp.Literal.string(this)) 1661 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1662 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1663 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1664 1665 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1667 def bytestring_sql(self, expression: exp.ByteString) -> str: 1668 this = self.sql(expression, "this") 1669 is_bytes = bool(expression.args.get("is_bytes")) 1670 if is_bytes and self.BYTE_STRING_FUNCTION: 1671 # Hex digits can be quoted directly without escaping. 1672 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1673 1674 if self.dialect.BYTE_START: 1675 escaped_byte_string = self.escape_str( 1676 this, 1677 escape_backslash=bool( 1678 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1679 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1680 ), 1681 delimiter=self.dialect.BYTE_END, 1682 escaped_delimiter=self._escaped_byte_quote_end, 1683 is_byte_string=True, 1684 is_bytes=is_bytes, 1685 ) 1686 delimited_byte_string = ( 1687 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1688 ) 1689 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1690 return self.sql( 1691 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1692 ) 1693 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1694 return self.sql( 1695 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1696 ) 1697 1698 return delimited_byte_string 1699 1700 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1701 return self.sql(exp.Literal.string(this)) 1702 1703 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1704 return ""
1706 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1707 this = self.sql(expression, "this") 1708 escape = expression.args.get("escape") 1709 unicode_start = self.dialect.UNICODE_START 1710 1711 if unicode_start: 1712 escape_substitute = r"\\\1" 1713 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1714 else: 1715 escape_substitute = r"\\u\1" 1716 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1717 1718 if escape: 1719 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1720 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1721 else: 1722 escape_pattern = ESCAPED_UNICODE_RE 1723 escape_sql = "" 1724 1725 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1726 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1727 1728 if unicode_start: 1729 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1730 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1731 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1732 else: 1733 this = self.escape_str(this, escape_backslash=False) 1734 1735 return f"{left_quote}{this}{right_quote}{escape_sql}"
1737 def rawstring_sql(self, expression: exp.RawString) -> str: 1738 string = expression.this 1739 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1740 string = string.replace("\\", "\\\\") 1741 1742 string = self.escape_str(string, escape_backslash=False) 1743 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:
1751 def datatype_param_bound_limiter( 1752 self, 1753 expression: exp.DataType, 1754 type_value: exp.DType, 1755 defaults: tuple[int, ...], 1756 bounds: tuple[int | None, ...], 1757 ) -> exp.DataType: 1758 params = expression.expressions 1759 1760 if not params: 1761 if defaults: 1762 expression.set( 1763 "expressions", 1764 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1765 ) 1766 return expression 1767 1768 if not bounds: 1769 return expression 1770 1771 for i, param in enumerate(params): 1772 bound = bounds[i] if i < len(bounds) else None 1773 if bound is None: 1774 continue 1775 1776 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1777 value = ( 1778 param_value.to_py() 1779 if isinstance(param_value, exp.Literal) and param_value.is_number 1780 else None 1781 ) 1782 if isinstance(value, (int, Decimal)) and value > bound: 1783 self.unsupported( 1784 f"{type_value.value} parameter {param_value.name} exceeds " 1785 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1786 ) 1787 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1788 1789 return expression
1791 def datatype_sql(self, expression: exp.DataType) -> str: 1792 nested = "" 1793 values = "" 1794 1795 expr_nested = expression.args.get("nested") 1796 type_value = expression.this 1797 1798 if ( 1799 not expr_nested 1800 and isinstance(type_value, exp.DType) 1801 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1802 ): 1803 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1804 1805 interior = ( 1806 self.expressions( 1807 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1808 ) 1809 if expr_nested and self.pretty 1810 else self.expressions(expression, flat=True) 1811 ) 1812 1813 if type_value in self.UNSUPPORTED_TYPES: 1814 self.unsupported( 1815 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1816 ) 1817 1818 type_sql: t.Any = "" 1819 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1820 type_sql = self.sql(expression, "kind") 1821 elif type_value == exp.DType.CHARACTER_SET: 1822 char = f"CHAR({interior})" if interior else "CHAR" 1823 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1824 else: 1825 type_sql = ( 1826 self.TYPE_MAPPING.get(type_value, type_value.value) 1827 if isinstance(type_value, exp.DType) 1828 else type_value 1829 ) 1830 1831 if interior: 1832 if expr_nested: 1833 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1834 if expression.args.get("values") is not None: 1835 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1836 values = self.expressions(expression, key="values", flat=True) 1837 values = f"{delimiters[0]}{values}{delimiters[1]}" 1838 elif type_value == exp.DType.INTERVAL: 1839 nested = f" {interior}" 1840 else: 1841 nested = f"({interior})" 1842 1843 type_sql = f"{type_sql}{nested}{values}" 1844 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1845 exp.DType.TIMETZ, 1846 exp.DType.TIMESTAMPTZ, 1847 ): 1848 type_sql = f"{type_sql} WITH TIME ZONE" 1849 1850 collate = self.sql(expression, "collate") 1851 if collate: 1852 type_sql = f"{type_sql} COLLATE {collate}" 1853 1854 return type_sql
1856 def directory_sql(self, expression: exp.Directory) -> str: 1857 local = "LOCAL " if expression.args.get("local") else "" 1858 row_format = self.sql(expression, "row_format") 1859 row_format = f" {row_format}" if row_format else "" 1860 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1862 def delete_sql(self, expression: exp.Delete) -> str: 1863 hint = self.sql(expression, "hint") 1864 this = self.sql(expression, "this") 1865 this = f" FROM {this}" if this else "" 1866 using = self.expressions(expression, key="using") 1867 using = f" USING {using}" if using else "" 1868 cluster = self.sql(expression, "cluster") 1869 cluster = f" {cluster}" if cluster else "" 1870 where = self.sql(expression, "where") 1871 returning = self.sql(expression, "returning") 1872 order = self.sql(expression, "order") 1873 limit = self.sql(expression, "limit") 1874 tables = self.expressions(expression, key="tables") 1875 tables = f" {tables}" if tables else "" 1876 if self.RETURNING_END: 1877 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1878 else: 1879 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1880 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1882 def drop_sql(self, expression: exp.Drop) -> str: 1883 tables = self.expressions(expression, key="tables", flat=True) 1884 expressions = self.expressions(expression, flat=True) 1885 expressions = f" ({expressions})" if expressions else "" 1886 kind = expression.args["kind"] 1887 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1888 iceberg = ( 1889 " ICEBERG" 1890 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1891 else "" 1892 ) 1893 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1894 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1895 on_cluster = self.sql(expression, "cluster") 1896 on_cluster = f" {on_cluster}" if on_cluster else "" 1897 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1898 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1899 cascade = " CASCADE" if expression.args.get("cascade") else "" 1900 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1901 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1902 purge = " PURGE" if expression.args.get("purge") else "" 1903 sync = " SYNC" if expression.args.get("sync") else "" 1904 force = " FORCE" if expression.args.get("force") else "" 1905 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1907 def set_operation(self, expression: exp.SetOperation) -> str: 1908 op_type = type(expression) 1909 op_name = op_type.key.upper() 1910 1911 distinct = expression.args.get("distinct") 1912 if ( 1913 distinct is False 1914 and op_type in (exp.Except, exp.Intersect) 1915 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1916 ): 1917 self.unsupported(f"{op_name} ALL is not supported") 1918 1919 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1920 1921 if distinct is None: 1922 distinct = default_distinct 1923 if distinct is None: 1924 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1925 1926 if distinct is default_distinct: 1927 distinct_or_all = "" 1928 else: 1929 distinct_or_all = " DISTINCT" if distinct else " ALL" 1930 1931 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1932 side_kind = f"{side_kind} " if side_kind else "" 1933 1934 by_name = " BY NAME" if expression.args.get("by_name") else "" 1935 on = self.expressions(expression, key="on", flat=True) 1936 on = f" ON ({on})" if on else "" 1937 1938 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1940 def set_operations(self, expression: exp.SetOperation) -> str: 1941 if not self.SET_OP_MODIFIERS: 1942 limit = expression.args.get("limit") 1943 order = expression.args.get("order") 1944 offset = expression.args.get("offset") 1945 1946 if limit or order or offset: 1947 select = self._move_ctes_to_top_level( 1948 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1949 ) 1950 1951 for arg in ("limit", "order", "offset"): 1952 if value := expression.args.get(arg): 1953 select.set(arg, value.pop()) 1954 return self.sql(select) 1955 1956 sqls: list[str] = [] 1957 stack: list[str | exp.Expr] = [expression] 1958 1959 while stack: 1960 node = stack.pop() 1961 1962 if isinstance(node, exp.SetOperation): 1963 stack.append(node.expression) 1964 stack.append( 1965 self.maybe_comment( 1966 self.set_operation(node), comments=node.comments, separated=True 1967 ) 1968 ) 1969 stack.append(node.this) 1970 else: 1971 if ( 1972 not self.SET_OP_LIMITS 1973 and isinstance(node, exp.Select) 1974 and node.args.get("limit") 1975 ): 1976 node = node.subquery(copy=False) 1977 if not self.SET_OP_PARENTHESIZED_OPERANDS: 1978 node = exp.select("*").from_(node, copy=False) 1979 sqls.append(self.sql(node)) 1980 1981 this = self.sep().join(sqls) 1982 this = self.query_modifiers(expression, this) 1983 return self.prepend_ctes(expression, this)
1985 def fetch_sql(self, expression: exp.Fetch) -> str: 1986 direction = expression.args.get("direction") 1987 direction = f" {direction}" if direction else "" 1988 count = self.sql(expression, "count") 1989 count = f" {count}" if count else "" 1990 limit_options = self.sql(expression, "limit_options") 1991 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1992 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1994 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1995 percent = " PERCENT" if expression.args.get("percent") else "" 1996 rows = " ROWS" if expression.args.get("rows") else "" 1997 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1998 if not with_ties and rows: 1999 with_ties = " ONLY" 2000 return f"{percent}{rows}{with_ties}"
2014 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2015 using = self.sql(expression, "using") 2016 using = f" USING {using}" if using else "" 2017 columns = self.expressions(expression, key="columns", flat=True) 2018 columns = f"({columns})" if columns else "" 2019 partition_by = self.expressions(expression, key="partition_by", flat=True) 2020 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2021 where = self.sql(expression, "where") 2022 include = self.expressions(expression, key="include", flat=True) 2023 if include: 2024 include = f" INCLUDE ({include})" 2025 with_storage = self.expressions(expression, key="with_storage", flat=True) 2026 with_storage = f" WITH ({with_storage})" if with_storage else "" 2027 tablespace = self.sql(expression, "tablespace") 2028 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 2029 on = self.sql(expression, "on") 2030 on = f" ON {on}" if on else "" 2031 2032 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2034 def index_sql(self, expression: exp.Index) -> str: 2035 unique = "UNIQUE " if expression.args.get("unique") else "" 2036 primary = "PRIMARY " if expression.args.get("primary") else "" 2037 amp = "AMP " if expression.args.get("amp") else "" 2038 name = self.sql(expression, "this") 2039 name = f"{name} " if name else "" 2040 table = self.sql(expression, "table") 2041 table = f"{self.INDEX_ON} {table}" if table else "" 2042 2043 index = "INDEX " if not table else "" 2044 2045 params = self.sql(expression, "params") 2046 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2048 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2049 this = expression.this 2050 if this and this.is_string: 2051 resolved = maybe_parse(this.name).sql(self.dialect) 2052 if "expressions" in expression.args: 2053 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2054 # We can't safely emit the call to other dialects since name/arg semantics may differ 2055 self.unsupported( 2056 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2057 ) 2058 return resolved 2059 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2060 return self.func("IDENTIFIER", this)
2062 def identifier_sql(self, expression: exp.Identifier) -> str: 2063 text = expression.name 2064 lower = text.lower() 2065 quoted = expression.quoted 2066 text = lower if self.normalize and not quoted else text 2067 if self._identifier_escaped_sequences: 2068 for char, escaped in self._identifier_escaped_sequences.items(): 2069 text = text.replace(char, escaped) 2070 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2071 if ( 2072 quoted 2073 or self.dialect.can_quote(expression, self.identify) 2074 or lower in self.RESERVED_KEYWORDS 2075 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2076 ): 2077 text = ( 2078 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2079 ) 2080 return text
2095 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2096 input_format = self.sql(expression, "input_format") 2097 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2098 output_format = self.sql(expression, "output_format") 2099 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2100 return self.sep().join((input_format, output_format))
2110 def properties_sql(self, expression: exp.Properties) -> str: 2111 root_properties = [] 2112 with_properties = [] 2113 2114 for p in expression.expressions: 2115 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2116 if p_loc == exp.Properties.Location.POST_WITH: 2117 with_properties.append(p) 2118 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2119 root_properties.append(p) 2120 2121 root_props_ast = exp.Properties(expressions=root_properties) 2122 root_props_ast.parent = expression.parent 2123 2124 with_props_ast = exp.Properties(expressions=with_properties) 2125 with_props_ast.parent = expression.parent 2126 2127 root_props = self.root_properties(root_props_ast) 2128 with_props = self.with_properties(with_props_ast) 2129 2130 if root_props and with_props and not self.pretty: 2131 with_props = " " + with_props 2132 2133 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2140 def properties( 2141 self, 2142 properties: exp.Properties, 2143 prefix: str = "", 2144 sep: str = ", ", 2145 suffix: str = "", 2146 wrapped: bool = True, 2147 ) -> str: 2148 if properties.expressions: 2149 expressions = self.expressions(properties, sep=sep, indent=False) 2150 if expressions: 2151 expressions = self.wrap(expressions) if wrapped else expressions 2152 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2153 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2158 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2159 properties_locs = defaultdict(list) 2160 for p in properties.expressions: 2161 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2162 if p_loc != exp.Properties.Location.UNSUPPORTED: 2163 properties_locs[p_loc].append(p) 2164 else: 2165 self.unsupported(f"Unsupported property {p.key}") 2166 2167 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2174 def property_sql(self, expression: exp.Property) -> str: 2175 property_cls = expression.__class__ 2176 if property_cls == exp.Property: 2177 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2178 2179 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2180 if not property_name: 2181 self.unsupported(f"Unsupported property {expression.key}") 2182 2183 return f"{property_name}={self.sql(expression, 'this')}"
2188 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2189 if self.SUPPORTS_CREATE_TABLE_LIKE: 2190 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2191 options = f" {options}" if options else "" 2192 2193 like = f"LIKE {self.sql(expression, 'this')}{options}" 2194 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2195 like = f"({like})" 2196 2197 return like 2198 2199 if expression.expressions: 2200 self.unsupported("Transpilation of LIKE property options is unsupported") 2201 2202 select = exp.select("*").from_(expression.this).limit(0) 2203 return f"AS {self.sql(select)}"
2210 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2211 no = "NO " if expression.args.get("no") else "" 2212 local = expression.args.get("local") 2213 local = f"{local} " if local else "" 2214 dual = "DUAL " if expression.args.get("dual") else "" 2215 before = "BEFORE " if expression.args.get("before") else "" 2216 after = "AFTER " if expression.args.get("after") else "" 2217 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:
2233 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2234 if expression.args.get("no"): 2235 return "NO MERGEBLOCKRATIO" 2236 if expression.args.get("default"): 2237 return "DEFAULT MERGEBLOCKRATIO" 2238 2239 percent = " PERCENT" if expression.args.get("percent") else "" 2240 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2247 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2248 default = expression.args.get("default") 2249 minimum = expression.args.get("minimum") 2250 maximum = expression.args.get("maximum") 2251 if default or minimum or maximum: 2252 if default: 2253 prop = "DEFAULT" 2254 elif minimum: 2255 prop = "MINIMUM" 2256 else: 2257 prop = "MAXIMUM" 2258 return f"{prop} DATABLOCKSIZE" 2259 units = expression.args.get("units") 2260 units = f" {units}" if units else "" 2261 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2263 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2264 autotemp = expression.args.get("autotemp") 2265 always = expression.args.get("always") 2266 default = expression.args.get("default") 2267 manual = expression.args.get("manual") 2268 never = expression.args.get("never") 2269 2270 if autotemp is not None: 2271 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2272 elif always: 2273 prop = "ALWAYS" 2274 elif default: 2275 prop = "DEFAULT" 2276 elif manual: 2277 prop = "MANUAL" 2278 elif never: 2279 prop = "NEVER" 2280 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2282 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2283 no = expression.args.get("no") 2284 no = " NO" if no else "" 2285 concurrent = expression.args.get("concurrent") 2286 concurrent = " CONCURRENT" if concurrent else "" 2287 target = self.sql(expression, "target") 2288 target = f" {target}" if target else "" 2289 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2291 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2292 if isinstance(expression.this, list): 2293 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2294 if expression.this: 2295 modulus = self.sql(expression, "this") 2296 remainder = self.sql(expression, "expression") 2297 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2298 2299 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2300 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2301 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2303 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2304 this = self.sql(expression, "this") 2305 2306 for_values_or_default = expression.expression 2307 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2308 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2309 else: 2310 for_values_or_default = " DEFAULT" 2311 2312 return f"PARTITION OF {this}{for_values_or_default}"
2314 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2315 kind = expression.args.get("kind") 2316 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2317 for_or_in = expression.args.get("for_or_in") 2318 for_or_in = f" {for_or_in}" if for_or_in else "" 2319 lock_type = expression.args.get("lock_type") 2320 override = " OVERRIDE" if expression.args.get("override") else "" 2321 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2323 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2324 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2325 statistics = expression.args.get("statistics") 2326 statistics_sql = "" 2327 if statistics is not None: 2328 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2329 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2331 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2332 this = self.sql(expression, "this") 2333 this = f"HISTORY_TABLE={this}" if this else "" 2334 data_consistency: str | None = self.sql(expression, "data_consistency") 2335 data_consistency = ( 2336 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2337 ) 2338 retention_period: str | None = self.sql(expression, "retention_period") 2339 retention_period = ( 2340 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2341 ) 2342 2343 if this: 2344 on_sql = self.func("ON", this, data_consistency, retention_period) 2345 else: 2346 on_sql = "ON" if expression.args.get("on") else "OFF" 2347 2348 sql = f"SYSTEM_VERSIONING={on_sql}" 2349 2350 return f"WITH({sql})" if expression.args.get("with_") else sql
2352 def insert_sql(self, expression: exp.Insert) -> str: 2353 hint = self.sql(expression, "hint") 2354 overwrite = expression.args.get("overwrite") 2355 2356 if isinstance(expression.this, exp.Directory): 2357 this = " OVERWRITE" if overwrite else " INTO" 2358 else: 2359 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2360 2361 stored = self.sql(expression, "stored") 2362 stored = f" {stored}" if stored else "" 2363 alternative = expression.args.get("alternative") 2364 alternative = f" OR {alternative}" if alternative else "" 2365 ignore = " IGNORE" if expression.args.get("ignore") else "" 2366 is_function = expression.args.get("is_function") 2367 if is_function: 2368 this = f"{this} FUNCTION" 2369 this = f"{this} {self.sql(expression, 'this')}" 2370 2371 exists = " IF EXISTS" if expression.args.get("exists") else "" 2372 where = self.sql(expression, "where") 2373 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2374 using = self.expressions(expression, key="using", flat=True) 2375 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2376 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2377 on_conflict = self.sql(expression, "conflict") 2378 on_conflict = f" {on_conflict}" if on_conflict else "" 2379 by_name = " BY NAME" if expression.args.get("by_name") else "" 2380 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2381 returning = self.sql(expression, "returning") 2382 2383 if self.RETURNING_END: 2384 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2385 else: 2386 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2387 2388 partition_by = self.sql(expression, "partition") 2389 partition_by = f" {partition_by}" if partition_by else "" 2390 settings = self.sql(expression, "settings") 2391 settings = f" {settings}" if settings else "" 2392 2393 source = self.sql(expression, "source") 2394 source = f"TABLE {source}" if source else "" 2395 2396 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2397 return self.prepend_ctes(expression, sql)
2415 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2416 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2417 2418 constraint = self.sql(expression, "constraint") 2419 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2420 2421 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2422 if conflict_keys: 2423 conflict_keys = f"({conflict_keys})" 2424 2425 index_predicate = self.sql(expression, "index_predicate") 2426 conflict_keys = f"{conflict_keys}{index_predicate} " 2427 2428 action = self.sql(expression, "action") 2429 2430 expressions = self.expressions(expression, flat=True) 2431 if expressions: 2432 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2433 expressions = f" {set_keyword}{expressions}" 2434 2435 where = self.sql(expression, "where") 2436 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2441 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2442 fields = self.sql(expression, "fields") 2443 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2444 escaped = self.sql(expression, "escaped") 2445 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2446 items = self.sql(expression, "collection_items") 2447 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2448 keys = self.sql(expression, "map_keys") 2449 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2450 lines = self.sql(expression, "lines") 2451 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2452 null = self.sql(expression, "null") 2453 null = f" NULL DEFINED AS {null}" if null else "" 2454 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2482 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2483 table = self.table_parts(expression) 2484 only = "ONLY " if expression.args.get("only") else "" 2485 partition = self.sql(expression, "partition") 2486 partition = f" {partition}" if partition else "" 2487 version = self.sql(expression, "version") 2488 version = f" {version}" if version else "" 2489 alias = self.sql(expression, "alias") 2490 alias = f"{sep}{alias}" if alias else "" 2491 2492 sample = self.sql(expression, "sample") 2493 post_alias = "" 2494 pre_alias = "" 2495 2496 if self.dialect.ALIAS_POST_TABLESAMPLE: 2497 pre_alias = sample 2498 else: 2499 post_alias = sample 2500 2501 if self.dialect.ALIAS_POST_VERSION: 2502 pre_alias = f"{pre_alias}{version}" 2503 else: 2504 post_alias = f"{post_alias}{version}" 2505 2506 hints = self.expressions(expression, key="hints", sep=" ") 2507 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2508 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2509 joins = self.indent( 2510 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2511 ) 2512 laterals = self.expressions(expression, key="laterals", sep="") 2513 2514 file_format = self.sql(expression, "format") 2515 pattern = self.sql(expression, "pattern") 2516 if file_format: 2517 pattern = f", PATTERN => {pattern}" if pattern else "" 2518 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2519 elif pattern: 2520 file_format = f" (PATTERN => {pattern})" 2521 2522 ordinality = expression.args.get("ordinality") or "" 2523 if ordinality: 2524 ordinality = f" WITH ORDINALITY{alias}" 2525 alias = "" 2526 2527 when = self.sql(expression, "when") 2528 if when: 2529 if self.HISTORICAL_DATA_POST_ALIAS: 2530 alias = f"{alias} {when}" 2531 else: 2532 table = f"{table} {when}" 2533 2534 changes = self.sql(expression, "changes") 2535 changes = f" {changes}" if changes else "" 2536 2537 rows_from = self.expressions(expression, key="rows_from") 2538 if rows_from: 2539 table = f"ROWS FROM {self.wrap(rows_from)}" 2540 2541 indexed = expression.args.get("indexed") 2542 if indexed is not None: 2543 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2544 else: 2545 indexed = "" 2546 2547 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2549 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2550 table = self.func("TABLE", expression.this) 2551 alias = self.sql(expression, "alias") 2552 alias = f" AS {alias}" if alias else "" 2553 sample = self.sql(expression, "sample") 2554 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2555 joins = self.indent( 2556 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2557 ) 2558 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2560 def tablesample_sql( 2561 self, 2562 expression: exp.TableSample, 2563 tablesample_keyword: str | None = None, 2564 ) -> str: 2565 method = self.sql(expression, "method") 2566 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2567 numerator = self.sql(expression, "bucket_numerator") 2568 denominator = self.sql(expression, "bucket_denominator") 2569 field = self.sql(expression, "bucket_field") 2570 field = f" ON {field}" if field else "" 2571 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2572 seed = self.sql(expression, "seed") 2573 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2574 2575 size = self.sql(expression, "size") 2576 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2577 size = f"{size} ROWS" 2578 2579 percent = self.sql(expression, "percent") 2580 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2581 percent = f"{percent} PERCENT" 2582 2583 expr = f"{bucket}{percent}{size}" 2584 if self.TABLESAMPLE_REQUIRES_PARENS: 2585 expr = f"({expr})" 2586 2587 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2664 def pivot_sql(self, expression: exp.Pivot) -> str: 2665 expressions = self.expressions(expression, flat=True) 2666 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2667 2668 group = self.sql(expression, "group") 2669 2670 if expression.this: 2671 this = self.sql(expression, "this") 2672 if not expressions: 2673 sql = f"UNPIVOT {this}" 2674 else: 2675 on = f"{self.seg('ON')} {expressions}" 2676 into = self.sql(expression, "into") 2677 into = f"{self.seg('INTO')} {into}" if into else "" 2678 using = self.expressions(expression, key="using", flat=True) 2679 using = f"{self.seg('USING')} {using}" if using else "" 2680 sql = f"{direction} {this}{on}{into}{using}{group}" 2681 return self.prepend_ctes(expression, sql) 2682 2683 if not expression.unpivot: 2684 # Wrap IN-list values with explicit aliases where the target dialect would differ 2685 new_field_exprs = self._pivot_in_value_aliases(expression) 2686 if new_field_exprs is not None: 2687 expression.fields[0].set("expressions", new_field_exprs) 2688 2689 alias = self.sql(expression, "alias") 2690 if alias: 2691 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2692 2693 fields = self.expressions( 2694 expression, 2695 "fields", 2696 sep=" ", 2697 dynamic=True, 2698 new_line=True, 2699 skip_first=True, 2700 skip_last=True, 2701 ) 2702 2703 include_nulls = expression.args.get("include_nulls") 2704 if include_nulls is not None: 2705 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2706 else: 2707 nulls = "" 2708 2709 default_on_null = self.sql(expression, "default_on_null") 2710 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2711 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2712 return self.prepend_ctes(expression, sql)
2755 def update_sql(self, expression: exp.Update) -> str: 2756 hint = self.sql(expression, "hint") 2757 this = self.sql(expression, "this") 2758 join_sql, from_sql = self._update_from_joins_sql(expression) 2759 set_sql = self.expressions(expression, flat=True) 2760 where_sql = self.sql(expression, "where") 2761 returning = self.sql(expression, "returning") 2762 order = self.sql(expression, "order") 2763 limit = self.sql(expression, "limit") 2764 if self.RETURNING_END: 2765 expression_sql = f"{from_sql}{where_sql}{returning}" 2766 else: 2767 expression_sql = f"{returning}{from_sql}{where_sql}" 2768 options = self.expressions(expression, key="options") 2769 options = f" OPTION({options})" if options else "" 2770 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2771 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2773 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2774 values_as_table = values_as_table and self.VALUES_AS_TABLE 2775 2776 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2777 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2778 args = self.expressions(expression) 2779 alias = self.sql(expression, "alias") 2780 values = f"VALUES{self.seg('')}{args}" 2781 values = ( 2782 f"({values})" 2783 if self.WRAP_DERIVED_VALUES 2784 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2785 else values 2786 ) 2787 values = self.query_modifiers(expression, values) 2788 return f"{values} AS {alias}" if alias else values 2789 2790 # Converts `VALUES...` expression into a series of select unions. 2791 alias_node = expression.args.get("alias") 2792 column_names = alias_node and alias_node.columns 2793 2794 selects: list[exp.Query] = [] 2795 2796 for i, tup in enumerate(expression.expressions): 2797 row = tup.expressions 2798 2799 if i == 0 and column_names: 2800 row = [ 2801 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2802 ] 2803 2804 selects.append(exp.Select(expressions=row)) 2805 2806 if self.pretty: 2807 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2808 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2809 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2810 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2811 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2812 2813 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2814 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2815 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2820 @unsupported_args("expressions") 2821 def into_sql(self, expression: exp.Into) -> str: 2822 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2823 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2824 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2837 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2838 this = self.sql(expression, "this") 2839 2840 columns = self.expressions(expression, flat=True) 2841 2842 from_sql = self.sql(expression, "from_index") 2843 from_sql = f" FROM {from_sql}" if from_sql else "" 2844 2845 properties = expression.args.get("properties") 2846 properties_sql = ( 2847 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2848 ) 2849 2850 return f"{this}({columns}){from_sql}{properties_sql}"
2859 def group_sql(self, expression: exp.Group) -> str: 2860 group_by_all = expression.args.get("all") 2861 if group_by_all is True: 2862 modifier = " ALL" 2863 elif group_by_all is False: 2864 modifier = " DISTINCT" 2865 else: 2866 modifier = "" 2867 2868 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2869 2870 grouping_sets = self.expressions(expression, key="grouping_sets") 2871 cube = self.expressions(expression, key="cube") 2872 rollup = self.expressions(expression, key="rollup") 2873 2874 groupings = csv( 2875 self.seg(grouping_sets) if grouping_sets else "", 2876 self.seg(cube) if cube else "", 2877 self.seg(rollup) if rollup else "", 2878 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2879 sep=self.GROUPINGS_SEP, 2880 ) 2881 2882 if ( 2883 expression.expressions 2884 and groupings 2885 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2886 ): 2887 add_separator = True 2888 2889 if grouping_sets: 2890 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2891 add_separator = False 2892 else: 2893 self.unsupported( 2894 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2895 ) 2896 2897 if add_separator: 2898 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2899 2900 return f"{group_by}{groupings}"
2906 def connect_sql(self, expression: exp.Connect) -> str: 2907 start = self.sql(expression, "start") 2908 start = self.seg(f"START WITH {start}") if start else "" 2909 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2910 connect = self.sql(expression, "connect") 2911 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2912 return start + connect
2917 def join_sql(self, expression: exp.Join) -> str: 2918 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2919 side = None 2920 else: 2921 side = expression.side 2922 2923 op_sql = " ".join( 2924 op 2925 for op in ( 2926 expression.method, 2927 "GLOBAL" if expression.args.get("global_") else None, 2928 side, 2929 expression.kind, 2930 expression.hint if self.JOIN_HINTS else None, 2931 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2932 ) 2933 if op 2934 ) 2935 match_cond = self.sql(expression, "match_condition") 2936 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2937 on_sql = self.sql(expression, "on") 2938 using = expression.args.get("using") 2939 2940 if not on_sql and using: 2941 on_sql = csv(*(self.sql(column) for column in using)) 2942 2943 this = expression.this 2944 this_sql = self.sql(this) 2945 2946 exprs = self.expressions(expression) 2947 if exprs: 2948 this_sql = f"{this_sql},{self.seg(exprs)}" 2949 2950 if on_sql: 2951 on_sql = self.indent(on_sql, skip_first=True) 2952 space = self.seg(" " * self.pad) if self.pretty else " " 2953 if using: 2954 on_sql = f"{space}USING ({on_sql})" 2955 else: 2956 on_sql = f"{space}ON {on_sql}" 2957 elif not op_sql: 2958 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2959 return f" {this_sql}" 2960 2961 return f", {this_sql}" 2962 2963 if op_sql != "STRAIGHT_JOIN": 2964 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2965 2966 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2967 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:
2974 def lateral_op(self, expression: exp.Lateral) -> str: 2975 cross_apply = expression.args.get("cross_apply") 2976 2977 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2978 if cross_apply is True: 2979 op = "INNER JOIN " 2980 elif cross_apply is False: 2981 op = "LEFT JOIN " 2982 else: 2983 op = "" 2984 2985 return f"{op}LATERAL"
2987 def lateral_sql(self, expression: exp.Lateral) -> str: 2988 this = self.sql(expression, "this") 2989 2990 if expression.args.get("view"): 2991 alias = expression.args["alias"] 2992 columns = self.expressions(alias, key="columns", flat=True) 2993 table = f" {alias.name}" if alias.name else "" 2994 columns = f" AS {columns}" if columns else "" 2995 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2996 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2997 2998 table_alias = expression.args.get("alias") 2999 offset = expression.this.args.get("offset") 3000 3001 if ( 3002 self.UNNEST_WITH_ORDINALITY 3003 and table_alias 3004 and isinstance(expression.this, exp.Unnest) 3005 and isinstance(offset, exp.Identifier) 3006 ): 3007 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3008 table_alias = table_alias.copy() 3009 table_alias.append("columns", offset.copy()) 3010 3011 alias = self.sql(table_alias) 3012 alias = f" AS {alias}" if alias else "" 3013 3014 ordinality = expression.args.get("ordinality") or "" 3015 if ordinality: 3016 ordinality = f" WITH ORDINALITY{alias}" 3017 alias = "" 3018 3019 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
3021 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3022 this = self.sql(expression, "this") 3023 3024 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3025 return this 3026 3027 args = [ 3028 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3029 for e in (expression.args.get(k) for k in ("offset", "expression")) 3030 if e 3031 ] 3032 3033 args_sql = ", ".join(self.sql(e) for e in args) 3034 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3035 expressions = self.expressions(expression, flat=True) 3036 limit_options = self.sql(expression, "limit_options") 3037 expressions = f" BY {expressions}" if expressions else "" 3038 3039 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3041 def offset_sql(self, expression: exp.Offset) -> str: 3042 this = self.sql(expression, "this") 3043 value = expression.expression 3044 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3045 expressions = self.expressions(expression, flat=True) 3046 expressions = f" BY {expressions}" if expressions else "" 3047 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3049 def setitem_sql(self, expression: exp.SetItem) -> str: 3050 kind = self.sql(expression, "kind") 3051 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3052 kind = "" 3053 else: 3054 kind = f"{kind} " if kind else "" 3055 this = self.sql(expression, "this") 3056 expressions = self.expressions(expression) 3057 collate = self.sql(expression, "collate") 3058 collate = f" COLLATE {collate}" if collate else "" 3059 global_ = "GLOBAL " if expression.args.get("global_") else "" 3060 return f"{global_}{kind}{this}{expressions}{collate}"
3067 def queryband_sql(self, expression: exp.QueryBand) -> str: 3068 this = self.sql(expression, "this") 3069 update = " UPDATE" if expression.args.get("update") else "" 3070 scope = self.sql(expression, "scope") 3071 scope = f" FOR {scope}" if scope else "" 3072 3073 return f"QUERY_BAND = {this}{update}{scope}"
3078 def lock_sql(self, expression: exp.Lock) -> str: 3079 if not self.LOCKING_READS_SUPPORTED: 3080 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3081 return "" 3082 3083 update = expression.args["update"] 3084 key = expression.args.get("key") 3085 if update: 3086 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3087 else: 3088 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3089 expressions = self.expressions(expression, flat=True) 3090 expressions = f" OF {expressions}" if expressions else "" 3091 wait = expression.args.get("wait") 3092 3093 if wait is not None: 3094 if isinstance(wait, exp.Literal): 3095 wait = f" WAIT {self.sql(wait)}" 3096 else: 3097 wait = " NOWAIT" if wait else " SKIP LOCKED" 3098 3099 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:
3107 def escape_str( 3108 self, 3109 text: str, 3110 escape_backslash: bool = True, 3111 delimiter: str | None = None, 3112 escaped_delimiter: str | None = None, 3113 is_byte_string: bool = False, 3114 is_bytes: bool = False, 3115 ) -> str: 3116 if is_byte_string: 3117 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3118 else: 3119 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3120 3121 if supports_escape_sequences: 3122 text = "".join( 3123 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3124 for ch in text 3125 ) 3126 3127 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3128 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3129 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3130 3131 delimiter = delimiter or self.dialect.QUOTE_END 3132 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3133 3134 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3136 def loaddata_sql(self, expression: exp.LoadData) -> str: 3137 is_overwrite = expression.args.get("overwrite") 3138 overwrite = " OVERWRITE" if is_overwrite else "" 3139 this = self.sql(expression, "this") 3140 3141 files = expression.args.get("files") 3142 if files: 3143 files_sql = self.expressions(files, flat=True) 3144 files_sql = f"FILES{self.wrap(files_sql)}" 3145 if is_overwrite: 3146 this = f" {this}" 3147 elif expression.args.get("temp"): 3148 this = f" INTO TEMP TABLE {this}" 3149 else: 3150 this = f" INTO TABLE {this}" 3151 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3152 3153 local = " LOCAL" if expression.args.get("local") else "" 3154 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3155 this = f" INTO TABLE {this}" 3156 partition = self.sql(expression, "partition") 3157 partition = f" {partition}" if partition else "" 3158 input_format = self.sql(expression, "input_format") 3159 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3160 serde = self.sql(expression, "serde") 3161 serde = f" SERDE {serde}" if serde else "" 3162 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3176 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3177 this = self.sql(expression, "this") 3178 this = f"{this} " if this else this 3179 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3180 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3182 def withfill_sql(self, expression: exp.WithFill) -> str: 3183 from_sql = self.sql(expression, "from_") 3184 from_sql = f" FROM {from_sql}" if from_sql else "" 3185 to_sql = self.sql(expression, "to") 3186 to_sql = f" TO {to_sql}" if to_sql else "" 3187 step_sql = self.sql(expression, "step") 3188 step_sql = f" STEP {step_sql}" if step_sql else "" 3189 interpolated_values = [ 3190 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3191 if isinstance(e, exp.Alias) 3192 else self.sql(e, "this") 3193 for e in expression.args.get("interpolate") or [] 3194 ] 3195 interpolate = ( 3196 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3197 ) 3198 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3252 def ordered_sql(self, expression: exp.Ordered) -> str: 3253 desc = expression.args.get("desc") 3254 asc = not desc 3255 3256 nulls_first = expression.args.get("nulls_first") 3257 nulls_last = not nulls_first 3258 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3259 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3260 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3261 3262 this = self.sql(expression, "this") 3263 3264 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3265 nulls_sort_change = "" 3266 if nulls_first and ( 3267 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3268 ): 3269 nulls_sort_change = " NULLS FIRST" 3270 elif ( 3271 nulls_last 3272 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3273 and not nulls_are_last 3274 ): 3275 nulls_sort_change = " NULLS LAST" 3276 3277 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3278 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3279 window = expression.find_ancestor(exp.Window, exp.Select) 3280 3281 if isinstance(window, exp.Window): 3282 window_this = window.this 3283 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3284 window_this = window_this.this 3285 spec = window.args.get("spec") 3286 else: 3287 window_this = None 3288 spec = None 3289 3290 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3291 # without a spec or with a ROWS spec, but not with RANGE 3292 if not ( 3293 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3294 and (not spec or spec.text("kind").upper() == "ROWS") 3295 ): 3296 if window_this and spec: 3297 self.unsupported( 3298 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3299 ) 3300 nulls_sort_change = "" 3301 elif self.NULL_ORDERING_SUPPORTED is False and ( 3302 (asc and nulls_sort_change == " NULLS LAST") 3303 or (desc and nulls_sort_change == " NULLS FIRST") 3304 ): 3305 # BigQuery does not allow these ordering/nulls combinations when used under 3306 # an aggregation func or under a window containing one 3307 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3308 3309 if isinstance(ancestor, exp.Window): 3310 ancestor = ancestor.this 3311 if isinstance(ancestor, exp.AggFunc): 3312 self.unsupported( 3313 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3314 ) 3315 nulls_sort_change = "" 3316 elif self.NULL_ORDERING_SUPPORTED is None: 3317 if expression.this.is_int: 3318 self.unsupported( 3319 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3320 ) 3321 elif not isinstance(expression.this, exp.Rand): 3322 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3323 target = self.sql(resolved) if resolved is not None else this 3324 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3325 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3326 nulls_sort_change = "" 3327 3328 with_fill = self.sql(expression, "with_fill") 3329 with_fill = f" {with_fill}" if with_fill else "" 3330 3331 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3341 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3342 partition = self.partition_by_sql(expression) 3343 order = self.sql(expression, "order") 3344 measures = self.expressions(expression, key="measures") 3345 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3346 rows = self.sql(expression, "rows") 3347 rows = self.seg(rows) if rows else "" 3348 after = self.sql(expression, "after") 3349 after = self.seg(after) if after else "" 3350 pattern = self.sql(expression, "pattern") 3351 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3352 definition_sqls = [ 3353 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3354 for definition in expression.args.get("define", []) 3355 ] 3356 definitions = self.expressions(sqls=definition_sqls) 3357 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3358 body = "".join( 3359 ( 3360 partition, 3361 order, 3362 measures, 3363 rows, 3364 after, 3365 pattern, 3366 define, 3367 ) 3368 ) 3369 alias = self.sql(expression, "alias") 3370 alias = f" {alias}" if alias else "" 3371 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3373 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3374 limit = expression.args.get("limit") 3375 3376 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3377 count = limit.args.get("count") 3378 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3379 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3380 limit = exp.Limit( 3381 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3382 ) 3383 elif ( 3384 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3385 ): 3386 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3387 3388 return csv( 3389 *sqls, 3390 *[self.sql(join) for join in expression.args.get("joins") or []], 3391 self.sql(expression, "match"), 3392 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3393 self.sql(expression, "prewhere"), 3394 self.sql(expression, "where"), 3395 self.sql(expression, "connect"), 3396 self.sql(expression, "group"), 3397 self.sql(expression, "having"), 3398 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3399 self.sql(expression, "order"), 3400 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3401 *self.after_limit_modifiers(expression), 3402 self.sql(expression, "for_"), 3403 self.options_modifier(expression), 3404 sep="", 3405 )
3411 def forclause_sql(self, expression: exp.ForClause) -> str: 3412 kind = expression.args["kind"] 3413 if kind == "BROWSE": 3414 return f"{self.sep()}FOR BROWSE" 3415 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3416 # the target dialect doesn't support QueryOption, so we drop the clause. 3417 options = self.expressions(expression, key="expressions") 3418 if not options: 3419 return "" 3420 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]:
3439 def select_sql(self, expression: exp.Select) -> str: 3440 into = expression.args.get("into") 3441 if not self.SUPPORTS_SELECT_INTO and into: 3442 into.pop() 3443 3444 hint = self.sql(expression, "hint") 3445 distinct = self.sql(expression, "distinct") 3446 distinct = f" {distinct}" if distinct else "" 3447 kind = self.sql(expression, "kind") 3448 3449 limit = expression.args.get("limit") 3450 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3451 top = self.limit_sql(limit, top=True) 3452 limit.pop() 3453 else: 3454 top = "" 3455 3456 expressions = self.expressions(expression) 3457 3458 if kind: 3459 if kind in self.SELECT_KINDS: 3460 kind = f" AS {kind}" 3461 else: 3462 if kind == "STRUCT": 3463 expressions = self.expressions( 3464 sqls=[ 3465 self.sql( 3466 exp.Struct( 3467 expressions=[ 3468 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3469 if isinstance(e, exp.Alias) 3470 else e 3471 for e in expression.expressions 3472 ] 3473 ) 3474 ) 3475 ] 3476 ) 3477 kind = "" 3478 3479 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3480 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3481 3482 exclude = expression.args.get("exclude") 3483 3484 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3485 exclude_sql = self.expressions(sqls=exclude, flat=True) 3486 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3487 3488 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3489 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3490 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3491 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3492 sql = self.query_modifiers( 3493 expression, 3494 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3495 self.sql(expression, "into", comment=False), 3496 self.sql(expression, "from_", comment=False), 3497 ) 3498 3499 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3500 if expression.args.get("with_"): 3501 sql = self.maybe_comment(sql, expression) 3502 expression.pop_comments() 3503 3504 sql = self.prepend_ctes(expression, sql) 3505 3506 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3507 expression.set("exclude", None) 3508 subquery = expression.subquery(copy=False) 3509 star = exp.Star(except_=exclude) 3510 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3511 3512 if not self.SUPPORTS_SELECT_INTO and into: 3513 if into.args.get("temporary"): 3514 table_kind = " TEMPORARY" 3515 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3516 table_kind = " UNLOGGED" 3517 else: 3518 table_kind = "" 3519 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3520 3521 return sql
3533 def star_sql(self, expression: exp.Star) -> str: 3534 except_ = self.expressions(expression, key="except_", flat=True) 3535 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3536 replace = self.expressions(expression, key="replace", flat=True) 3537 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3538 rename = self.expressions(expression, key="rename", flat=True) 3539 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3540 ilike = self.sql(expression, "ilike") 3541 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3542 return f"*{ilike}{except_}{replace}{rename}"
3558 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3559 alias = self.sql(expression, "alias") 3560 alias = f"{sep}{alias}" if alias else "" 3561 sample = self.sql(expression, "sample") 3562 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3563 alias = f"{sample}{alias}" 3564 3565 # Set to None so it's not generated again by self.query_modifiers() 3566 expression.set("sample", None) 3567 3568 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3569 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3570 return self.prepend_ctes(expression, sql)
3576 def unnest_sql(self, expression: exp.Unnest) -> str: 3577 args = self.expressions(expression, flat=True) 3578 3579 alias = expression.args.get("alias") 3580 offset = expression.args.get("offset") 3581 3582 if self.UNNEST_WITH_ORDINALITY: 3583 if alias and isinstance(offset, exp.Expr): 3584 alias.append("columns", offset) 3585 expression.set("offset", None) 3586 3587 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3588 columns = alias.columns 3589 alias = self.sql(columns[0]) if columns else "" 3590 else: 3591 alias = self.sql(alias) 3592 3593 alias = f" AS {alias}" if alias else alias 3594 if self.UNNEST_WITH_ORDINALITY: 3595 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3596 else: 3597 if isinstance(offset, exp.Expr): 3598 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3599 elif offset: 3600 suffix = f"{alias} WITH OFFSET" 3601 else: 3602 suffix = alias 3603 3604 return f"UNNEST({args}){suffix}"
3613 def window_sql(self, expression: exp.Window) -> str: 3614 this = self.sql(expression, "this") 3615 partition = self.partition_by_sql(expression) 3616 order = expression.args.get("order") 3617 order = self.order_sql(order, flat=True) if order else "" 3618 spec = self.sql(expression, "spec") 3619 alias = self.sql(expression, "alias") 3620 over = self.sql(expression, "over") or "OVER" 3621 3622 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3623 3624 first = expression.args.get("first") 3625 if first is None: 3626 first = "" 3627 else: 3628 first = "FIRST" if first else "LAST" 3629 3630 if not partition and not order and not spec and alias: 3631 return f"{this} {alias}" 3632 3633 args = self.format_args( 3634 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3635 ) 3636 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3642 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3643 kind = self.sql(expression, "kind") 3644 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3645 end = ( 3646 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3647 or "CURRENT ROW" 3648 ) 3649 3650 window_spec = f"{kind} BETWEEN {start} AND {end}" 3651 3652 exclude = self.sql(expression, "exclude") 3653 if exclude: 3654 if self.SUPPORTS_WINDOW_EXCLUDE: 3655 window_spec += f" EXCLUDE {exclude}" 3656 else: 3657 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3658 3659 return window_spec
3666 def between_sql(self, expression: exp.Between) -> str: 3667 this = self.sql(expression, "this") 3668 low = self.sql(expression, "low") 3669 high = self.sql(expression, "high") 3670 symmetric = expression.args.get("symmetric") 3671 3672 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3673 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3674 3675 flag = ( 3676 " SYMMETRIC" 3677 if symmetric 3678 else " ASYMMETRIC" 3679 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3680 else "" # silently drop ASYMMETRIC – semantics identical 3681 ) 3682 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]:
3684 def bracket_offset_expressions( 3685 self, expression: exp.Bracket, index_offset: int | None = None 3686 ) -> list[exp.Expr]: 3687 if expression.args.get("json_access"): 3688 return expression.expressions 3689 3690 return apply_index_offset( 3691 expression.this, 3692 expression.expressions, 3693 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3694 dialect=self.dialect, 3695 )
3708 def any_sql(self, expression: exp.Any) -> str: 3709 this = self.sql(expression, "this") 3710 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3711 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3712 this = self.wrap(this) 3713 return f"ANY{this}" 3714 return f"ANY {this}"
3719 def case_sql(self, expression: exp.Case) -> str: 3720 this = self.sql(expression, "this") 3721 statements = [f"CASE {this}" if this else "CASE"] 3722 3723 for e in expression.args["ifs"]: 3724 statements.append(f"WHEN {self.sql(e, 'this')}") 3725 statements.append(f"THEN {self.sql(e, 'true')}") 3726 3727 default = self.sql(expression, "default") 3728 3729 if default: 3730 statements.append(f"ELSE {default}") 3731 3732 statements.append("END") 3733 3734 if self.pretty and self.too_wide(statements): 3735 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3736 3737 return " ".join(statements)
3749 def extract_sql(self, expression: exp.Extract) -> str: 3750 import sqlglot.dialects.dialect 3751 3752 this = ( 3753 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3754 if self.NORMALIZE_EXTRACT_DATE_PARTS 3755 else expression.this 3756 ) 3757 if self.EXTRACT_ALLOWS_QUOTES: 3758 this_sql = self.sql(this) 3759 elif isinstance(this, exp.WeekStart): 3760 this_sql = self.weekstart_name(this) 3761 else: 3762 this_sql = this.name 3763 expression_sql = self.sql(expression, "expression") 3764 3765 return f"EXTRACT({this_sql} FROM {expression_sql})"
3767 def trim_sql(self, expression: exp.Trim) -> str: 3768 trim_type = self.sql(expression, "position") 3769 3770 if trim_type == "LEADING": 3771 func_name = "LTRIM" 3772 elif trim_type == "TRAILING": 3773 func_name = "RTRIM" 3774 else: 3775 func_name = "TRIM" 3776 3777 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3779 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3780 args = expression.expressions 3781 if isinstance(expression, exp.ConcatWs): 3782 args = args[1:] # Skip the delimiter 3783 3784 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3785 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3786 3787 concat_coalesce = ( 3788 self.dialect.CONCAT_WS_COALESCE 3789 if isinstance(expression, exp.ConcatWs) 3790 else self.dialect.CONCAT_COALESCE 3791 ) 3792 3793 if not concat_coalesce and expression.args.get("coalesce"): 3794 3795 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3796 if not e.type: 3797 import sqlglot.optimizer.annotate_types 3798 3799 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3800 3801 if e.is_string or e.is_type(exp.DType.ARRAY): 3802 return e 3803 3804 return exp.func("coalesce", e, exp.Literal.string("")) 3805 3806 args = [_wrap_with_coalesce(e) for e in args] 3807 3808 return args
3810 def concat_sql(self, expression: exp.Concat) -> str: 3811 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3812 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3813 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3814 # instead of coalescing them to empty string. 3815 import sqlglot.dialects.dialect 3816 3817 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3818 3819 expressions = self.convert_concat_args(expression) 3820 3821 # Some dialects don't allow a single-argument CONCAT call 3822 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3823 return self.sql(expressions[0]) 3824 3825 return self.func("CONCAT", *expressions)
3827 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3828 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3829 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3830 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3831 all_args = expression.expressions 3832 expression.set("coalesce", True) 3833 return self.sql( 3834 exp.case() 3835 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3836 .else_(expression) 3837 ) 3838 3839 return self.func( 3840 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3841 )
3847 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3848 this = self.sql(expression, "this") 3849 this = f" {this}" if this else "" 3850 expressions = self.expressions(expression, flat=True) 3851 expressions = f" ({expressions})" if expressions else "" 3852 reference = self.sql(expression, "reference") 3853 reference = f" {reference}" if reference else "" 3854 delete = self.sql(expression, "delete") 3855 delete = f" ON DELETE {delete}" if delete else "" 3856 update = self.sql(expression, "update") 3857 update = f" ON UPDATE {update}" if update else "" 3858 options = self.expressions(expression, key="options", flat=True, sep=" ") 3859 options = f" {options}" if options else "" 3860 return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}"
3862 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3863 this = self.sql(expression, "this") 3864 this = f" {this}" if this else "" 3865 expressions = self.expressions(expression, flat=True) 3866 include = self.sql(expression, "include") 3867 options = self.expressions(expression, key="options", flat=True, sep=" ") 3868 options = f" {options}" if options else "" 3869 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3878 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3879 if self.MATCH_AGAINST_TABLE_PREFIX: 3880 expressions = [] 3881 for expr in expression.expressions: 3882 if isinstance(expr, exp.Table): 3883 expressions.append(f"TABLE {self.sql(expr)}") 3884 else: 3885 expressions.append(expr) 3886 else: 3887 expressions = expression.expressions 3888 3889 modifier = expression.args.get("modifier") 3890 modifier = f" {modifier}" if modifier else "" 3891 return ( 3892 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3893 )
3907 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3908 if isinstance(expression, exp.JSONPathPart): 3909 transform = self.TRANSFORMS.get(expression.__class__) 3910 if not callable(transform): 3911 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3912 return "" 3913 3914 return transform(self, expression) 3915 3916 if isinstance(expression, int): 3917 return str(expression) 3918 3919 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3920 escaped = expression.replace("'", "\\'") 3921 escaped = f"'{escaped}'" 3922 else: 3923 escaped = expression.replace('"', '\\"') 3924 escaped = f'"{escaped}"' 3925 3926 return escaped
3931 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3932 # Output the Teradata column FORMAT override. 3933 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3934 this = self.sql(expression, "this") 3935 fmt = self.sql(expression, "format") 3936 return f"{this} (FORMAT {fmt})"
3964 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3965 null_handling = expression.args.get("null_handling") 3966 null_handling = f" {null_handling}" if null_handling else "" 3967 return_type = self.sql(expression, "return_type") 3968 return_type = f" RETURNING {return_type}" if return_type else "" 3969 strict = " STRICT" if expression.args.get("strict") else "" 3970 return self.func( 3971 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3972 )
3974 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3975 this = self.sql(expression, "this") 3976 order = self.sql(expression, "order") 3977 null_handling = expression.args.get("null_handling") 3978 null_handling = f" {null_handling}" if null_handling else "" 3979 return_type = self.sql(expression, "return_type") 3980 return_type = f" RETURNING {return_type}" if return_type else "" 3981 strict = " STRICT" if expression.args.get("strict") else "" 3982 return self.func( 3983 "JSON_ARRAYAGG", 3984 this, 3985 suffix=f"{order}{null_handling}{return_type}{strict})", 3986 )
3988 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3989 path = self.sql(expression, "path") 3990 path = f" PATH {path}" if path else "" 3991 nested_schema = self.sql(expression, "nested_schema") 3992 3993 if nested_schema: 3994 return f"NESTED{path} {nested_schema}" 3995 3996 this = self.sql(expression, "this") 3997 kind = self.sql(expression, "kind") 3998 kind = f" {kind}" if kind else "" 3999 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 4000 4001 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 4002 return f"{this}{kind}{format_json}{path}{ordinality}"
4007 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4008 this = self.sql(expression, "this") 4009 path = self.sql(expression, "path") 4010 path = f", {path}" if path else "" 4011 error_handling = expression.args.get("error_handling") 4012 error_handling = f" {error_handling}" if error_handling else "" 4013 empty_handling = expression.args.get("empty_handling") 4014 empty_handling = f" {empty_handling}" if empty_handling else "" 4015 schema = self.sql(expression, "schema") 4016 return self.func( 4017 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4018 )
4020 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4021 this = self.sql(expression, "this") 4022 kind = self.sql(expression, "kind") 4023 path = self.sql(expression, "path") 4024 path = f" {path}" if path else "" 4025 as_json = " AS JSON" if expression.args.get("as_json") else "" 4026 return f"{this} {kind}{path}{as_json}"
4028 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4029 this = self.sql(expression, "this") 4030 path = self.sql(expression, "path") 4031 path = f", {path}" if path else "" 4032 expressions = self.expressions(expression) 4033 with_ = ( 4034 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4035 if expressions 4036 else "" 4037 ) 4038 return f"OPENJSON({this}{path}){with_}"
4040 def in_sql(self, expression: exp.In) -> str: 4041 query = expression.args.get("query") 4042 unnest = expression.args.get("unnest") 4043 field = expression.args.get("field") 4044 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4045 4046 if query: 4047 in_sql = self.sql(query) 4048 elif unnest: 4049 in_sql = self.in_unnest_op(unnest) 4050 elif field: 4051 in_sql = self.sql(field) 4052 else: 4053 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4054 4055 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4060 def interval_sql(self, expression: exp.Interval) -> str: 4061 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4062 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4063 exp.AutoRefreshProperty, 4064 ) 4065 interval_keyword = "INTERVAL" if include_keyword else "" 4066 unit_expression = expression.args.get("unit") 4067 unit = self.sql(unit_expression) if unit_expression else "" 4068 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4069 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4070 unit = f" {unit}" if unit else "" 4071 4072 if self.SINGLE_STRING_INTERVAL: 4073 this = expression.this.name if expression.this else "" 4074 if this: 4075 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4076 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4077 return f"{interval_keyword}'{this}'{unit}" 4078 return f"{interval_keyword}'{this}{unit}'" 4079 return f"{interval_keyword}{unit}" 4080 4081 this = self.sql(expression, "this") 4082 if this: 4083 if not include_keyword and expression.this.is_string: 4084 this = expression.this.name 4085 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4086 this = f"({this})" 4087 if include_keyword: 4088 this = f" {this}" 4089 4090 return f"{interval_keyword}{this}{unit}"
4095 def reference_sql(self, expression: exp.Reference) -> str: 4096 this = self.sql(expression, "this") 4097 expressions = self.expressions(expression, flat=True) 4098 expressions = f"({expressions})" if expressions else "" 4099 options = self.expressions(expression, key="options", flat=True, sep=" ") 4100 options = f" {options}" if options else "" 4101 return f"REFERENCES {this}{expressions}{options}"
4103 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4104 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4105 parent = expression.parent 4106 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4107 4108 return self.func( 4109 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4110 )
4130 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4131 alias = expression.args["alias"] 4132 4133 parent = expression.parent 4134 pivot = parent and parent.parent 4135 4136 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4137 identifier_alias = isinstance(alias, exp.Identifier) 4138 literal_alias = isinstance(alias, exp.Literal) 4139 4140 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4141 alias.replace(exp.Literal.string(alias.output_name)) 4142 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4143 alias.replace(exp.to_identifier(alias.output_name)) 4144 4145 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:
4186 def connector_sql( 4187 self, 4188 expression: exp.Connector, 4189 op: str, 4190 stack: list[str | exp.Expr] | None = None, 4191 ) -> str: 4192 if stack is not None: 4193 stack.append(expression.right) 4194 if expression.comments and self.comments: 4195 op = self.maybe_comment(op, comments=expression.comments) 4196 4197 stack.extend((op, expression.left)) 4198 return op 4199 4200 stack = [expression] 4201 sqls: list[str] = [] 4202 ops = set() 4203 4204 while stack: 4205 node = stack.pop() 4206 if isinstance(node, exp.Connector): 4207 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4208 else: 4209 sql = self.sql(node) 4210 if sqls and sqls[-1] in ops: 4211 sqls[-1] += f" {sql}" 4212 else: 4213 sqls.append(sql) 4214 4215 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4216 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4236 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4237 format_sql = self.sql(expression, "format") 4238 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4239 to_sql = self.sql(expression, "to") 4240 to_sql = f" {to_sql}" if to_sql else "" 4241 action = self.sql(expression, "action") 4242 action = f" {action}" if action else "" 4243 default = self.sql(expression, "default") 4244 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4245 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4275 def comment_sql(self, expression: exp.Comment) -> str: 4276 this = self.sql(expression, "this") 4277 kind = expression.args["kind"] 4278 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4279 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4280 expression_sql = self.sql(expression, "expression") 4281 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4283 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4284 this = self.sql(expression, "this") 4285 delete = " DELETE" if expression.args.get("delete") else "" 4286 recompress = self.sql(expression, "recompress") 4287 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4288 to_disk = self.sql(expression, "to_disk") 4289 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4290 to_volume = self.sql(expression, "to_volume") 4291 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4292 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4294 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4295 where = self.sql(expression, "where") 4296 group = self.sql(expression, "group") 4297 aggregates = self.expressions(expression, key="aggregates") 4298 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4299 4300 if not (where or group or aggregates) and len(expression.expressions) == 1: 4301 return f"TTL {self.expressions(expression, flat=True)}" 4302 4303 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4322 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4323 this = self.sql(expression, "this") 4324 4325 exists = "" 4326 if expression.args.get("exists"): 4327 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4328 exists = " IF EXISTS" 4329 else: 4330 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4331 4332 dtype = self.sql(expression, "dtype") 4333 if dtype: 4334 collate = self.sql(expression, "collate") 4335 collate = f" COLLATE {collate}" if collate else "" 4336 using = self.sql(expression, "using") 4337 using = f" USING {using}" if using else "" 4338 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4339 null_constraint = self._alter_column_null_constraint_sql(expression) 4340 4341 return ( 4342 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4343 f"{collate}{using}{null_constraint}" 4344 ) 4345 4346 default = self.sql(expression, "default") 4347 if default: 4348 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4349 4350 comment = self.sql(expression, "comment") 4351 if comment: 4352 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4353 4354 visible = expression.args.get("visible") 4355 if visible: 4356 return f"ALTER COLUMN{exists} {this} SET {visible}" 4357 4358 allow_null = expression.args.get("allow_null") 4359 drop = expression.args.get("drop") 4360 4361 if not drop and not allow_null: 4362 self.unsupported("Unsupported ALTER COLUMN syntax") 4363 4364 if allow_null is not None: 4365 keyword = "DROP" if drop else "SET" 4366 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4367 4368 return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4381 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4382 this = self.sql(expression, "this") 4383 rename_from = self.sql(expression, "rename_from") 4384 if rename_from: 4385 if not self.SUPPORTS_CHANGE_COLUMN: 4386 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4387 return f"CHANGE COLUMN {rename_from} {this}" 4388 if not self.SUPPORTS_MODIFY_COLUMN: 4389 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4390 return f"MODIFY COLUMN {this}"
4406 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4407 compound = " COMPOUND" if expression.args.get("compound") else "" 4408 this = self.sql(expression, "this") 4409 expressions = self.expressions(expression, flat=True) 4410 expressions = f"({expressions})" if expressions else "" 4411 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4413 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4414 if not self.RENAME_TABLE_WITH_DB: 4415 # Remove db from tables 4416 expression = expression.transform( 4417 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4418 ).assert_is(exp.AlterRename) 4419 this = self.sql(expression, "this") 4420 to_kw = " TO" if include_to else "" 4421 return f"RENAME{to_kw} {this}"
4436 def alter_sql(self, expression: exp.Alter) -> str: 4437 actions = expression.actions 4438 4439 if ( 4440 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4441 and actions 4442 and isinstance(actions[0], exp.ColumnDef) 4443 ): 4444 actions_sql = self.expressions(expression, key="actions", flat=True) 4445 actions_sql = f"ADD {actions_sql}" 4446 else: 4447 actions_list = [] 4448 for action in actions: 4449 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4450 action_sql = self.add_column_sql(action) 4451 else: 4452 action_sql = self.sql(action) 4453 if isinstance(action, exp.Query): 4454 action_sql = f"AS {action_sql}" 4455 4456 actions_list.append(action_sql) 4457 4458 actions_sql = self.format_args(*actions_list).lstrip("\n") 4459 4460 iceberg = ( 4461 "ICEBERG " 4462 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4463 else "" 4464 ) 4465 exists = " IF EXISTS" if expression.args.get("exists") else "" 4466 on_cluster = self.sql(expression, "cluster") 4467 on_cluster = f" {on_cluster}" if on_cluster else "" 4468 only = " ONLY" if expression.args.get("only") else "" 4469 options = expression.args.get("options") or [] 4470 options_sql = self.expressions( 4471 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4472 ) 4473 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4474 partitions_sql = self.expressions( 4475 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4476 sep=" ", 4477 flat=True, 4478 ) 4479 if partitions_sql and (actions_sql or options_sql): 4480 partitions_sql = f" {partitions_sql}" 4481 kind = self.sql(expression, "kind") 4482 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4483 check = " WITH CHECK" if expression.args.get("check") else "" 4484 cascade = ( 4485 " CASCADE" 4486 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4487 else "" 4488 ) 4489 this = self.sql(expression, "this") 4490 this = f" {this}" if this else "" 4491 4492 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4499 def add_column_sql(self, expression: exp.Expr) -> str: 4500 sql = self.sql(expression) 4501 if isinstance(expression, exp.Schema): 4502 column_text = " COLUMNS" 4503 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4504 column_text = " COLUMN" 4505 else: 4506 column_text = "" 4507 4508 return f"ADD{column_text} {sql}"
4521 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4522 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4523 location = self.sql(expression, "location") 4524 location = f" {location}" if location else "" 4525 return f"ADD {exists}{self.sql(expression.this)}{location}"
4527 def distinct_sql(self, expression: exp.Distinct) -> str: 4528 this = self.expressions(expression, flat=True) 4529 4530 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4531 case = exp.case() 4532 for arg in expression.expressions: 4533 case = case.when(arg.is_(exp.null()), exp.null()) 4534 this = self.sql(case.else_(f"({this})")) 4535 4536 this = f" {this}" if this else "" 4537 4538 on = self.sql(expression, "on") 4539 on = f" ON {on}" if on else "" 4540 return f"DISTINCT{this}{on}"
4569 def div_sql(self, expression: exp.Div) -> str: 4570 l, r = expression.left, expression.right 4571 4572 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4573 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4574 4575 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4576 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4577 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4578 4579 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4580 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4581 # Native integer division depends on the target's operand types, which may differ 4582 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4583 # integer literals or casts 4584 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4585 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4586 ): 4587 return self.sql(exp.IntDiv(this=l, expression=r)) 4588 return self._truncated_div_sql(l, r) 4589 4590 return self.binary(expression, "/")
4615 def escape_sql(self, expression: exp.Escape) -> str: 4616 this = expression.this 4617 if ( 4618 isinstance(this, (exp.Like, exp.ILike)) 4619 and isinstance(this.expression, (exp.All, exp.Any)) 4620 and not self.SUPPORTS_LIKE_QUANTIFIERS 4621 ): 4622 return self._like_sql(this, escape=expression) 4623 return self.binary(expression, "ESCAPE")
4634 def is_sql(self, expression: exp.Is) -> str: 4635 negate = expression.args.get("negate") 4636 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4637 positive = bool(expression.expression.this) != bool(negate) 4638 return self.sql(expression.this if positive else exp.not_(expression.this)) 4639 return self.binary(expression, "IS NOT" if negate else "IS")
4711 def mod_sql(self, expression: exp.Mod) -> str: 4712 this = self.sql(expression, "this") 4713 expr = self.sql(expression, "expression") 4714 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4715 4716 parent = expression.parent 4717 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4718 return f"({sql})" 4719 4720 return sql
4750 def log_sql(self, expression: exp.Log) -> str: 4751 this = expression.this 4752 expr = expression.expression 4753 4754 if self.dialect.LOG_BASE_FIRST is False: 4755 this, expr = expr, this 4756 elif self.dialect.LOG_BASE_FIRST is None and expr: 4757 if this.name in ("2", "10"): 4758 return self.func(f"LOG{this.name}", expr) 4759 4760 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4761 4762 return self.func("LOG", this, expr)
4771 def binary(self, expression: exp.Binary, op: str) -> str: 4772 sqls: list[str] = [] 4773 stack: list[None | str | exp.Expr] = [expression] 4774 binary_type = type(expression) 4775 4776 while stack: 4777 node = stack.pop() 4778 4779 if type(node) is binary_type: 4780 op_func = node.args.get("operator") 4781 if op_func: 4782 op = f"OPERATOR({self.sql(op_func)})" 4783 4784 stack.append(node.args.get("expression")) 4785 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4786 stack.append(node.args.get("this")) 4787 else: 4788 sqls.append(self.sql(node)) 4789 4790 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4799 def function_fallback_sql(self, expression: exp.Func) -> str: 4800 args = [] 4801 4802 for key in expression.arg_types: 4803 arg_value = expression.args.get(key) 4804 4805 if isinstance(arg_value, list): 4806 for value in arg_value: 4807 args.append(value) 4808 elif arg_value is not None: 4809 args.append(arg_value) 4810 4811 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:
4828 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4829 arg_sqls = tuple( 4830 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4831 ) 4832 if self.pretty and self.too_wide(arg_sqls): 4833 return self.indent( 4834 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4835 ) 4836 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:
4841 def format_time( 4842 self, 4843 expression: exp.Expr, 4844 inverse_time_mapping: dict[str, str] | None = None, 4845 inverse_time_trie: dict | None = None, 4846 ) -> str | None: 4847 return format_time( 4848 self.sql(expression, "format"), 4849 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4850 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4851 )
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:
4853 def expressions( 4854 self, 4855 expression: exp.Expr | None = None, 4856 key: str | None = None, 4857 sqls: t.Collection[str | exp.Expr] | None = None, 4858 flat: bool = False, 4859 indent: bool = True, 4860 skip_first: bool = False, 4861 skip_last: bool = False, 4862 sep: str = ", ", 4863 prefix: str = "", 4864 dynamic: bool = False, 4865 new_line: bool = False, 4866 ) -> str: 4867 expressions = expression.args.get(key or "expressions") if expression else sqls 4868 4869 if not expressions: 4870 return "" 4871 4872 if flat: 4873 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4874 4875 num_sqls = len(expressions) 4876 result_sqls = [] 4877 4878 for i, e in enumerate(expressions): 4879 sql = self.sql(e, comment=False) 4880 if not sql: 4881 continue 4882 4883 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4884 4885 if self.pretty: 4886 if self.leading_comma: 4887 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4888 else: 4889 result_sqls.append( 4890 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4891 ) 4892 else: 4893 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4894 4895 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4896 if new_line: 4897 result_sqls.insert(0, "") 4898 result_sqls.append("") 4899 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4900 else: 4901 result_sql = "".join(result_sqls) 4902 4903 return ( 4904 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4905 if indent 4906 else result_sql 4907 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4909 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4910 flat = flat or isinstance(expression.parent, exp.Properties) 4911 expressions_sql = self.expressions(expression, flat=flat) 4912 if flat: 4913 return f"{op} {expressions_sql}" 4914 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4916 def naked_property(self, expression: exp.Property) -> str: 4917 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4918 if not property_name: 4919 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4920 return f"{property_name} {self.sql(expression, 'this')}"
4928 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4929 this = self.sql(expression, "this") 4930 expressions = self.no_identify(self.expressions, expression) 4931 expressions = ( 4932 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4933 ) 4934 return f"{this}{expressions}" if expressions.strip() != "" else this
4953 def when_sql(self, expression: exp.When) -> str: 4954 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4955 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4956 condition = self.sql(expression, "condition") 4957 condition = f" AND {condition}" if condition else "" 4958 4959 then_expression = expression.args.get("then") 4960 if isinstance(then_expression, exp.Insert): 4961 this = self.sql(then_expression, "this") 4962 this = f"INSERT {this}" if this else "INSERT" 4963 then = self.sql(then_expression, "expression") 4964 then = f"{this} VALUES {then}" if then else this 4965 elif isinstance(then_expression, exp.Update): 4966 if isinstance(then_expression.args.get("expressions"), exp.Star): 4967 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4968 else: 4969 expressions_sql = self.expressions(then_expression) 4970 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4971 else: 4972 then = self.sql(then_expression) 4973 4974 if isinstance(then_expression, (exp.Insert, exp.Update)): 4975 where = self.sql(then_expression, "where") 4976 if where and not self.SUPPORTS_MERGE_WHERE: 4977 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4978 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4979 where = "" 4980 then = f"{then}{where}" 4981 return f"WHEN {matched}{source}{condition} THEN {then}"
4986 def merge_sql(self, expression: exp.Merge) -> str: 4987 table = expression.this 4988 table_alias = "" 4989 4990 hints = table.args.get("hints") 4991 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4992 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4993 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4994 4995 this = self.sql(table) 4996 using = f"USING {self.sql(expression, 'using')}" 4997 whens = self.sql(expression, "whens") 4998 4999 on = self.sql(expression, "on") 5000 on = f"ON {on}" if on else "" 5001 5002 if not on: 5003 on = self.expressions(expression, key="using_cond") 5004 on = f"USING ({on})" if on else "" 5005 5006 returning = self.sql(expression, "returning") 5007 if returning: 5008 whens = f"{whens}{returning}" 5009 5010 sep = self.sep() 5011 5012 return self.prepend_ctes( 5013 expression, 5014 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5015 )
@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:
5021 @unsupported_args("default") 5022 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5023 if not self.SUPPORTS_TO_NUMBER: 5024 self.unsupported("Unsupported TO_NUMBER function") 5025 return self._tonumber_cast_sql(expression) 5026 5027 fmt = expression.args.get("format") 5028 if not fmt: 5029 self.unsupported("Conversion format is required for TO_NUMBER") 5030 return self._tonumber_cast_sql(expression) 5031 5032 return self.func("TO_NUMBER", expression.this, fmt)
5039 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5040 this = self.sql(expression, "this") 5041 kind = self.sql(expression, "kind") 5042 settings_sql = self.expressions(expression, key="settings", sep=" ") 5043 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5044 return f"{this}({kind}{args})"
def
aggregatekeyproperty_sql( self, expression: sqlglot.expressions.properties.AggregateKeyProperty) -> str:
def
duplicatekeyproperty_sql( self, expression: sqlglot.expressions.properties.DuplicateKeyProperty) -> str:
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.properties.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
def
distributedbyproperty_sql( self, expression: sqlglot.expressions.properties.DistributedByProperty) -> str:
5068 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5069 expressions = self.expressions(expression, flat=True) 5070 expressions = f" {self.wrap(expressions)}" if expressions else "" 5071 buckets = self.sql(expression, "buckets") 5072 kind = self.sql(expression, "kind") 5073 buckets = f" BUCKETS {buckets}" if buckets else "" 5074 order = self.sql(expression, "order") 5075 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
5080 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5081 expressions = self.expressions(expression, key="expressions", flat=True) 5082 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5083 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5084 buckets = self.sql(expression, "buckets") 5085 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5087 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5088 this = self.sql(expression, "this") 5089 having = self.sql(expression, "having") 5090 5091 if having: 5092 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5093 5094 return self.func("ANY_VALUE", this)
5096 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5097 transform = self.func("TRANSFORM", *expression.expressions) 5098 row_format_before = self.sql(expression, "row_format_before") 5099 row_format_before = f" {row_format_before}" if row_format_before else "" 5100 record_writer = self.sql(expression, "record_writer") 5101 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5102 using = f" USING {self.sql(expression, 'command_script')}" 5103 schema = self.sql(expression, "schema") 5104 schema = f" AS {schema}" if schema else "" 5105 row_format_after = self.sql(expression, "row_format_after") 5106 row_format_after = f" {row_format_after}" if row_format_after else "" 5107 record_reader = self.sql(expression, "record_reader") 5108 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5109 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:
5111 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5112 key_block_size = self.sql(expression, "key_block_size") 5113 if key_block_size: 5114 return f"KEY_BLOCK_SIZE = {key_block_size}" 5115 5116 using = self.sql(expression, "using") 5117 if using: 5118 return f"USING {using}" 5119 5120 parser = self.sql(expression, "parser") 5121 if parser: 5122 return f"WITH PARSER {parser}" 5123 5124 comment = self.sql(expression, "comment") 5125 if comment: 5126 return f"COMMENT {comment}" 5127 5128 visible = expression.args.get("visible") 5129 if visible is not None: 5130 return "VISIBLE" if visible else "INVISIBLE" 5131 5132 engine_attr = self.sql(expression, "engine_attr") 5133 if engine_attr: 5134 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5135 5136 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5137 if secondary_engine_attr: 5138 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5139 5140 self.unsupported("Unsupported index constraint option.") 5141 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
5147 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5148 kind = self.sql(expression, "kind") 5149 kind = f"{kind} INDEX" if kind else "INDEX" 5150 this = self.sql(expression, "this") 5151 this = f" {this}" if this else "" 5152 index_type = self.sql(expression, "index_type") 5153 index_type = f" USING {index_type}" if index_type else "" 5154 expressions = self.expressions(expression, flat=True) 5155 expressions = f" ({expressions})" if expressions else "" 5156 options = self.expressions(expression, key="options", sep=" ") 5157 options = f" {options}" if options else "" 5158 return f"{kind}{this}{index_type}{expressions}{options}"
5160 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5161 if self.NVL2_SUPPORTED: 5162 return self.function_fallback_sql(expression) 5163 5164 case = exp.Case().when( 5165 expression.this.is_(exp.null()).not_(copy=False), 5166 expression.args["true"], 5167 copy=False, 5168 ) 5169 else_cond = expression.args.get("false") 5170 if else_cond: 5171 case.else_(else_cond, copy=False) 5172 5173 return self.sql(case)
5181 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5182 this = self.sql(expression, "this") 5183 expr = self.sql(expression, "expression") 5184 position = self.sql(expression, "position") 5185 position = f", {position}" if position else "" 5186 iterator = self.sql(expression, "iterator") 5187 condition = self.sql(expression, "condition") 5188 condition = f" IF {condition}" if condition else "" 5189 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
5239 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5240 this_sql = self.sql(expression, "this") 5241 if isinstance(expression.this, exp.Table): 5242 this_sql = f"TABLE {this_sql}" 5243 5244 return self.func( 5245 "FORECAST", 5246 this_sql, 5247 expression.args.get("data_col"), 5248 expression.args.get("timestamp_col"), 5249 expression.args.get("model"), 5250 expression.args.get("id_cols"), 5251 expression.args.get("horizon"), 5252 expression.args.get("forecast_end_timestamp"), 5253 expression.args.get("confidence_level"), 5254 expression.args.get("output_historical_time_series"), 5255 expression.args.get("context_window"), 5256 )
5258 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5259 this_sql = self.sql(expression, "this") 5260 if isinstance(expression.this, exp.Table): 5261 this_sql = f"TABLE {this_sql}" 5262 5263 return self.func( 5264 "FEATURES_AT_TIME", 5265 this_sql, 5266 expression.args.get("time"), 5267 expression.args.get("num_rows"), 5268 expression.args.get("ignore_feature_nulls"), 5269 )
5271 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5272 this_sql = self.sql(expression, "this") 5273 if isinstance(expression.this, exp.Table): 5274 this_sql = f"TABLE {this_sql}" 5275 5276 query_table = self.sql(expression, "query_table") 5277 if isinstance(expression.args["query_table"], exp.Table): 5278 query_table = f"TABLE {query_table}" 5279 5280 return self.func( 5281 "VECTOR_SEARCH", 5282 this_sql, 5283 expression.args.get("column_to_search"), 5284 query_table, 5285 expression.args.get("query_column_to_search"), 5286 expression.args.get("top_k"), 5287 expression.args.get("distance_type"), 5288 expression.args.get("options"), 5289 )
@unsupported_args('partition_start', 'partition_end', 'force', 'mode')
def
refresh_sql(self, expression: sqlglot.expressions.core.Refresh) -> str:
5296 @unsupported_args("partition_start", "partition_end", "force", "mode") 5297 def refresh_sql(self, expression: exp.Refresh) -> str: 5298 this = self.sql(expression, "this") 5299 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5300 return f"REFRESH {kind}{this}"
5302 def toarray_sql(self, expression: exp.ToArray) -> str: 5303 arg = expression.this 5304 if not arg.type: 5305 import sqlglot.optimizer.annotate_types 5306 5307 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5308 5309 if arg.is_type(exp.DType.ARRAY): 5310 return self.sql(arg) 5311 5312 cond_for_null = arg.is_(exp.null()) 5313 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5315 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5316 this = expression.this 5317 time_format = self.format_time(expression) 5318 5319 if time_format: 5320 return self.sql( 5321 exp.cast( 5322 exp.StrToTime(this=this, format=expression.args["format"]), 5323 exp.DType.TIME, 5324 ) 5325 ) 5326 5327 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5328 return self.sql(this) 5329 5330 return self.sql(exp.cast(this, exp.DType.TIME))
5332 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5333 this = expression.this 5334 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5335 return self.sql(this) 5336 5337 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5339 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5340 this = expression.this 5341 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5342 return self.sql(this) 5343 5344 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5346 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5347 this = expression.this 5348 time_format = self.format_time(expression) 5349 safe = expression.args.get("safe") 5350 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5351 return self.sql( 5352 exp.cast( 5353 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5354 exp.DType.DATE, 5355 ) 5356 ) 5357 5358 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5359 return self.sql(this) 5360 5361 if safe: 5362 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5363 5364 return self.sql(exp.cast(this, exp.DType.DATE))
5376 def lastday_sql(self, expression: exp.LastDay) -> str: 5377 if self.LAST_DAY_SUPPORTS_DATE_PART: 5378 return self.function_fallback_sql(expression) 5379 5380 unit = expression.args.get("unit") 5381 if unit and unit.name.upper() != "MONTH": 5382 self.unsupported("Date parts are not supported in LAST_DAY.") 5383 5384 return self.func("LAST_DAY", expression.this)
def
arrayinsert_sql( self, expression: sqlglot.expressions.array.ArrayInsert, index_offset: int = 0) -> str:
5396 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5397 this = expression.this 5398 position = expression.args["position"] 5399 offset = index_offset - (expression.args.get("offset") or 0) 5400 5401 if offset: 5402 if position.is_int: 5403 value = position.to_py() 5404 if value >= 0: 5405 position = exp.Literal.number(value + offset) 5406 elif offset < 0 and value == -1: 5407 # 1-based -1 appends, which a 0-based position can only express as the size 5408 position = exp.ArraySize(this=this.copy()) 5409 else: 5410 # Negative positions count from the end, so they shift in the opposite 5411 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5412 position = exp.Literal.number(value - offset) 5413 else: 5414 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5415 5416 return self.func("ARRAY_INSERT", this, position, expression.expression)
5418 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5419 if self.CAN_IMPLEMENT_ARRAY_ANY: 5420 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5421 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5422 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5423 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5424 5425 import sqlglot.dialects.dialect 5426 5427 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5428 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5429 self.unsupported("ARRAY_ANY is unsupported") 5430 5431 return self.function_fallback_sql(expression)
5433 def struct_sql(self, expression: exp.Struct) -> str: 5434 expression.set( 5435 "expressions", 5436 [ 5437 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5438 if isinstance(e, exp.PropertyEQ) 5439 else e 5440 for e in expression.expressions 5441 ], 5442 ) 5443 5444 return self.function_fallback_sql(expression)
5452 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5453 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5454 tables = f" {self.expressions(expression)}" 5455 5456 exists = " IF EXISTS" if expression.args.get("exists") else "" 5457 5458 on_cluster = self.sql(expression, "cluster") 5459 on_cluster = f" {on_cluster}" if on_cluster else "" 5460 5461 identity = self.sql(expression, "identity") 5462 identity = f" {identity} IDENTITY" if identity else "" 5463 5464 option = self.sql(expression, "option") 5465 option = f" {option}" if option else "" 5466 5467 partition = self.sql(expression, "partition") 5468 partition = f" {partition}" if partition else "" 5469 5470 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5474 def convert_sql(self, expression: exp.Convert) -> str: 5475 to = expression.this 5476 value = expression.expression 5477 style = expression.args.get("style") 5478 safe = expression.args.get("safe") 5479 strict = expression.args.get("strict") 5480 5481 if not to or not value: 5482 return "" 5483 5484 # Retrieve length of datatype and override to default if not specified 5485 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5486 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5487 5488 transformed: exp.Expr | None = None 5489 cast = exp.Cast if strict else exp.TryCast 5490 5491 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5492 if isinstance(style, exp.Literal) and style.is_int: 5493 import sqlglot.dialects.tsql 5494 5495 style_value = style.name 5496 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5497 if not converted_style: 5498 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5499 5500 fmt = exp.Literal.string(converted_style) 5501 5502 if to.this == exp.DType.DATE: 5503 transformed = exp.StrToDate(this=value, format=fmt) 5504 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5505 transformed = exp.StrToTime(this=value, format=fmt) 5506 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5507 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5508 elif to.this == exp.DType.TEXT: 5509 transformed = exp.TimeToStr(this=value, format=fmt) 5510 5511 if not transformed: 5512 transformed = cast(this=value, to=to, safe=safe) 5513 5514 return self.sql(transformed)
5591 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5592 option = self.sql(expression, "this") 5593 5594 if expression.expressions: 5595 upper = option.upper() 5596 5597 # Snowflake FILE_FORMAT options are separated by whitespace 5598 sep = " " if upper == "FILE_FORMAT" else ", " 5599 5600 # Databricks copy/format options do not set their list of values with EQ 5601 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5602 values = self.expressions(expression, flat=True, sep=sep) 5603 return f"{option}{op}({values})" 5604 5605 value = self.sql(expression, "expression") 5606 5607 if not value: 5608 return option 5609 5610 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5611 5612 return f"{option}{op}{value}"
5614 def credentials_sql(self, expression: exp.Credentials) -> str: 5615 cred_expr = expression.args.get("credentials") 5616 if isinstance(cred_expr, exp.Literal): 5617 # Redshift case: CREDENTIALS <string> 5618 credentials = self.sql(expression, "credentials") 5619 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5620 else: 5621 # Snowflake case: CREDENTIALS = (...) 5622 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5623 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5624 5625 storage = self.sql(expression, "storage") 5626 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5627 5628 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5629 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5630 5631 iam_role = self.sql(expression, "iam_role") 5632 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5633 5634 region = self.sql(expression, "region") 5635 region = f" REGION {region}" if region else "" 5636 5637 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5639 def copy_sql(self, expression: exp.Copy) -> str: 5640 this = self.sql(expression, "this") 5641 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5642 5643 credentials = self.sql(expression, "credentials") 5644 credentials = self.seg(credentials) if credentials else "" 5645 files = self.expressions(expression, key="files", flat=True) 5646 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5647 5648 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5649 params = self.expressions( 5650 expression, 5651 key="params", 5652 sep=sep, 5653 new_line=True, 5654 skip_last=True, 5655 skip_first=True, 5656 indent=self.COPY_PARAMS_ARE_WRAPPED, 5657 ) 5658 5659 if params: 5660 if self.COPY_PARAMS_ARE_WRAPPED: 5661 params = f" WITH ({params})" 5662 elif not self.pretty and (files or credentials): 5663 params = f" {params}" 5664 5665 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5670 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5671 on_sql = "ON" if expression.args.get("on") else "OFF" 5672 filter_col: str | None = self.sql(expression, "filter_column") 5673 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5674 retention_period: str | None = self.sql(expression, "retention_period") 5675 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5676 5677 if filter_col or retention_period: 5678 on_sql = self.func("ON", filter_col, retention_period) 5679 5680 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5682 def maskingpolicycolumnconstraint_sql( 5683 self, expression: exp.MaskingPolicyColumnConstraint 5684 ) -> str: 5685 this = self.sql(expression, "this") 5686 expressions = self.expressions(expression, flat=True) 5687 expressions = f" USING ({expressions})" if expressions else "" 5688 return f"MASKING POLICY {this}{expressions}"
5698 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5699 this = self.sql(expression, "this") 5700 expr = expression.expression 5701 5702 if isinstance(expr, exp.Func): 5703 # T-SQL's CLR functions are case sensitive 5704 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5705 else: 5706 expr = self.sql(expression, "expression") 5707 5708 return self.scope_resolution(expr, this)
5716 def rand_sql(self, expression: exp.Rand) -> str: 5717 lower = self.sql(expression, "lower") 5718 upper = self.sql(expression, "upper") 5719 5720 if lower and upper: 5721 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5722 return self.func("RAND", expression.this)
5724 def changes_sql(self, expression: exp.Changes) -> str: 5725 information = self.sql(expression, "information") 5726 information = f"INFORMATION => {information}" 5727 at_before = self.sql(expression, "at_before") 5728 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5729 end = self.sql(expression, "end") 5730 end = f"{self.seg('')}{end}" if end else "" 5731 5732 return f"CHANGES ({information}){at_before}{end}"
5734 def pad_sql(self, expression: exp.Pad) -> str: 5735 prefix = "L" if expression.args.get("is_left") else "R" 5736 5737 fill_pattern = self.sql(expression, "fill_pattern") or None 5738 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5739 fill_pattern = "' '" 5740 5741 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5747 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5748 generate_series = exp.GenerateSeries(**expression.args) 5749 5750 parent = expression.parent 5751 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5752 parent = parent.parent 5753 5754 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5755 return self.sql(exp.Unnest(expressions=[generate_series])) 5756 5757 if isinstance(parent, exp.Select): 5758 self.unsupported("GenerateSeries projection unnesting is not supported.") 5759 5760 return self.sql(generate_series)
5762 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5763 if self.SUPPORTS_CONVERT_TIMEZONE: 5764 return self.function_fallback_sql(expression) 5765 5766 source_tz = expression.args.get("source_tz") 5767 target_tz = expression.args.get("target_tz") 5768 timestamp = expression.args.get("timestamp") 5769 5770 if source_tz and timestamp: 5771 timestamp = exp.AtTimeZone( 5772 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5773 ) 5774 5775 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5776 5777 return self.sql(expr)
5779 def json_sql(self, expression: exp.JSON) -> str: 5780 this = self.sql(expression, "this") 5781 this = f" {this}" if this else "" 5782 5783 _with = expression.args.get("with_") 5784 5785 if _with is None: 5786 with_sql = "" 5787 elif not _with: 5788 with_sql = " WITHOUT" 5789 else: 5790 with_sql = " WITH" 5791 5792 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5793 5794 return f"JSON{this}{with_sql}{unique_sql}"
5796 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5797 path = self.sql(expression, "path") 5798 returning = self.sql(expression, "returning") 5799 returning = f" RETURNING {returning}" if returning else "" 5800 5801 on_condition = self.sql(expression, "on_condition") 5802 on_condition = f" {on_condition}" if on_condition else "" 5803 5804 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5810 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5811 else_ = "ELSE " if expression.args.get("else_") else "" 5812 condition = self.sql(expression, "expression") 5813 condition = f"WHEN {condition} THEN " if condition else else_ 5814 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5815 return f"{condition}{insert}"
5817 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5818 kind = self.sql(expression, "kind") 5819 source = self.sql(expression, "source") 5820 5821 if kind: 5822 expressions = self.seg(self.expressions(expression, sep=" ")) 5823 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5824 else: 5825 # If kind isn't present, we have Hive's FROM-first form: 5826 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5827 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5828 sql = f"FROM {source}{self.seg(inserts)}" 5829 5830 return self.prepend_ctes(expression, sql)
5832 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5833 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5834 empty = expression.args.get("empty") 5835 empty = ( 5836 f"DEFAULT {empty} ON EMPTY" 5837 if isinstance(empty, exp.Expr) 5838 else self.sql(expression, "empty") 5839 ) 5840 5841 error = expression.args.get("error") 5842 error = ( 5843 f"DEFAULT {error} ON ERROR" 5844 if isinstance(error, exp.Expr) 5845 else self.sql(expression, "error") 5846 ) 5847 5848 if error and empty: 5849 error = ( 5850 f"{empty} {error}" 5851 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5852 else f"{error} {empty}" 5853 ) 5854 empty = "" 5855 5856 null = self.sql(expression, "null") 5857 5858 return f"{empty}{error}{null}"
5864 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5865 this = self.sql(expression, "this") 5866 path = self.sql(expression, "path") 5867 5868 passing = self.expressions(expression, "passing") 5869 passing = f" PASSING {passing}" if passing else "" 5870 5871 on_condition = self.sql(expression, "on_condition") 5872 on_condition = f" {on_condition}" if on_condition else "" 5873 5874 path = f"{path}{passing}{on_condition}" 5875 5876 return self.func("JSON_EXISTS", this, path)
5918 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5919 array_agg = self.function_fallback_sql(expression) 5920 column_expr = expression.this 5921 if isinstance(column_expr, exp.Order): 5922 column_expr = column_expr.this 5923 5924 return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
6005 def overlay_sql(self, expression: exp.Overlay) -> str: 6006 this = self.sql(expression, "this") 6007 expr = self.sql(expression, "expression") 6008 from_sql = self.sql(expression, "from_") 6009 for_sql = self.sql(expression, "for_") 6010 for_sql = f" FOR {for_sql}" if for_sql else "" 6011 6012 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
6019 def string_sql(self, expression: exp.String) -> str: 6020 this = expression.this 6021 zone = expression.args.get("zone") 6022 6023 if zone: 6024 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6025 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6026 # set for source_tz to transpile the time conversion before the STRING cast 6027 this = exp.ConvertTimezone( 6028 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6029 ) 6030 6031 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
6041 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6042 filler = self.sql(expression, "this") 6043 filler = f" {filler}" if filler else "" 6044 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6045 return f"TRUNCATE{filler} {with_count}"
6047 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6048 if self.SUPPORTS_UNIX_SECONDS: 6049 return self.function_fallback_sql(expression) 6050 6051 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6052 6053 return self.sql( 6054 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6055 )
6057 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6058 dim = expression.expression 6059 6060 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6061 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6062 if not (dim.is_int and dim.name == "1"): 6063 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6064 dim = None 6065 6066 # If dimension is required but not specified, default initialize it 6067 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6068 dim = exp.Literal.number(1) 6069 6070 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
6072 def attach_sql(self, expression: exp.Attach) -> str: 6073 this = self.sql(expression, "this") 6074 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6075 expressions = self.expressions(expression) 6076 expressions = f" ({expressions})" if expressions else "" 6077 6078 return f"ATTACH{exists_sql} {this}{expressions}"
6080 def detach_sql(self, expression: exp.Detach) -> str: 6081 kind = self.sql(expression, "kind") 6082 kind = f" {kind}" if kind else "" 6083 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6084 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6085 exists = " IF EXISTS" if expression.args.get("exists") else "" 6086 if exists: 6087 kind = kind or " DATABASE" 6088 6089 this = self.sql(expression, "this") 6090 this = f" {this}" if this else "" 6091 cluster = self.sql(expression, "cluster") 6092 cluster = f" {cluster}" if cluster else "" 6093 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6094 sync = " SYNC" if expression.args.get("sync") else "" 6095 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
6108 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6109 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6110 encode = f"{encode} {self.sql(expression, 'this')}" 6111 6112 properties = expression.args.get("properties") 6113 if properties: 6114 encode = f"{encode} {self.properties(properties)}" 6115 6116 return encode
6118 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6119 this = self.sql(expression, "this") 6120 include = f"INCLUDE {this}" 6121 6122 column_def = self.sql(expression, "column_def") 6123 if column_def: 6124 include = f"{include} {column_def}" 6125 6126 alias = self.sql(expression, "alias") 6127 if alias: 6128 include = f"{include} AS {alias}" 6129 6130 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
6143 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6144 partitions = self.expressions(expression, "partition_expressions") 6145 create = self.expressions(expression, "create_expressions") 6146 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
6148 def partitionbyrangepropertydynamic_sql( 6149 self, expression: exp.PartitionByRangePropertyDynamic 6150 ) -> str: 6151 start = self.sql(expression, "start") 6152 end = self.sql(expression, "end") 6153 6154 every = expression.args["every"] 6155 if isinstance(every, exp.Interval) and every.this.is_string: 6156 every.this.replace(exp.Literal.number(every.name)) 6157 6158 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6171 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6172 kind = self.sql(expression, "kind") 6173 option = self.sql(expression, "option") 6174 option = f" {option}" if option else "" 6175 this = self.sql(expression, "this") 6176 this = f" {this}" if this else "" 6177 columns = self.expressions(expression) 6178 columns = f" {columns}" if columns else "" 6179 return f"{kind}{option} STATISTICS{this}{columns}"
6181 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6182 this = self.sql(expression, "this") 6183 columns = self.expressions(expression) 6184 inner_expression = self.sql(expression, "expression") 6185 inner_expression = f" {inner_expression}" if inner_expression else "" 6186 update_options = self.sql(expression, "update_options") 6187 update_options = f" {update_options} UPDATE" if update_options else "" 6188 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
6199 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6200 kind = self.sql(expression, "kind") 6201 this = self.sql(expression, "this") 6202 this = f" {this}" if this else "" 6203 inner_expression = self.sql(expression, "expression") 6204 return f"VALIDATE {kind}{this}{inner_expression}"
6206 def analyze_sql(self, expression: exp.Analyze) -> str: 6207 options = self.expressions(expression, key="options", sep=" ") 6208 options = f" {options}" if options else "" 6209 kind = self.sql(expression, "kind") 6210 kind = f" {kind}" if kind else "" 6211 tables = self.expressions(expression, key="tables", flat=True) 6212 tables = f" {tables}" if tables else "" 6213 mode = self.sql(expression, "mode") 6214 mode = f" {mode}" if mode else "" 6215 properties = self.sql(expression, "properties") 6216 properties = f" {properties}" if properties else "" 6217 partition = self.sql(expression, "partition") 6218 partition = f" {partition}" if partition else "" 6219 inner_expression = self.sql(expression, "expression") 6220 inner_expression = f" {inner_expression}" if inner_expression else "" 6221 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6223 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6224 this = self.sql(expression, "this") 6225 namespaces = self.expressions(expression, key="namespaces") 6226 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6227 passing = self.expressions(expression, key="passing") 6228 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6229 columns = self.expressions(expression, key="columns") 6230 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6231 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6232 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6238 def export_sql(self, expression: exp.Export) -> str: 6239 this = self.sql(expression, "this") 6240 connection = self.sql(expression, "connection") 6241 connection = f"WITH CONNECTION {connection} " if connection else "" 6242 options = self.sql(expression, "options") 6243 return f"EXPORT DATA {connection}{options} AS {this}"
6249 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6250 variables = self.expressions(expression, "this") 6251 default = self.sql(expression, "default") 6252 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6253 6254 kind = self.sql(expression, "kind") 6255 if isinstance(expression.args.get("kind"), exp.Schema): 6256 kind = f"TABLE {kind}" 6257 6258 kind = f" {kind}" if kind else "" 6259 6260 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
6262 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6263 kind = self.sql(expression, "kind") 6264 this = self.sql(expression, "this") 6265 set = self.sql(expression, "expression") 6266 to = self.sql(expression, "to") 6267 to = f" TO {to}" if to else "" 6268 default = self.sql(expression, "default") 6269 default = f" DEFAULT {default}" if default else "" 6270 using = self.sql(expression, "using") 6271 using = f" USING {using}" if using else "" 6272 6273 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6274 6275 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:
6303 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6304 # Snowflake GET/PUT statements: 6305 # PUT <file> <internalStage> <properties> 6306 # GET <internalStage> <file> <properties> 6307 props = expression.args.get("properties") 6308 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6309 this = self.sql(expression, "this") 6310 target = self.sql(expression, "target") 6311 6312 if isinstance(expression, exp.Put): 6313 return f"PUT {this} {target}{props_sql}" 6314 else: 6315 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
6317 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6318 this = self.sql(expression, "this") 6319 expr = self.sql(expression, "expression") 6320 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6321 return f"TRANSLATE({this} USING {expr}{with_error})"
6323 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6324 if self.SUPPORTS_DECODE_CASE: 6325 return self.func("DECODE", *expression.expressions) 6326 6327 decode_expr, *expressions = expression.expressions 6328 6329 ifs = [] 6330 for search, result in zip(expressions[::2], expressions[1::2]): 6331 if isinstance(search, exp.Literal): 6332 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6333 elif isinstance(search, exp.Null): 6334 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6335 else: 6336 if isinstance(search, exp.Binary): 6337 search = exp.paren(search) 6338 6339 cond = exp.or_( 6340 decode_expr.eq(search), 6341 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6342 copy=False, 6343 ) 6344 ifs.append(exp.If(this=cond, true=result)) 6345 6346 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6347 return self.sql(case)
6349 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6350 this = self.sql(expression, "this") 6351 this = self.seg(this, sep="") 6352 dimensions = self.expressions( 6353 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6354 ) 6355 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6356 metrics = self.expressions( 6357 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6358 ) 6359 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6360 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6361 facts = self.seg(f"FACTS {facts}") if facts else "" 6362 where = self.sql(expression, "where") 6363 where = self.seg(f"WHERE {where}") if where else "" 6364 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6365 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6367 def getextract_sql(self, expression: exp.GetExtract) -> str: 6368 this = expression.this 6369 expr = expression.expression 6370 6371 if not this.type or not expression.type: 6372 import sqlglot.optimizer.annotate_types 6373 6374 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6375 6376 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6377 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6378 6379 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6396 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6397 method = self.sql(expression, "method") 6398 kind = expression.args.get("kind") 6399 if not kind: 6400 return f"REFRESH {method}" 6401 6402 every = self.sql(expression, "every") 6403 unit = self.sql(expression, "unit") 6404 every = f" EVERY {every} {unit}" if every else "" 6405 starts = self.sql(expression, "starts") 6406 starts = f" STARTS {starts}" if starts else "" 6407 6408 return f"REFRESH {method} ON {kind}{every}{starts}"
6417 def uuid_sql(self, expression: exp.Uuid) -> str: 6418 is_string = expression.args.get("is_string", False) 6419 uuid_func_sql = self.func("UUID") 6420 6421 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6422 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6423 6424 return uuid_func_sql
6426 def initcap_sql(self, expression: exp.Initcap) -> str: 6427 delimiters = expression.expression 6428 6429 if delimiters: 6430 # do not generate delimiters arg if we are round-tripping from default delimiters 6431 if ( 6432 delimiters.is_string 6433 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6434 ): 6435 delimiters = None 6436 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6437 self.unsupported("INITCAP does not support custom delimiters") 6438 delimiters = None 6439 6440 return self.func("INITCAP", expression.this, delimiters)
6450 def weekstart_name(self, expression: exp.WeekStart) -> str: 6451 import sqlglot.dialects.dialect 6452 6453 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6454 this = expression.this.name.upper() 6455 6456 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6457 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6458 6459 if dow_from_week_start_day != dow_from_week_offset: 6460 self.unsupported( 6461 f"WEEK({this}) is not supported; falling back to the default week start day" 6462 ) 6463 6464 return "WEEK"
6466 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6467 name = self.weekstart_name(expression) 6468 6469 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6470 if isinstance(expression.parent, exp.DateTrunc): 6471 return self.sql(exp.Literal.string(name)) 6472 6473 return name
def
functionspecification_sql(self, expression: sqlglot.expressions.query.FunctionSpecification) -> str:
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: