sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from decimal import Decimal 8from functools import reduce, wraps 9 10from sqlglot import exp 11from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 12from sqlglot.expressions import apply_index_offset 13from sqlglot.expressions.core import maybe_parse 14from sqlglot.helper import csv, name_sequence, seq_get 15from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 16from sqlglot.time import format_time 17from sqlglot.tokens import TokenType 18 19if t.TYPE_CHECKING: 20 from sqlglot._typing import E 21 from sqlglot.dialects.dialect import DialectType 22 23 G = t.TypeVar("G", bound="Generator") 24 GeneratorMethod = t.Callable[[G, E], str] 25 26logger = logging.getLogger("sqlglot") 27 28_HIGH_BYTE_RE = re.compile(r"[\x80-\xff]") 29 30ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 31UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 32 33 34def unsupported_args( 35 *args: str | tuple[str, str], 36) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 37 """ 38 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 39 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 40 """ 41 diagnostic_by_arg: dict[str, str | None] = {} 42 for arg in args: 43 if isinstance(arg, str): 44 diagnostic_by_arg[arg] = None 45 else: 46 diagnostic_by_arg[arg[0]] = arg[1] 47 48 def decorator(func: GeneratorMethod) -> GeneratorMethod: 49 @wraps(func) 50 def _func(generator: G, expression: E) -> str: 51 expression_name = expression.__class__.__name__ 52 dialect_name = generator.dialect.__class__.__name__ 53 54 for arg_name, diagnostic in diagnostic_by_arg.items(): 55 if expression.args.get(arg_name): 56 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 57 arg_name, expression_name, dialect_name 58 ) 59 generator.unsupported(diagnostic) 60 61 return func(generator, expression) 62 63 return _func 64 65 return decorator 66 67 68AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = { 69 "windows": lambda self, e: ( 70 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 71 if e.args.get("windows") 72 else "" 73 ), 74 "qualify": lambda self, e: self.sql(e, "qualify"), 75} 76 77 78_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {} 79 80 81def _build_dispatch( 82 cls: type[Generator], 83) -> dict[type[exp.Expr], t.Callable[..., str]]: 84 dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS) 85 86 for attr_name in dir(cls): 87 if not attr_name.endswith("_sql") or attr_name.startswith("_"): 88 continue 89 90 expr_key = attr_name[:-4] 91 expr_cls = exp.EXPR_CLASSES.get(expr_key) 92 93 if expr_cls and expr_cls not in dispatch: 94 dispatch[expr_cls] = getattr(cls, attr_name) 95 96 return dispatch 97 98 99class Generator: 100 """ 101 Generator converts a given syntax tree to the corresponding SQL string. 102 103 Args: 104 pretty: Whether to format the produced SQL string. 105 Default: False. 106 identify: Determines when an identifier should be quoted. Possible values are: 107 False (default): Never quote, except in cases where it's mandatory by the dialect. 108 True: Always quote except for specials cases. 109 'safe': Only quote identifiers that are case insensitive. 110 normalize: Whether to normalize identifiers to lowercase. 111 Default: False. 112 pad: The pad size in a formatted string. For example, this affects the indentation of 113 a projection in a query, relative to its nesting level. 114 Default: 2. 115 indent: The indentation size in a formatted string. For example, this affects the 116 indentation of subqueries and filters under a `WHERE` clause. 117 Default: 2. 118 normalize_functions: How to normalize function names. Possible values are: 119 "upper" or True (default): Convert names to uppercase. 120 "lower": Convert names to lowercase. 121 False: Disables function name normalization. 122 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 123 Default ErrorLevel.WARN. 124 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 125 This is only relevant if unsupported_level is ErrorLevel.RAISE. 126 Default: 3 127 leading_comma: Whether the comma is leading or trailing in select expressions. 128 This is only relevant when generating in pretty mode. 129 Default: False 130 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 131 The default is on the smaller end because the length only represents a segment and not the true 132 line length. 133 Default: 80 134 comments: Whether to preserve comments in the output SQL code. 135 Default: True 136 """ 137 138 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 139 **JSON_PATH_PART_TRANSFORMS, 140 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 141 exp.AllowedValuesProperty: lambda self, e: ( 142 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 143 ), 144 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 145 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 146 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 147 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 148 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 149 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 150 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 151 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 152 exp.BinaryColumnConstraint: lambda *_: "BINARY", 153 exp.CaseSpecificColumnConstraint: lambda _, e: ( 154 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 155 ), 156 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 157 exp.Ceil: lambda self, e: self.ceil_floor(e), 158 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 159 exp.CharacterSetProperty: lambda self, e: ( 160 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 161 ), 162 exp.ClusteredColumnConstraint: lambda self, e: ( 163 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 164 ), 165 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 166 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 167 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 168 exp.ConvertToCharset: lambda self, e: self.func( 169 "CONVERT", e.this, e.args["dest"], e.args.get("source") 170 ), 171 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 172 exp.CopyTagsProperty: lambda *_: "COPY TAGS", 173 exp.CredentialsProperty: lambda self, e: ( 174 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 175 ), 176 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 177 exp.SessionUser: lambda *_: "SESSION_USER", 178 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 179 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 180 exp.ApiProperty: lambda *_: "API", 181 exp.ApplicationProperty: lambda *_: "APPLICATION", 182 exp.CatalogProperty: lambda *_: "CATALOG", 183 exp.ComputeProperty: lambda *_: "COMPUTE", 184 exp.DatabaseProperty: lambda *_: "DATABASE", 185 exp.DynamicProperty: lambda *_: "DYNAMIC", 186 exp.EmptyProperty: lambda *_: "EMPTY", 187 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 188 exp.EndStatement: lambda *_: "END", 189 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 190 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 191 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 192 exp.EphemeralColumnConstraint: lambda self, e: ( 193 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 194 ), 195 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 196 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 197 exp.Except: lambda self, e: self.set_operations(e), 198 exp.ExternalProperty: lambda *_: "EXTERNAL", 199 exp.Floor: lambda self, e: self.ceil_floor(e), 200 exp.Get: lambda self, e: self.get_put_sql(e), 201 exp.GlobalProperty: lambda *_: "GLOBAL", 202 exp.HeapProperty: lambda *_: "HEAP", 203 exp.HybridProperty: lambda *_: "HYBRID", 204 exp.IcebergProperty: lambda *_: "ICEBERG", 205 exp.IgnoreGrantsProperty: lambda *_: "IGNORE GRANTS", 206 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 207 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 208 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 209 exp.Intersect: lambda self, e: self.set_operations(e), 210 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 211 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 212 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 213 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 214 exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"), 215 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 216 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 217 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 218 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 219 exp.LanguageProperty: lambda self, e: self.naked_property(e), 220 exp.LocationProperty: lambda self, e: self.naked_property(e), 221 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 222 exp.MaskingProperty: lambda *_: "MASKING", 223 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 224 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 225 exp.NetworkProperty: lambda *_: "NETWORK", 226 exp.NonClusteredColumnConstraint: lambda self, e: ( 227 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 228 ), 229 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 230 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 231 exp.OnCommitProperty: lambda _, e: ( 232 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 233 ), 234 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 235 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 236 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 237 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 238 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 239 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 240 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 241 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 242 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 243 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 244 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 245 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 246 f"PROJECTION POLICY {self.sql(e, 'this')}" 247 ), 248 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 249 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 250 exp.Put: lambda self, e: self.get_put_sql(e), 251 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 252 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 253 ), 254 exp.ReturnsProperty: lambda self, e: ( 255 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 256 ), 257 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 258 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 259 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 260 exp.SecureProperty: lambda *_: "SECURE", 261 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 262 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 263 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 264 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 265 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 266 exp.SqlReadWriteProperty: lambda _, e: e.name, 267 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 268 exp.StabilityProperty: lambda _, e: e.name, 269 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 270 exp.StreamingTableProperty: lambda *_: "STREAMING", 271 exp.StrictProperty: lambda *_: "STRICT", 272 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 273 exp.TableColumn: lambda self, e: self.sql(e.this), 274 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 275 exp.TemporaryProperty: lambda *_: "TEMPORARY", 276 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 277 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 278 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 279 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 280 exp.TransientProperty: lambda *_: "TRANSIENT", 281 exp.VirtualProperty: lambda *_: "VIRTUAL", 282 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 283 exp.Union: lambda self, e: self.set_operations(e), 284 exp.UnloggedProperty: lambda *_: "UNLOGGED", 285 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 286 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 287 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 288 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 289 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 290 exp.UtcTimestamp: lambda self, e: self.sql( 291 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 292 ), 293 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 294 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 295 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 296 exp.VolatileProperty: lambda *_: "VOLATILE", 297 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 298 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 299 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 300 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 301 exp.WithoutRowidProperty: lambda *_: "WITHOUT ROWID", 302 exp.ForceProperty: lambda *_: "FORCE", 303 } 304 305 # Whether null ordering is supported in order by 306 # True: Full Support, None: No support, False: No support for certain cases 307 # such as window specifications, aggregate functions etc 308 NULL_ORDERING_SUPPORTED: bool | None = True 309 310 # Window functions that support NULLS FIRST/LAST 311 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 312 313 # Whether ignore nulls is inside the agg or outside. 314 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 315 IGNORE_NULLS_IN_FUNC = False 316 317 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 318 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 319 IGNORE_NULLS_BEFORE_ORDER = True 320 321 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 322 LOCKING_READS_SUPPORTED = False 323 324 # Whether the EXCEPT and INTERSECT operations can return duplicates 325 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 326 327 # Wrap derived values in parens, usually standard but spark doesn't support it 328 WRAP_DERIVED_VALUES = True 329 330 # Whether create function uses an AS before the RETURN 331 CREATE_FUNCTION_RETURN_AS = True 332 333 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 334 MATCHED_BY_SOURCE = True 335 336 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 337 SUPPORTS_MERGE_WHERE = False 338 339 # Whether MERGE uses ALL BY NAME instead of a star to update or insert all columns 340 MERGE_ALL_BY_NAME = False 341 342 # Whether the INTERVAL expression works only with values like '1 day' 343 SINGLE_STRING_INTERVAL = False 344 345 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 346 INTERVAL_ALLOWS_PLURAL_FORM = True 347 348 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 349 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 350 AUTO_REFRESH_BARE_INTERVALS = False 351 352 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 353 LIMIT_FETCH = "ALL" 354 355 # Whether limit and fetch allows expressions or just limits 356 LIMIT_ONLY_LITERALS = False 357 358 # Whether a table is allowed to be renamed with a db 359 RENAME_TABLE_WITH_DB = True 360 361 # The separator for grouping sets and rollups 362 GROUPINGS_SEP = "," 363 364 # Whether GROUPING SETS can follow GROUP BY expressions without a comma 365 SUPPORTS_GROUPING_SETS_AS_SUFFIX = False 366 367 # The string used for creating an index on a table 368 INDEX_ON = "ON" 369 370 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 371 INOUT_SEPARATOR = " " 372 373 # Whether join hints should be generated 374 JOIN_HINTS = True 375 376 # Whether directed joins are supported 377 DIRECTED_JOINS = False 378 379 # Whether top-k distance/similarity joins are supported 380 SUPPORTS_NEAREST_JOIN = False 381 382 # Whether table hints should be generated 383 TABLE_HINTS = True 384 385 # Whether query hints should be generated 386 QUERY_HINTS = True 387 388 # What kind of separator to use for query hints 389 QUERY_HINT_SEP = ", " 390 391 # Whether comparing against booleans (e.g. x IS TRUE) is supported 392 IS_BOOL_ALLOWED = True 393 394 # Whether casting a fractional number to an integer rounds it instead of truncating it 395 CAST_TO_INT_ROUNDS = False 396 397 # Whether the native integer division operator returns NULL for a zero divisor instead of raising 398 SAFE_INT_DIVISION = False 399 400 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 401 DUPLICATE_KEY_UPDATE_WITH_SET = True 402 403 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 404 LIMIT_IS_TOP = False 405 406 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 407 RETURNING_END = True 408 409 # Whether to generate an unquoted value for EXTRACT's date part argument 410 EXTRACT_ALLOWS_QUOTES = True 411 412 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 413 TZ_TO_WITH_TIME_ZONE = False 414 415 # Whether the NVL2 function is supported 416 NVL2_SUPPORTED = True 417 418 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 419 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 420 421 # Whether VALUES statements can be used as derived tables. 422 # MySQL 5 and Redshift do not allow this, so when False, it will convert 423 # SELECT * VALUES into SELECT UNION 424 VALUES_AS_TABLE = True 425 426 # Whether COLUMN/COLUMNS keywords are included when adding columns with ALTER TABLE. 427 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 428 429 # Whether ALTER TABLE mutli-column additions are wrapped in parentheses. 430 ALTER_TABLE_ADD_COLUMNS_WRAPPED = True 431 432 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 433 UNNEST_WITH_ORDINALITY = True 434 435 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 436 SEMI_ANTI_JOIN_WITH_SIDE = True 437 438 # Whether to include the type of a computed column in the CREATE DDL 439 COMPUTED_COLUMN_WITH_TYPE = True 440 441 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 442 SUPPORTS_TABLE_COPY = True 443 444 # Whether parentheses are required around the table sample's expression 445 TABLESAMPLE_REQUIRES_PARENS = True 446 447 # Whether a table sample clause's size needs to be followed by the ROWS keyword 448 TABLESAMPLE_SIZE_IS_ROWS = True 449 450 # The keyword(s) to use when generating a sample clause 451 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 452 453 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 454 TABLESAMPLE_WITH_METHOD = True 455 456 # The keyword to use when specifying the seed of a sample clause 457 TABLESAMPLE_SEED_KEYWORD = "SEED" 458 459 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 460 HISTORICAL_DATA_POST_ALIAS = False 461 462 # Whether COLLATE is a function instead of a binary operator 463 COLLATE_IS_FUNC = False 464 465 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 466 DATA_TYPE_SPECIFIERS_ALLOWED = False 467 468 # Whether conditions require booleans WHERE x = 0 vs WHERE x 469 ENSURE_BOOLS = False 470 471 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 472 CTE_RECURSIVE_KEYWORD_REQUIRED = True 473 474 # Whether CONCAT requires >1 arguments 475 SUPPORTS_SINGLE_ARG_CONCAT = True 476 477 # Whether LAST_DAY function supports a date part argument 478 LAST_DAY_SUPPORTS_DATE_PART = True 479 480 # Whether named columns are allowed in table aliases 481 SUPPORTS_TABLE_ALIAS_COLUMNS = True 482 483 # Whether named columns are allowed in CTE definitions 484 SUPPORTS_NAMED_CTE_COLUMNS = True 485 486 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 487 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 488 489 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 490 PIVOT_ALIAS_WITH_AS = True 491 492 # What delimiter to use for separating JSON key/value pairs 493 JSON_KEY_VALUE_PAIR_SEP = ":" 494 495 # INSERT OVERWRITE TABLE x override 496 INSERT_OVERWRITE = " OVERWRITE TABLE" 497 498 # Whether the SELECT .. INTO syntax is used instead of CTAS 499 SUPPORTS_SELECT_INTO = False 500 501 # Whether UNLOGGED tables can be created 502 SUPPORTS_UNLOGGED_TABLES = False 503 504 # Whether the CREATE TABLE LIKE statement is supported 505 SUPPORTS_CREATE_TABLE_LIKE = True 506 507 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 508 SUPPORTS_MODIFY_COLUMN = False 509 510 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 511 SUPPORTS_CHANGE_COLUMN = False 512 513 # Whether ALTER COLUMN can set a column's nullability together with its type 514 SUPPORTS_ALTER_COLUMN_NULLABILITY = False 515 516 # Whether ALTER COLUMN IF EXISTS is supported 517 SUPPORTS_ALTER_COLUMN_IF_EXISTS = False 518 519 # Whether the LikeProperty needs to be specified inside of the schema clause 520 LIKE_PROPERTY_INSIDE_SCHEMA = False 521 522 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 523 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 524 MULTI_ARG_DISTINCT = True 525 526 # Whether the JSON extraction operators expect a value of type JSON 527 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 528 529 # Whether bracketed keys like ["foo"] are supported in JSON paths 530 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 531 532 # Whether to escape keys using single quotes in JSON paths 533 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 534 535 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 536 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 537 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 538 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 539 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 540 541 # The JSONPathPart expressions supported by this dialect 542 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 543 544 # Whether any(f(x) for x in array) can be implemented by this dialect 545 CAN_IMPLEMENT_ARRAY_ANY = False 546 547 # Whether the function TO_NUMBER is supported 548 SUPPORTS_TO_NUMBER = True 549 550 # Whether EXCLUDE in window specification is supported 551 SUPPORTS_WINDOW_EXCLUDE = False 552 553 # Whether or not set op modifiers apply to the outer set op or select. 554 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 555 # True means limit 1 happens after the set op, False means it it happens on y. 556 SET_OP_MODIFIERS = True 557 558 # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses. 559 SET_OP_LIMITS = False 560 561 # Whether set operation operands can be parenthesized without a SELECT wrapper. 562 SET_OP_PARENTHESIZED_OPERANDS = True 563 564 # Whether parameters from COPY statement are wrapped in parentheses 565 COPY_PARAMS_ARE_WRAPPED = True 566 567 # Whether values of params are set with "=" token or empty space 568 COPY_PARAMS_EQ_REQUIRED = False 569 570 # Whether COPY statement has INTO keyword 571 COPY_HAS_INTO_KEYWORD = True 572 573 # Whether the conditional TRY(expression) function is supported 574 TRY_SUPPORTED = True 575 576 # Whether the UESCAPE syntax in unicode strings is supported 577 SUPPORTS_UESCAPE = True 578 579 # Function used to replace escaped unicode codes in unicode strings 580 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 581 582 # The keyword to use when generating a star projection with excluded columns 583 STAR_EXCEPT = "EXCEPT" 584 585 # The HEX function name 586 HEX_FUNC = "HEX" 587 588 # Function that constructs a binary value from a hex-encoded byte string. 589 BYTE_STRING_FUNCTION = "" 590 591 # The keywords to use when prefixing & separating WITH based properties 592 WITH_PROPERTIES_PREFIX = "WITH" 593 594 # Whether to quote the generated expression of exp.JsonPath 595 QUOTE_JSON_PATH = True 596 597 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 598 PAD_FILL_PATTERN_IS_REQUIRED = False 599 600 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 601 SUPPORTS_EXPLODING_PROJECTIONS = True 602 603 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 604 ARRAY_CONCAT_IS_VAR_LEN = True 605 606 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 607 SUPPORTS_CONVERT_TIMEZONE = False 608 609 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 610 SUPPORTS_MEDIAN = True 611 612 # Whether UNIX_SECONDS(timestamp) is supported 613 SUPPORTS_UNIX_SECONDS = False 614 615 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 616 ALTER_SET_WRAPPED = False 617 618 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 619 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 620 # TODO: The normalization should be done by default once we've tested it across all dialects. 621 NORMALIZE_EXTRACT_DATE_PARTS = False 622 623 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 624 PARSE_JSON_NAME: str | None = "PARSE_JSON" 625 626 # The function name of the exp.ArraySize expression 627 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 628 629 # The syntax to use when altering the type of a column 630 ALTER_SET_TYPE = "SET DATA TYPE" 631 632 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 633 # None -> Doesn't support it at all 634 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 635 # True (Postgres) -> Explicitly requires it 636 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 637 638 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 639 SUPPORTS_DECODE_CASE = True 640 641 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 642 SUPPORTS_BETWEEN_FLAGS = False 643 644 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 645 SUPPORTS_LIKE_QUANTIFIERS = True 646 647 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 648 MATCH_AGAINST_TABLE_PREFIX: str | None = None 649 650 # Whether to include the VARIABLE keyword for SET assignments 651 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 652 653 # The keyword to use for default value assignment in DECLARE statements 654 DECLARE_DEFAULT_ASSIGNMENT = "=" 655 656 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 657 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 658 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 659 UPDATE_STATEMENT_SUPPORTS_FROM = True 660 661 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 662 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 663 664 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 665 # - Snowflake: DROP ICEBERG TABLE a.b; 666 # - DuckDB: DROP TABLE a.b; 667 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 668 669 TYPE_MAPPING: t.ClassVar = { 670 exp.DType.DATETIME2: "TIMESTAMP", 671 exp.DType.NCHAR: "CHAR", 672 exp.DType.NVARCHAR: "VARCHAR", 673 exp.DType.MEDIUMTEXT: "TEXT", 674 exp.DType.LONGTEXT: "TEXT", 675 exp.DType.TINYTEXT: "TEXT", 676 exp.DType.BLOB: "VARBINARY", 677 exp.DType.MEDIUMBLOB: "BLOB", 678 exp.DType.LONGBLOB: "BLOB", 679 exp.DType.TINYBLOB: "BLOB", 680 exp.DType.INET: "INET", 681 exp.DType.ROWVERSION: "VARBINARY", 682 exp.DType.SMALLDATETIME: "TIMESTAMP", 683 } 684 685 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 686 687 # mapping of DType to its default parameters, bounds 688 TYPE_PARAM_SETTINGS: t.ClassVar[ 689 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 690 ] = {} 691 692 TIME_PART_SINGULARS: t.ClassVar = { 693 "MICROSECONDS": "MICROSECOND", 694 "SECONDS": "SECOND", 695 "MINUTES": "MINUTE", 696 "HOURS": "HOUR", 697 "DAYS": "DAY", 698 "WEEKS": "WEEK", 699 "MONTHS": "MONTH", 700 "QUARTERS": "QUARTER", 701 "YEARS": "YEAR", 702 } 703 704 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 705 "cluster": lambda self, e: self.sql(e, "cluster"), 706 "distribute": lambda self, e: self.sql(e, "distribute"), 707 "sort": lambda self, e: self.sql(e, "sort"), 708 **AFTER_HAVING_MODIFIER_TRANSFORMS, 709 } 710 711 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 712 713 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 714 715 PARAMETER_TOKEN = "@" 716 NAMED_PLACEHOLDER_TOKEN = ":" 717 718 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 719 720 PROPERTIES_LOCATION: t.ClassVar = { 721 exp.AggregateKeyProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 723 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 724 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 725 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 726 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 729 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 730 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 731 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 732 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 734 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 735 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 736 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 737 exp.CopyTagsProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 739 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 744 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 745 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 746 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 747 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 748 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 749 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 750 exp.DistKeyProperty: exp.Properties.Location.UNSUPPORTED, 751 exp.DistStyleProperty: exp.Properties.Location.UNSUPPORTED, 752 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 754 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 759 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 760 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 761 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 762 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 763 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 764 exp.HeapProperty: exp.Properties.Location.POST_WITH, 765 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 766 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 767 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 768 exp.IgnoreGrantsProperty: exp.Properties.Location.POST_SCHEMA, 769 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 770 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 771 exp.InteractiveProperty: exp.Properties.Location.UNSUPPORTED, 772 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 773 exp.JournalProperty: exp.Properties.Location.POST_NAME, 774 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 775 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 776 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 777 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 778 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 779 exp.LogProperty: exp.Properties.Location.POST_NAME, 780 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 781 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 782 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 783 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 784 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 785 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 786 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 787 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 788 exp.OptionsProperty: exp.Properties.Location.UNSUPPORTED, 789 exp.Order: exp.Properties.Location.POST_SCHEMA, 790 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 791 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 792 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 793 exp.PhysicalProperty: exp.Properties.Location.UNSUPPORTED, 794 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 795 exp.Property: exp.Properties.Location.POST_WITH, 796 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 797 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 798 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 799 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 800 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 801 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 802 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 803 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 804 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 805 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 806 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 807 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 808 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 809 exp.Set: exp.Properties.Location.POST_SCHEMA, 810 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 811 exp.SetProperty: exp.Properties.Location.POST_CREATE, 812 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 813 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 814 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 815 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 816 exp.SortKeyProperty: exp.Properties.Location.UNSUPPORTED, 817 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 818 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 819 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 820 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 821 exp.StorageProperty: exp.Properties.Location.UNSUPPORTED, 822 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 823 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 824 exp.Tags: exp.Properties.Location.POST_WITH, 825 exp.TableSpaceProperty: exp.Properties.Location.UNSUPPORTED, 826 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 827 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 828 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 829 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 830 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 831 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 832 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 833 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 834 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 835 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 836 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 837 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 838 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 839 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 840 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 841 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 842 exp.WithoutRowidProperty: exp.Properties.Location.UNSUPPORTED, 843 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 844 } 845 846 # Keywords that can't be used as unquoted identifier names 847 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 848 849 # Exprs whose comments are separated from them for better formatting 850 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 851 exp.Command, 852 exp.Create, 853 exp.Describe, 854 exp.Delete, 855 exp.Drop, 856 exp.From, 857 exp.Insert, 858 exp.Join, 859 exp.MultitableInserts, 860 exp.Order, 861 exp.Group, 862 exp.Having, 863 exp.Select, 864 exp.SetOperation, 865 exp.Update, 866 exp.Where, 867 exp.With, 868 ) 869 870 # Exprs that should not have their comments generated in maybe_comment 871 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 872 exp.Binary, 873 exp.SetOperation, 874 ) 875 876 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 877 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 878 exp.Column, 879 exp.Literal, 880 exp.Neg, 881 exp.Paren, 882 ) 883 884 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 885 exp.DType.NVARCHAR, 886 exp.DType.VARCHAR, 887 exp.DType.CHAR, 888 exp.DType.NCHAR, 889 } 890 891 # Exprs that need to have all CTEs under them bubbled up to them 892 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 893 894 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 895 896 MOD_OPERATOR = "%" 897 898 # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses 899 MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 900 exp.Mul, 901 exp.Div, 902 exp.IntDiv, 903 exp.Mod, 904 ) 905 906 # ALTER options that follow the others without a comma, e.g. MySQL's 907 # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ... 908 ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 909 910 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 911 912 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 913 914 __slots__ = ( 915 "pretty", 916 "identify", 917 "normalize", 918 "pad", 919 "_indent", 920 "normalize_functions", 921 "unsupported_level", 922 "max_unsupported", 923 "leading_comma", 924 "max_text_width", 925 "comments", 926 "dialect", 927 "unsupported_messages", 928 "_escaped_quote_end", 929 "_escaped_byte_quote_end", 930 "_escaped_identifier_end", 931 "_identifier_escaped_sequences", 932 "_next_name", 933 "_identifier_start", 934 "_identifier_end", 935 "_quote_json_path_key_using_brackets", 936 "_dispatch", 937 ) 938 939 def __init__( 940 self, 941 pretty: bool | int | None = None, 942 identify: str | bool = False, 943 normalize: bool = False, 944 pad: int = 2, 945 indent: int = 2, 946 normalize_functions: str | bool | None = None, 947 unsupported_level: ErrorLevel = ErrorLevel.WARN, 948 max_unsupported: int = 3, 949 leading_comma: bool = False, 950 max_text_width: int = 80, 951 comments: bool = True, 952 dialect: DialectType = None, 953 ): 954 import sqlglot 955 import sqlglot.dialects.dialect 956 957 self.pretty = pretty if pretty is not None else sqlglot.pretty 958 self.identify = identify 959 self.normalize = normalize 960 self.pad = pad 961 self._indent = indent 962 self.unsupported_level = unsupported_level 963 self.max_unsupported = max_unsupported 964 self.leading_comma = leading_comma 965 self.max_text_width = max_text_width 966 self.comments = comments 967 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 968 969 # This is both a Dialect property and a Generator argument, so we prioritize the latter 970 self.normalize_functions = ( 971 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 972 ) 973 974 self.unsupported_messages: list[str] = [] 975 self._escaped_quote_end: str = ( 976 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 977 ) 978 self._escaped_byte_quote_end: str = ( 979 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 980 if self.dialect.BYTE_END 981 else "" 982 ) 983 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 984 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 985 986 self._next_name = name_sequence("_t") 987 988 self._identifier_start = self.dialect.IDENTIFIER_START 989 self._identifier_end = self.dialect.IDENTIFIER_END 990 991 self._quote_json_path_key_using_brackets = True 992 993 cls = type(self) 994 dispatch = _DISPATCH_CACHE.get(cls) 995 if dispatch is None: 996 dispatch = _build_dispatch(cls) 997 _DISPATCH_CACHE[cls] = dispatch 998 self._dispatch = dispatch 999 1000 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 1001 """ 1002 Generates the SQL string corresponding to the given syntax tree. 1003 1004 Args: 1005 expression: The syntax tree. 1006 copy: Whether to copy the expression. The generator performs mutations so 1007 it is safer to copy. 1008 1009 Returns: 1010 The SQL string corresponding to `expression`. 1011 """ 1012 if copy: 1013 expression = expression.copy() 1014 1015 expression = self.preprocess(expression) 1016 1017 self.unsupported_messages = [] 1018 sql = self.sql(expression).strip() 1019 1020 if self.pretty: 1021 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1022 1023 if self.unsupported_level == ErrorLevel.IGNORE: 1024 return sql 1025 1026 if self.unsupported_level == ErrorLevel.WARN: 1027 for msg in self.unsupported_messages: 1028 logger.warning(msg) 1029 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1030 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1031 1032 return sql 1033 1034 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1035 """Apply generic preprocessing transformations to a given expression.""" 1036 expression = self._move_ctes_to_top_level(expression) 1037 1038 if self.ENSURE_BOOLS: 1039 import sqlglot.transforms 1040 1041 expression = sqlglot.transforms.ensure_bools(expression) 1042 1043 return expression 1044 1045 def _move_ctes_to_top_level(self, expression: E) -> E: 1046 if ( 1047 not expression.parent 1048 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 1049 and any(node.parent is not expression for node in expression.find_all(exp.With)) 1050 ): 1051 import sqlglot.transforms 1052 1053 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 1054 return expression 1055 1056 def unsupported(self, message: str) -> None: 1057 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1058 raise UnsupportedError(message) 1059 self.unsupported_messages.append(message) 1060 1061 def sep(self, sep: str = " ") -> str: 1062 return f"{sep.strip()}\n" if self.pretty else sep 1063 1064 def seg(self, sql: str, sep: str = " ") -> str: 1065 return f"{self.sep(sep)}{sql}" 1066 1067 def sanitize_comment(self, comment: str) -> str: 1068 comment = " " + comment if comment[0].strip() else comment 1069 comment = comment + " " if comment[-1].strip() else comment 1070 1071 # Escape block comment markers to prevent premature closure or unintended nesting. 1072 # This is necessary because single-line comments (--) are converted to block comments 1073 # (/* */) on output, and any */ in the original text would close the comment early. 1074 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1075 1076 return comment 1077 1078 def maybe_comment( 1079 self, 1080 sql: str, 1081 expression: exp.Expr | None = None, 1082 comments: list[str] | None = None, 1083 separated: bool = False, 1084 ) -> str: 1085 comments = ( 1086 ((expression and expression.comments) if comments is None else comments) # type: ignore 1087 if self.comments 1088 else None 1089 ) 1090 1091 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1092 return sql 1093 1094 comments_list = [ 1095 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1096 for comment in comments 1097 if comment 1098 ] 1099 1100 if not comments_list: 1101 return sql 1102 1103 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1104 comments_sql = self.sep().join(comments_list) 1105 return ( 1106 f"{self.sep()}{comments_sql}{sql}" 1107 if not sql or sql[0].isspace() 1108 else f"{comments_sql}{self.sep()}{sql}" 1109 ) 1110 1111 return f"{sql} {' '.join(comments_list)}" 1112 1113 def wrap(self, expression: exp.Expr | str) -> str: 1114 this_sql = ( 1115 self.sql(expression) 1116 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1117 else self.sql(expression, "this") 1118 ) 1119 if not this_sql: 1120 return "()" 1121 1122 this_sql = self.indent(this_sql, level=1, pad=0) 1123 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1124 1125 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1126 original = self.identify 1127 self.identify = False 1128 result = func(*args, **kwargs) 1129 self.identify = original 1130 return result 1131 1132 def normalize_func(self, name: str) -> str: 1133 if self.normalize_functions == "upper" or self.normalize_functions is True: 1134 return name.upper() 1135 if self.normalize_functions == "lower": 1136 return name.lower() 1137 return name 1138 1139 def indent( 1140 self, 1141 sql: str, 1142 level: int = 0, 1143 pad: int | None = None, 1144 skip_first: bool = False, 1145 skip_last: bool = False, 1146 ) -> str: 1147 if not self.pretty or not sql: 1148 return sql 1149 1150 pad = self.pad if pad is None else pad 1151 lines = sql.split("\n") 1152 1153 return "\n".join( 1154 ( 1155 line 1156 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1157 else f"{' ' * (level * self._indent + pad)}{line}" 1158 ) 1159 for i, line in enumerate(lines) 1160 ) 1161 1162 def sql( 1163 self, 1164 expression: str | exp.Expr | None, 1165 key: str | None = None, 1166 comment: bool = True, 1167 ) -> str: 1168 if not expression: 1169 return "" 1170 1171 if isinstance(expression, str): 1172 return expression 1173 1174 if key: 1175 value = expression.args.get(key) 1176 if value: 1177 return self.sql(value) 1178 return "" 1179 1180 handler = self._dispatch.get(expression.__class__) 1181 1182 if handler: 1183 sql = handler(self, expression) 1184 elif isinstance(expression, exp.Func): 1185 sql = self.function_fallback_sql(expression) 1186 elif isinstance(expression, exp.Property): 1187 sql = self.property_sql(expression) 1188 else: 1189 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1190 1191 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1192 1193 def uncache_sql(self, expression: exp.Uncache) -> str: 1194 table = self.sql(expression, "this") 1195 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1196 return f"UNCACHE TABLE{exists_sql} {table}" 1197 1198 def cache_sql(self, expression: exp.Cache) -> str: 1199 lazy = " LAZY" if expression.args.get("lazy") else "" 1200 table = self.sql(expression, "this") 1201 options = expression.args.get("options") 1202 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1203 sql = self.sql(expression, "expression") 1204 sql = f" AS{self.sep()}{sql}" if sql else "" 1205 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1206 return self.prepend_ctes(expression, sql) 1207 1208 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1209 default = "DEFAULT " if expression.args.get("default") else "" 1210 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1211 1212 def column_parts(self, expression: exp.Column) -> str: 1213 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1214 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1215 return self.sql(expression, "this") 1216 1217 return ".".join( 1218 self.sql(part) 1219 for part in ( 1220 expression.args.get("catalog"), 1221 expression.args.get("db"), 1222 expression.args.get("table"), 1223 expression.args.get("this"), 1224 ) 1225 if part 1226 ) 1227 1228 def column_sql(self, expression: exp.Column) -> str: 1229 join_mark = " (+)" if expression.args.get("join_mark") else "" 1230 1231 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1232 join_mark = "" 1233 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1234 1235 return f"{self.column_parts(expression)}{join_mark}" 1236 1237 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1238 return self.column_sql(expression) 1239 1240 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1241 this = self.sql(expression, "this") 1242 this = f" {this}" if this else "" 1243 position = self.sql(expression, "position") 1244 return f"{position}{this}" 1245 1246 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1247 column = self.sql(expression, "this") 1248 kind = self.sql(expression, "kind") 1249 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1250 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1251 kind = f"{sep}{kind}" if kind else "" 1252 constraints = f" {constraints}" if constraints else "" 1253 position = self.sql(expression, "position") 1254 position = f" {position}" if position else "" 1255 1256 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1257 kind = "" 1258 1259 return f"{exists}{column}{kind}{constraints}{position}" 1260 1261 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1262 this = self.sql(expression, "this") 1263 kind_sql = self.sql(expression, "kind").strip() 1264 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1265 1266 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1267 this = self.sql(expression, "this") 1268 if expression.args.get("not_null"): 1269 persisted = " PERSISTED NOT NULL" 1270 elif expression.args.get("persisted"): 1271 persisted = " PERSISTED" 1272 else: 1273 persisted = "" 1274 1275 return f"AS {this}{persisted}" 1276 1277 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1278 return self.token_sql(TokenType.AUTO_INCREMENT) 1279 1280 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1281 if isinstance(expression.this, list): 1282 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1283 else: 1284 this = self.sql(expression, "this") 1285 1286 return f"COMPRESS {this}" 1287 1288 def generatedasidentitycolumnconstraint_sql( 1289 self, expression: exp.GeneratedAsIdentityColumnConstraint 1290 ) -> str: 1291 this = "" 1292 if expression.this is not None: 1293 on_null = " ON NULL" if expression.args.get("on_null") else "" 1294 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1295 1296 start = expression.args.get("start") 1297 start = f"START WITH {start}" if start else "" 1298 increment = expression.args.get("increment") 1299 increment = f" INCREMENT BY {increment}" if increment else "" 1300 minvalue = expression.args.get("minvalue") 1301 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1302 maxvalue = expression.args.get("maxvalue") 1303 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1304 cycle = expression.args.get("cycle") 1305 cycle_sql = "" 1306 1307 if cycle is not None: 1308 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1309 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1310 1311 sequence_opts = "" 1312 if start or increment or cycle_sql: 1313 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1314 sequence_opts = f" ({sequence_opts.strip()})" 1315 1316 expr = self.sql(expression, "expression") 1317 expr = f"({expr})" if expr else "IDENTITY" 1318 1319 return f"GENERATED{this} AS {expr}{sequence_opts}" 1320 1321 def generatedasrowcolumnconstraint_sql( 1322 self, expression: exp.GeneratedAsRowColumnConstraint 1323 ) -> str: 1324 start = "START" if expression.args.get("start") else "END" 1325 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1326 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1327 1328 def periodforsystemtimeconstraint_sql( 1329 self, expression: exp.PeriodForSystemTimeConstraint 1330 ) -> str: 1331 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1332 1333 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1334 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1335 1336 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1337 desc = expression.args.get("desc") 1338 if desc is not None: 1339 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1340 options = self.expressions(expression, key="options", flat=True, sep=" ") 1341 options = f" {options}" if options else "" 1342 include = self.sql(expression, "include") 1343 return f"PRIMARY KEY{include}{options}" 1344 1345 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1346 this = self.sql(expression, "this") 1347 this = f" {this}" if this else "" 1348 index_type = expression.args.get("index_type") 1349 index_type = f" USING {index_type}" if index_type else "" 1350 on_conflict = self.sql(expression, "on_conflict") 1351 on_conflict = f" {on_conflict}" if on_conflict else "" 1352 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1353 options = self.expressions(expression, key="options", flat=True, sep=" ") 1354 options = f" {options}" if options else "" 1355 include = self.sql(expression, "include") 1356 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{include}{options}" 1357 1358 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1359 input_ = expression.args.get("input_") 1360 output = expression.args.get("output") 1361 variadic = expression.args.get("variadic") 1362 1363 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1364 if variadic: 1365 return "VARIADIC" 1366 1367 if input_ and output: 1368 return f"IN{self.INOUT_SEPARATOR}OUT" 1369 if input_: 1370 return "IN" 1371 if output: 1372 return "OUT" 1373 1374 return "" 1375 1376 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1377 return self.sql(expression, "this") 1378 1379 def create_sql(self, expression: exp.Create) -> str: 1380 kind = self.sql(expression, "kind") 1381 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1382 1383 properties = expression.args.get("properties") 1384 1385 if ( 1386 kind == "TRIGGER" 1387 and properties 1388 and properties.expressions 1389 and isinstance(properties.expressions[0], exp.TriggerProperties) 1390 and properties.expressions[0].args.get("constraint") 1391 ): 1392 kind = f"CONSTRAINT {kind}" 1393 1394 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1395 1396 this = self.createable_sql(expression, properties_locs) 1397 1398 properties_sql = "" 1399 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1400 exp.Properties.Location.POST_WITH 1401 ): 1402 props_ast = exp.Properties( 1403 expressions=[ 1404 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1405 *properties_locs[exp.Properties.Location.POST_WITH], 1406 ] 1407 ) 1408 props_ast.parent = expression 1409 properties_sql = self.sql(props_ast) 1410 1411 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1412 properties_sql = self.sep() + properties_sql 1413 elif not self.pretty: 1414 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1415 properties_sql = f" {properties_sql}" 1416 1417 begin = " BEGIN" if expression.args.get("begin") else "" 1418 1419 expression_sql = self.sql(expression, "expression") 1420 if expression_sql: 1421 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1422 1423 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1424 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1425 ): 1426 postalias_props_sql = "" 1427 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1428 postalias_props_sql = self.properties( 1429 exp.Properties( 1430 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1431 ), 1432 wrapped=False, 1433 ) 1434 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1435 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1436 1437 postindex_props_sql = "" 1438 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1439 postindex_props_sql = self.properties( 1440 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1441 wrapped=False, 1442 prefix=" ", 1443 ) 1444 1445 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1446 indexes = f" {indexes}" if indexes else "" 1447 index_sql = indexes + postindex_props_sql 1448 1449 replace = " OR REPLACE" if expression.args.get("replace") else "" 1450 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1451 unique = " UNIQUE" if expression.args.get("unique") else "" 1452 1453 clustered = expression.args.get("clustered") 1454 if clustered is None: 1455 clustered_sql = "" 1456 elif clustered: 1457 clustered_sql = " CLUSTERED COLUMNSTORE" 1458 else: 1459 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1460 1461 postcreate_props_sql = "" 1462 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1463 postcreate_props_sql = self.properties( 1464 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1465 sep=" ", 1466 prefix=" ", 1467 wrapped=False, 1468 ) 1469 1470 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1471 1472 postexpression_props_sql = "" 1473 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1474 postexpression_props_sql = self.properties( 1475 exp.Properties( 1476 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1477 ), 1478 sep=" ", 1479 prefix=" ", 1480 wrapped=False, 1481 ) 1482 1483 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1484 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1485 no_schema_binding = ( 1486 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1487 ) 1488 1489 clone = self.sql(expression, "clone") 1490 clone = f" {clone}" if clone else "" 1491 1492 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1493 properties_expression = f"{expression_sql}{properties_sql}" 1494 else: 1495 properties_expression = f"{properties_sql}{expression_sql}" 1496 1497 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1498 return self.prepend_ctes(expression, expression_sql) 1499 1500 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1501 start = self.sql(expression, "start") 1502 start = f"START WITH {start}" if start else "" 1503 increment = self.sql(expression, "increment") 1504 increment = f" INCREMENT BY {increment}" if increment else "" 1505 minvalue = self.sql(expression, "minvalue") 1506 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1507 maxvalue = self.sql(expression, "maxvalue") 1508 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1509 owned = self.sql(expression, "owned") 1510 owned = f" OWNED BY {owned}" if owned else "" 1511 1512 cache = expression.args.get("cache") 1513 if cache is None: 1514 cache_str = "" 1515 elif cache is True: 1516 cache_str = " CACHE" 1517 else: 1518 cache_str = f" CACHE {cache}" 1519 1520 options = self.expressions(expression, key="options", flat=True, sep=" ") 1521 options = f" {options}" if options else "" 1522 1523 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1524 1525 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1526 timing = expression.args.get("timing", "") 1527 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1528 timing_events = f"{timing} {events}".strip() if timing or events else "" 1529 1530 parts = [timing_events, "ON", self.sql(expression, "table")] 1531 1532 if referenced_table := expression.args.get("referenced_table"): 1533 parts.extend(["FROM", self.sql(referenced_table)]) 1534 1535 if deferrable := expression.args.get("deferrable"): 1536 parts.append(deferrable) 1537 1538 if initially := expression.args.get("initially"): 1539 parts.append(f"INITIALLY {initially}") 1540 1541 if referencing := expression.args.get("referencing"): 1542 parts.append(self.sql(referencing)) 1543 1544 if for_each := expression.args.get("for_each"): 1545 parts.append(f"FOR EACH {for_each}") 1546 1547 if when := expression.args.get("when"): 1548 parts.append(f"WHEN ({self.sql(when)})") 1549 1550 parts.append(self.sql(expression, "execute")) 1551 1552 return self.sep().join(parts) 1553 1554 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1555 parts = [] 1556 1557 if old_alias := expression.args.get("old"): 1558 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1559 1560 if new_alias := expression.args.get("new"): 1561 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1562 1563 return f"REFERENCING {' '.join(parts)}" 1564 1565 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1566 columns = expression.args.get("columns") 1567 if columns: 1568 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1569 1570 return self.sql(expression, "this") 1571 1572 def clone_sql(self, expression: exp.Clone) -> str: 1573 this = self.sql(expression, "this") 1574 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1575 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1576 return f"{shallow}{keyword} {this}" 1577 1578 def describe_sql(self, expression: exp.Describe) -> str: 1579 style = expression.args.get("style") 1580 style = f" {style}" if style else "" 1581 partition = self.sql(expression, "partition") 1582 partition = f" {partition}" if partition else "" 1583 format = self.sql(expression, "format") 1584 format = f" {format}" if format else "" 1585 as_json = " AS JSON" if expression.args.get("as_json") else "" 1586 1587 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1588 1589 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1590 tag = self.sql(expression, "tag") 1591 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1592 1593 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1594 with_ = self.sql(expression, "with_") 1595 if with_: 1596 sql = f"{with_}{self.sep()}{sql}" 1597 return sql 1598 1599 def with_sql(self, expression: exp.With) -> str: 1600 udfs = self.expressions(expression, key="udfs", flat=True) 1601 udfs = f"WITH {udfs}" if udfs else "" 1602 1603 sql = self.expressions(expression, flat=True) 1604 1605 recursive = ( 1606 "RECURSIVE " 1607 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1608 else "" 1609 ) 1610 search = self.sql(expression, "search") 1611 search = f" {search}" if search else "" 1612 cycle = self.sql(expression, "cycle") 1613 cycle = f" {cycle}" if cycle else "" 1614 1615 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1616 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1617 1618 def cte_sql(self, expression: exp.CTE) -> str: 1619 alias = expression.args.get("alias") 1620 if alias: 1621 alias.add_comments(expression.pop_comments()) 1622 1623 alias_sql = self.sql(expression, "alias") 1624 1625 materialized = expression.args.get("materialized") 1626 if materialized is False: 1627 materialized = "NOT MATERIALIZED " 1628 elif materialized: 1629 materialized = "MATERIALIZED " 1630 1631 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1632 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1633 1634 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1635 1636 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1637 alias = self.sql(expression, "this") 1638 columns = self.expressions(expression, key="columns", flat=True) 1639 columns = f"({columns})" if columns else "" 1640 1641 if ( 1642 columns 1643 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1644 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1645 ): 1646 columns = "" 1647 self.unsupported("Named columns are not supported in table alias.") 1648 1649 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1650 alias = self._next_name() 1651 1652 return f"{alias}{columns}" 1653 1654 def bitstring_sql(self, expression: exp.BitString) -> str: 1655 this = self.sql(expression, "this") 1656 if self.dialect.BIT_START: 1657 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1658 return f"{int(this, 2)}" 1659 1660 def hexstring_sql( 1661 self, expression: exp.HexString, binary_function_repr: str | None = None 1662 ) -> str: 1663 this = self.sql(expression, "this") 1664 is_integer_type = expression.args.get("is_integer") 1665 1666 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1667 not self.dialect.HEX_START and not binary_function_repr 1668 ): 1669 # Integer representation will be returned if: 1670 # - The read dialect treats the hex value as integer literal but not the write 1671 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1672 return f"{int(this, 16)}" 1673 1674 if not is_integer_type: 1675 # Read dialect treats the hex value as BINARY/BLOB 1676 if binary_function_repr: 1677 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1678 return self.func(binary_function_repr, exp.Literal.string(this)) 1679 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1680 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1681 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1682 1683 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1684 1685 def bytestring_sql(self, expression: exp.ByteString) -> str: 1686 this = self.sql(expression, "this") 1687 is_bytes = bool(expression.args.get("is_bytes")) 1688 if is_bytes and self.BYTE_STRING_FUNCTION: 1689 # Hex digits can be quoted directly without escaping. 1690 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1691 1692 if self.dialect.BYTE_START: 1693 escaped_byte_string = self.escape_str( 1694 this, 1695 escape_backslash=bool( 1696 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1697 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1698 ), 1699 delimiter=self.dialect.BYTE_END, 1700 escaped_delimiter=self._escaped_byte_quote_end, 1701 is_byte_string=True, 1702 is_bytes=is_bytes, 1703 ) 1704 delimited_byte_string = ( 1705 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1706 ) 1707 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1708 return self.sql( 1709 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1710 ) 1711 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1712 return self.sql( 1713 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1714 ) 1715 1716 return delimited_byte_string 1717 1718 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1719 return self.sql(exp.Literal.string(this)) 1720 1721 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1722 return "" 1723 1724 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1725 this = self.sql(expression, "this") 1726 escape = expression.args.get("escape") 1727 unicode_start = self.dialect.UNICODE_START 1728 1729 if unicode_start: 1730 escape_substitute = r"\\\1" 1731 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1732 else: 1733 escape_substitute = r"\\u\1" 1734 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1735 1736 if escape: 1737 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1738 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1739 else: 1740 escape_pattern = ESCAPED_UNICODE_RE 1741 escape_sql = "" 1742 1743 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1744 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1745 1746 if unicode_start: 1747 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1748 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1749 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1750 else: 1751 this = self.escape_str(this, escape_backslash=False) 1752 1753 return f"{left_quote}{this}{right_quote}{escape_sql}" 1754 1755 def rawstring_sql(self, expression: exp.RawString) -> str: 1756 string = expression.this 1757 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1758 string = string.replace("\\", "\\\\") 1759 1760 string = self.escape_str(string, escape_backslash=False) 1761 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1762 1763 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1764 this = self.sql(expression, "this") 1765 specifier = self.sql(expression, "expression") 1766 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1767 return f"{this}{specifier}" 1768 1769 def datatype_param_bound_limiter( 1770 self, 1771 expression: exp.DataType, 1772 type_value: exp.DType, 1773 defaults: tuple[int, ...], 1774 bounds: tuple[int | None, ...], 1775 ) -> exp.DataType: 1776 params = expression.expressions 1777 1778 if not params: 1779 if defaults: 1780 expression.set( 1781 "expressions", 1782 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1783 ) 1784 return expression 1785 1786 if not bounds: 1787 return expression 1788 1789 for i, param in enumerate(params): 1790 bound = bounds[i] if i < len(bounds) else None 1791 if bound is None: 1792 continue 1793 1794 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1795 value = ( 1796 param_value.to_py() 1797 if isinstance(param_value, exp.Literal) and param_value.is_number 1798 else None 1799 ) 1800 if isinstance(value, (int, Decimal)) and value > bound: 1801 self.unsupported( 1802 f"{type_value.value} parameter {param_value.name} exceeds " 1803 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1804 ) 1805 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1806 1807 return expression 1808 1809 def datatype_sql(self, expression: exp.DataType) -> str: 1810 nested = "" 1811 values = "" 1812 1813 expr_nested = expression.args.get("nested") 1814 type_value = expression.this 1815 1816 if ( 1817 not expr_nested 1818 and isinstance(type_value, exp.DType) 1819 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1820 ): 1821 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1822 1823 interior = ( 1824 self.expressions( 1825 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1826 ) 1827 if expr_nested and self.pretty 1828 else self.expressions(expression, flat=True) 1829 ) 1830 1831 if type_value in self.UNSUPPORTED_TYPES: 1832 self.unsupported( 1833 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1834 ) 1835 1836 type_sql: t.Any = "" 1837 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1838 type_sql = self.sql(expression, "kind") 1839 elif type_value == exp.DType.CHARACTER_SET: 1840 char = f"CHAR({interior})" if interior else "CHAR" 1841 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1842 else: 1843 type_sql = ( 1844 self.TYPE_MAPPING.get(type_value, type_value.value) 1845 if isinstance(type_value, exp.DType) 1846 else type_value 1847 ) 1848 1849 if interior: 1850 if expr_nested: 1851 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1852 if expression.args.get("values") is not None: 1853 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1854 values = self.expressions(expression, key="values", flat=True) 1855 values = f"{delimiters[0]}{values}{delimiters[1]}" 1856 elif type_value == exp.DType.INTERVAL: 1857 nested = f" {interior}" 1858 else: 1859 nested = f"({interior})" 1860 1861 type_sql = f"{type_sql}{nested}{values}" 1862 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1863 exp.DType.TIMETZ, 1864 exp.DType.TIMESTAMPTZ, 1865 ): 1866 type_sql = f"{type_sql} WITH TIME ZONE" 1867 1868 collate = self.sql(expression, "collate") 1869 if collate: 1870 type_sql = f"{type_sql} COLLATE {collate}" 1871 1872 return type_sql 1873 1874 def directory_sql(self, expression: exp.Directory) -> str: 1875 local = "LOCAL " if expression.args.get("local") else "" 1876 row_format = self.sql(expression, "row_format") 1877 row_format = f" {row_format}" if row_format else "" 1878 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1879 1880 def delete_sql(self, expression: exp.Delete) -> str: 1881 hint = self.sql(expression, "hint") 1882 this = self.sql(expression, "this") 1883 this = f" FROM {this}" if this else "" 1884 using = self.expressions(expression, key="using") 1885 using = f" USING {using}" if using else "" 1886 cluster = self.sql(expression, "cluster") 1887 cluster = f" {cluster}" if cluster else "" 1888 where = self.sql(expression, "where") 1889 returning = self.sql(expression, "returning") 1890 order = self.sql(expression, "order") 1891 limit = self.sql(expression, "limit") 1892 tables = self.expressions(expression, key="tables") 1893 tables = f" {tables}" if tables else "" 1894 if self.RETURNING_END: 1895 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1896 else: 1897 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1898 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1899 1900 def drop_sql(self, expression: exp.Drop) -> str: 1901 tables = self.expressions(expression, key="tables", flat=True) 1902 expressions = self.expressions(expression, flat=True) 1903 expressions = f" ({expressions})" if expressions else "" 1904 kind = expression.args["kind"] 1905 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1906 iceberg = ( 1907 " ICEBERG" 1908 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1909 else "" 1910 ) 1911 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1912 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1913 on_cluster = self.sql(expression, "cluster") 1914 on_cluster = f" {on_cluster}" if on_cluster else "" 1915 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1916 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1917 cascade = " CASCADE" if expression.args.get("cascade") else "" 1918 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1919 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1920 purge = " PURGE" if expression.args.get("purge") else "" 1921 sync = " SYNC" if expression.args.get("sync") else "" 1922 force = " FORCE" if expression.args.get("force") else "" 1923 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1924 1925 def set_operation(self, expression: exp.SetOperation) -> str: 1926 op_type = type(expression) 1927 op_name = op_type.key.upper() 1928 1929 distinct = expression.args.get("distinct") 1930 if ( 1931 distinct is False 1932 and op_type in (exp.Except, exp.Intersect) 1933 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1934 ): 1935 self.unsupported(f"{op_name} ALL is not supported") 1936 1937 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1938 1939 if distinct is None: 1940 distinct = default_distinct 1941 if distinct is None: 1942 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1943 1944 if distinct is default_distinct: 1945 distinct_or_all = "" 1946 else: 1947 distinct_or_all = " DISTINCT" if distinct else " ALL" 1948 1949 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1950 side_kind = f"{side_kind} " if side_kind else "" 1951 1952 by_name = " BY NAME" if expression.args.get("by_name") else "" 1953 on = self.expressions(expression, key="on", flat=True) 1954 on = f" ON ({on})" if on else "" 1955 1956 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1957 1958 def set_operations(self, expression: exp.SetOperation) -> str: 1959 if not self.SET_OP_MODIFIERS: 1960 limit = expression.args.get("limit") 1961 order = expression.args.get("order") 1962 offset = expression.args.get("offset") 1963 1964 if limit or order or offset: 1965 select = self._move_ctes_to_top_level( 1966 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1967 ) 1968 1969 for arg in ("limit", "order", "offset"): 1970 if value := expression.args.get(arg): 1971 select.set(arg, value.pop()) 1972 return self.sql(select) 1973 1974 sqls: list[str] = [] 1975 stack: list[str | exp.Expr | int | None] = [expression] 1976 depth = 0 1977 open_parens = "" 1978 1979 while stack: 1980 node = stack.pop() 1981 1982 if isinstance(node, int): 1983 # Closes a plain grouping opened below, restoring its depth 1984 depth = node 1985 sqls[-1] += self.sep("") + self.indent(")", level=depth, pad=0) 1986 elif isinstance(node, exp.SetOperation): 1987 stack.append(node.expression) 1988 stack.append( 1989 self.maybe_comment( 1990 self.set_operation(node), comments=node.comments, separated=True 1991 ) 1992 ) 1993 left = node.this 1994 if ( 1995 not self.SET_OP_PARENTHESIZED_OPERANDS 1996 and not self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT 1997 ): 1998 # Left grouping is implicit, so skip the `SELECT * FROM (...)` wrapper: 1999 # it renames duplicate columns and deepens recursion. Inner CTEs and 2000 # modifiers still need the wrapper. 2001 wrappers: list[exp.Subquery] = [] 2002 inner = left 2003 while isinstance(inner, exp.Subquery) and inner.is_wrapper: 2004 wrappers.append(inner) 2005 inner = inner.this 2006 2007 if ( 2008 isinstance(inner, exp.SetOperation) 2009 and not inner.args.get("with_") 2010 and not any(inner.args.get(key) for key in exp.QUERY_MODIFIERS) 2011 ): 2012 for wrapper in wrappers: 2013 inner.add_comments(wrapper.pop_comments()) 2014 left = inner 2015 elif ( 2016 self.SET_OP_PARENTHESIZED_OPERANDS 2017 and isinstance(left, exp.Subquery) 2018 and left.is_wrapper 2019 and not left.comments 2020 and isinstance(left.this, exp.SetOperation) 2021 and not left.this.args.get("with_") 2022 and not any(left.this.args.get(key) for key in exp.QUERY_MODIFIERS) 2023 ): 2024 # Write a plain grouping's parentheses here rather than recursing through 2025 # subquery_sql: left-to-right chains nest one grouping per INTERSECT, and 2026 # long ones would exhaust the stack. The depth pushed here marks the close. 2027 open_parens += self.indent("(", level=depth, pad=0) + self.sep("") 2028 stack.append(depth) 2029 depth += 1 2030 left = left.this 2031 stack.append(left) 2032 else: 2033 if ( 2034 not self.SET_OP_LIMITS 2035 and isinstance(node, exp.Select) 2036 and node.args.get("limit") 2037 ): 2038 node = node.subquery(copy=False) 2039 if not self.SET_OP_PARENTHESIZED_OPERANDS: 2040 node = exp.select("*").from_(node, copy=False) 2041 elif ( 2042 not self.SET_OP_PARENTHESIZED_OPERANDS 2043 and isinstance(node, exp.Subquery) 2044 and isinstance(node.unnest(), exp.SetOperation) 2045 ): 2046 node = exp.select("*").from_(node, copy=False) 2047 sqls.append(open_parens + self.indent(self.sql(node), level=depth, pad=0)) 2048 open_parens = "" 2049 2050 this = self.sep().join(sqls) 2051 this = self.query_modifiers(expression, this) 2052 return self.prepend_ctes(expression, this) 2053 2054 def fetch_sql(self, expression: exp.Fetch) -> str: 2055 direction = expression.args.get("direction") 2056 direction = f" {direction}" if direction else "" 2057 count = self.sql(expression, "count") 2058 count = f" {count}" if count else "" 2059 limit_options = self.sql(expression, "limit_options") 2060 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 2061 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 2062 2063 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 2064 percent = " PERCENT" if expression.args.get("percent") else "" 2065 rows = " ROWS" if expression.args.get("rows") else "" 2066 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 2067 if not with_ties and rows: 2068 with_ties = " ONLY" 2069 return f"{percent}{rows}{with_ties}" 2070 2071 def filter_sql(self, expression: exp.Filter) -> str: 2072 this = self.sql(expression, "this") 2073 where = self.sql(expression, "expression").strip() 2074 return f"{this} FILTER({where})" 2075 2076 def per_sql(self, expression: exp.Per) -> str: 2077 keys = ", ".join( 2078 f"{self.sql(key, 'this')} {self.sql(key, 'with_fill')}" 2079 if isinstance(key, exp.Ordered) and key.args.get("with_fill") 2080 else self.sql(key) 2081 for key in expression.expressions 2082 ) 2083 return f"{self.sql(expression, 'this')} PER ({keys})" 2084 2085 def hint_sql(self, expression: exp.Hint) -> str: 2086 if not self.QUERY_HINTS: 2087 self.unsupported("Hints are not supported") 2088 return "" 2089 2090 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 2091 2092 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2093 using = self.sql(expression, "using") 2094 using = f" USING {using}" if using else "" 2095 columns = self.expressions(expression, key="columns", flat=True) 2096 columns = f"({columns})" if columns else "" 2097 partition_by = self.expressions(expression, key="partition_by", flat=True) 2098 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2099 where = self.sql(expression, "where") 2100 include = self.expressions(expression, key="include", flat=True) 2101 if include: 2102 include = f" INCLUDE ({include})" 2103 with_storage = self.expressions(expression, key="with_storage", flat=True) 2104 with_storage = f" WITH ({with_storage})" if with_storage else "" 2105 tablespace = self.sql(expression, "tablespace") 2106 if tablespace: 2107 prefix = "" if isinstance(expression.parent, exp.Index) else " USING INDEX" 2108 tablespace = f"{prefix} TABLESPACE {tablespace}" 2109 on = self.sql(expression, "on") 2110 on = f" ON {on}" if on else "" 2111 2112 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 2113 2114 def index_sql(self, expression: exp.Index) -> str: 2115 unique = "UNIQUE " if expression.args.get("unique") else "" 2116 primary = "PRIMARY " if expression.args.get("primary") else "" 2117 amp = "AMP " if expression.args.get("amp") else "" 2118 name = self.sql(expression, "this") 2119 name = f"{name} " if name else "" 2120 table = self.sql(expression, "table") 2121 table = f"{self.INDEX_ON} {table}" if table else "" 2122 2123 index = "INDEX " if not table else "" 2124 2125 params = self.sql(expression, "params") 2126 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 2127 2128 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2129 this = expression.this 2130 if this and this.is_string: 2131 resolved = maybe_parse(this.name).sql(self.dialect) 2132 if "expressions" in expression.args: 2133 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2134 # We can't safely emit the call to other dialects since name/arg semantics may differ 2135 self.unsupported( 2136 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2137 ) 2138 return resolved 2139 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2140 return self.func("IDENTIFIER", this) 2141 2142 def identifier_sql(self, expression: exp.Identifier) -> str: 2143 text = expression.name 2144 lower = text.lower() 2145 quoted = expression.quoted 2146 text = lower if self.normalize and not quoted else text 2147 if self._identifier_escaped_sequences: 2148 for char, escaped in self._identifier_escaped_sequences.items(): 2149 text = text.replace(char, escaped) 2150 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2151 if ( 2152 quoted 2153 or self.dialect.can_quote(expression, self.identify) 2154 or lower in self.RESERVED_KEYWORDS 2155 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2156 ): 2157 text = ( 2158 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2159 ) 2160 return text 2161 2162 def hex_sql(self, expression: exp.Hex) -> str: 2163 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2164 if self.dialect.HEX_LOWERCASE: 2165 text = self.func("LOWER", text) 2166 2167 return text 2168 2169 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2170 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2171 if not self.dialect.HEX_LOWERCASE: 2172 text = self.func("LOWER", text) 2173 return text 2174 2175 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2176 input_format = self.sql(expression, "input_format") 2177 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2178 output_format = self.sql(expression, "output_format") 2179 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2180 return self.sep().join((input_format, output_format)) 2181 2182 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2183 string = self.sql(exp.Literal.string(expression.name)) 2184 return f"{prefix}{string}" 2185 2186 def partition_sql(self, expression: exp.Partition) -> str: 2187 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2188 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2189 2190 def properties_sql(self, expression: exp.Properties) -> str: 2191 root_properties = [] 2192 with_properties = [] 2193 2194 for p in expression.expressions: 2195 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2196 if p_loc == exp.Properties.Location.POST_WITH: 2197 with_properties.append(p) 2198 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2199 root_properties.append(p) 2200 2201 root_props_ast = exp.Properties(expressions=root_properties) 2202 root_props_ast.parent = expression.parent 2203 2204 with_props_ast = exp.Properties(expressions=with_properties) 2205 with_props_ast.parent = expression.parent 2206 2207 root_props = self.root_properties(root_props_ast) 2208 with_props = self.with_properties(with_props_ast) 2209 2210 if root_props and with_props and not self.pretty: 2211 with_props = " " + with_props 2212 2213 return root_props + with_props 2214 2215 def root_properties(self, properties: exp.Properties) -> str: 2216 if properties.expressions: 2217 return self.expressions(properties, indent=False, sep=" ") 2218 return "" 2219 2220 def properties( 2221 self, 2222 properties: exp.Properties, 2223 prefix: str = "", 2224 sep: str = ", ", 2225 suffix: str = "", 2226 wrapped: bool = True, 2227 ) -> str: 2228 if properties.expressions: 2229 expressions = self.expressions(properties, sep=sep, indent=False) 2230 if expressions: 2231 expressions = self.wrap(expressions) if wrapped else expressions 2232 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2233 return "" 2234 2235 def with_properties(self, properties: exp.Properties) -> str: 2236 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2237 2238 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2239 properties_locs = defaultdict(list) 2240 for p in properties.expressions: 2241 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2242 if p_loc != exp.Properties.Location.UNSUPPORTED: 2243 properties_locs[p_loc].append(p) 2244 else: 2245 self.unsupported(f"Unsupported property {p.key}") 2246 2247 return properties_locs 2248 2249 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2250 if isinstance(expression.this, exp.Dot): 2251 return self.sql(expression, "this") 2252 return self.sql(exp.Literal.string(expression.name)) if string_key else expression.name 2253 2254 def property_sql(self, expression: exp.Property) -> str: 2255 property_cls = expression.__class__ 2256 if property_cls == exp.Property: 2257 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2258 2259 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2260 if not property_name: 2261 self.unsupported(f"Unsupported property {expression.key}") 2262 return "" 2263 2264 return f"{property_name}={self.sql(expression, 'this')}" 2265 2266 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2267 return f"UUID {self.sql(expression, 'this')}" 2268 2269 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2270 if self.SUPPORTS_CREATE_TABLE_LIKE: 2271 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2272 options = f" {options}" if options else "" 2273 2274 like = f"LIKE {self.sql(expression, 'this')}{options}" 2275 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2276 like = f"({like})" 2277 2278 return like 2279 2280 if expression.expressions: 2281 self.unsupported("Transpilation of LIKE property options is unsupported") 2282 2283 select = exp.select("*").from_(expression.this).limit(0) 2284 return f"AS {self.sql(select)}" 2285 2286 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2287 no = "NO " if expression.args.get("no") else "" 2288 protection = " PROTECTION" if expression.args.get("protection") else "" 2289 return f"{no}FALLBACK{protection}" 2290 2291 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2292 no = "NO " if expression.args.get("no") else "" 2293 local = expression.args.get("local") 2294 local = f"{local} " if local else "" 2295 dual = "DUAL " if expression.args.get("dual") else "" 2296 before = "BEFORE " if expression.args.get("before") else "" 2297 after = "AFTER " if expression.args.get("after") else "" 2298 return f"{no}{local}{dual}{before}{after}JOURNAL" 2299 2300 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2301 freespace = self.sql(expression, "this") 2302 percent = " PERCENT" if expression.args.get("percent") else "" 2303 return f"FREESPACE={freespace}{percent}" 2304 2305 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2306 if expression.args.get("default"): 2307 property = "DEFAULT" 2308 elif expression.args.get("on"): 2309 property = "ON" 2310 else: 2311 property = "OFF" 2312 return f"CHECKSUM={property}" 2313 2314 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2315 if expression.args.get("no"): 2316 return "NO MERGEBLOCKRATIO" 2317 if expression.args.get("default"): 2318 return "DEFAULT MERGEBLOCKRATIO" 2319 2320 percent = " PERCENT" if expression.args.get("percent") else "" 2321 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2322 2323 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2324 expressions = self.expressions(expression, flat=True) 2325 expressions = f"({expressions})" if expressions else "" 2326 return f"USING {self.sql(expression, 'this')}{expressions}" 2327 2328 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2329 default = expression.args.get("default") 2330 minimum = expression.args.get("minimum") 2331 maximum = expression.args.get("maximum") 2332 if default or minimum or maximum: 2333 if default: 2334 prop = "DEFAULT" 2335 elif minimum: 2336 prop = "MINIMUM" 2337 else: 2338 prop = "MAXIMUM" 2339 return f"{prop} DATABLOCKSIZE" 2340 units = expression.args.get("units") 2341 units = f" {units}" if units else "" 2342 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2343 2344 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2345 autotemp = expression.args.get("autotemp") 2346 always = expression.args.get("always") 2347 default = expression.args.get("default") 2348 manual = expression.args.get("manual") 2349 never = expression.args.get("never") 2350 2351 if autotemp is not None: 2352 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2353 elif always: 2354 prop = "ALWAYS" 2355 elif default: 2356 prop = "DEFAULT" 2357 elif manual: 2358 prop = "MANUAL" 2359 elif never: 2360 prop = "NEVER" 2361 return f"BLOCKCOMPRESSION={prop}" 2362 2363 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2364 no = expression.args.get("no") 2365 no = " NO" if no else "" 2366 concurrent = expression.args.get("concurrent") 2367 concurrent = " CONCURRENT" if concurrent else "" 2368 target = self.sql(expression, "target") 2369 target = f" {target}" if target else "" 2370 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2371 2372 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2373 if isinstance(expression.this, list): 2374 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2375 if expression.this: 2376 modulus = self.sql(expression, "this") 2377 remainder = self.sql(expression, "expression") 2378 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2379 2380 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2381 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2382 return f"FROM ({from_expressions}) TO ({to_expressions})" 2383 2384 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2385 this = self.sql(expression, "this") 2386 2387 for_values_or_default = expression.expression 2388 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2389 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2390 else: 2391 for_values_or_default = " DEFAULT" 2392 2393 return f"PARTITION OF {this}{for_values_or_default}" 2394 2395 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2396 kind = expression.args.get("kind") 2397 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2398 for_or_in = expression.args.get("for_or_in") 2399 for_or_in = f" {for_or_in}" if for_or_in else "" 2400 lock_type = expression.args.get("lock_type") 2401 override = " OVERRIDE" if expression.args.get("override") else "" 2402 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2403 2404 def lockingstatement_sql(self, expression: exp.LockingStatement) -> str: 2405 self.unsupported("LOCKING request modifier is not supported") 2406 return self.sql(expression, "expression") 2407 2408 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2409 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2410 statistics = expression.args.get("statistics") 2411 statistics_sql = "" 2412 if statistics is not None: 2413 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2414 return f"{data_sql}{statistics_sql}" 2415 2416 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2417 this = self.sql(expression, "this") 2418 this = f"HISTORY_TABLE={this}" if this else "" 2419 data_consistency: str | None = self.sql(expression, "data_consistency") 2420 data_consistency = ( 2421 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2422 ) 2423 retention_period: str | None = self.sql(expression, "retention_period") 2424 retention_period = ( 2425 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2426 ) 2427 2428 if this: 2429 on_sql = self.func("ON", this, data_consistency, retention_period) 2430 else: 2431 on_sql = "ON" if expression.args.get("on") else "OFF" 2432 2433 sql = f"SYSTEM_VERSIONING={on_sql}" 2434 2435 return f"WITH({sql})" if expression.args.get("with_") else sql 2436 2437 def insert_sql(self, expression: exp.Insert) -> str: 2438 hint = self.sql(expression, "hint") 2439 overwrite = expression.args.get("overwrite") 2440 2441 if isinstance(expression.this, exp.Directory): 2442 this = " OVERWRITE" if overwrite else " INTO" 2443 else: 2444 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2445 2446 stored = self.sql(expression, "stored") 2447 stored = f" {stored}" if stored else "" 2448 alternative = expression.args.get("alternative") 2449 alternative = f" OR {alternative}" if alternative else "" 2450 ignore = " IGNORE" if expression.args.get("ignore") else "" 2451 is_function = expression.args.get("is_function") 2452 if is_function: 2453 this = f"{this} FUNCTION" 2454 this = f"{this} {self.sql(expression, 'this')}" 2455 2456 exists = " IF EXISTS" if expression.args.get("exists") else "" 2457 where = self.sql(expression, "where") 2458 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2459 using = self.expressions(expression, key="using", flat=True) 2460 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2461 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2462 on_conflict = self.sql(expression, "conflict") 2463 on_conflict = f" {on_conflict}" if on_conflict else "" 2464 by_name = " BY NAME" if expression.args.get("by_name") else "" 2465 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2466 returning = self.sql(expression, "returning") 2467 2468 if self.RETURNING_END: 2469 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2470 else: 2471 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2472 2473 partition_by = self.sql(expression, "partition") 2474 partition_by = f" {partition_by}" if partition_by else "" 2475 settings = self.sql(expression, "settings") 2476 settings = f" {settings}" if settings else "" 2477 2478 source = self.sql(expression, "source") 2479 source = f"TABLE {source}" if source else "" 2480 2481 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2482 return self.prepend_ctes(expression, sql) 2483 2484 def introducer_sql(self, expression: exp.Introducer) -> str: 2485 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2486 2487 def kill_sql(self, expression: exp.Kill) -> str: 2488 kind = self.sql(expression, "kind") 2489 kind = f" {kind}" if kind else "" 2490 this = self.sql(expression, "this") 2491 this = f" {this}" if this else "" 2492 return f"KILL{kind}{this}" 2493 2494 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2495 return expression.name 2496 2497 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2498 return expression.name 2499 2500 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2501 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2502 2503 constraint = self.sql(expression, "constraint") 2504 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2505 2506 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2507 if conflict_keys: 2508 conflict_keys = f"({conflict_keys})" 2509 2510 index_predicate = self.sql(expression, "index_predicate") 2511 conflict_keys = f"{conflict_keys}{index_predicate} " 2512 2513 action = self.sql(expression, "action") 2514 2515 expressions = self.expressions(expression, flat=True) 2516 if expressions: 2517 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2518 expressions = f" {set_keyword}{expressions}" 2519 2520 where = self.sql(expression, "where") 2521 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2522 2523 def returning_sql(self, expression: exp.Returning) -> str: 2524 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2525 2526 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2527 fields = self.sql(expression, "fields") 2528 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2529 escaped = self.sql(expression, "escaped") 2530 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2531 items = self.sql(expression, "collection_items") 2532 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2533 keys = self.sql(expression, "map_keys") 2534 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2535 lines = self.sql(expression, "lines") 2536 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2537 null = self.sql(expression, "null") 2538 null = f" NULL DEFINED AS {null}" if null else "" 2539 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2540 2541 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2542 return f"WITH ({self.expressions(expression, flat=True)})" 2543 2544 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2545 this = f"{self.sql(expression, 'this')} INDEX" 2546 target = self.sql(expression, "target") 2547 target = f" FOR {target}" if target else "" 2548 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2549 2550 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2551 this = self.sql(expression, "this") 2552 kind = self.sql(expression, "kind") 2553 expr = self.sql(expression, "expression") 2554 return f"{this} ({kind} => {expr})" 2555 2556 def table_parts(self, expression: exp.Table) -> str: 2557 return ".".join( 2558 self.sql(part) 2559 for part in ( 2560 expression.args.get("catalog"), 2561 expression.args.get("db"), 2562 expression.args.get("this"), 2563 ) 2564 if part is not None 2565 ) 2566 2567 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2568 table = self.table_parts(expression) 2569 only = "ONLY " if expression.args.get("only") else "" 2570 partition = self.sql(expression, "partition") 2571 partition = f" {partition}" if partition else "" 2572 version = self.sql(expression, "version") 2573 version = f" {version}" if version else "" 2574 alias = self.sql(expression, "alias") 2575 alias = f"{sep}{alias}" if alias else "" 2576 2577 sample = self.sql(expression, "sample") 2578 post_alias = "" 2579 pre_alias = "" 2580 2581 if self.dialect.ALIAS_POST_TABLESAMPLE: 2582 pre_alias = sample 2583 else: 2584 post_alias = sample 2585 2586 if self.dialect.ALIAS_POST_VERSION: 2587 pre_alias = f"{pre_alias}{version}" 2588 else: 2589 post_alias = f"{post_alias}{version}" 2590 2591 hints = self.expressions(expression, key="hints", sep=" ") 2592 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2593 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2594 joins = self.indent( 2595 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2596 ) 2597 laterals = self.expressions(expression, key="laterals", sep="") 2598 2599 file_format = self.sql(expression, "format") 2600 pattern = self.sql(expression, "pattern") 2601 if file_format: 2602 pattern = f", PATTERN => {pattern}" if pattern else "" 2603 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2604 elif pattern: 2605 file_format = f" (PATTERN => {pattern})" 2606 2607 ordinality = expression.args.get("ordinality") or "" 2608 if ordinality: 2609 ordinality = f" WITH ORDINALITY{alias}" 2610 alias = "" 2611 2612 when = self.sql(expression, "when") 2613 if when: 2614 if self.HISTORICAL_DATA_POST_ALIAS: 2615 alias = f"{alias} {when}" 2616 else: 2617 table = f"{table} {when}" 2618 2619 changes = self.sql(expression, "changes") 2620 changes = f" {changes}" if changes else "" 2621 2622 rows_from = self.expressions(expression, key="rows_from") 2623 if rows_from: 2624 table = f"ROWS FROM {self.wrap(rows_from)}" 2625 2626 indexed = expression.args.get("indexed") 2627 if indexed is not None: 2628 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2629 else: 2630 indexed = "" 2631 2632 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2633 2634 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2635 table = self.func("TABLE", expression.this) 2636 alias = self.sql(expression, "alias") 2637 alias = f" AS {alias}" if alias else "" 2638 sample = self.sql(expression, "sample") 2639 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2640 joins = self.indent( 2641 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2642 ) 2643 return f"{table}{alias}{pivots}{sample}{joins}" 2644 2645 def tablesample_sql( 2646 self, 2647 expression: exp.TableSample, 2648 tablesample_keyword: str | None = None, 2649 ) -> str: 2650 if expression.expressions: 2651 self.unsupported("Multiple sample sets are not supported") 2652 return "" 2653 2654 method = self.sql(expression, "method") 2655 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2656 numerator = self.sql(expression, "bucket_numerator") 2657 denominator = self.sql(expression, "bucket_denominator") 2658 field = self.sql(expression, "bucket_field") 2659 field = f" ON {field}" if field else "" 2660 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2661 seed = self.sql(expression, "seed") 2662 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2663 2664 size = self.sql(expression, "size") 2665 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2666 size = f"{size} ROWS" 2667 2668 percent = self.sql(expression, "percent") 2669 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2670 percent = f"{percent} PERCENT" 2671 2672 expr = f"{bucket}{percent}{size}" 2673 if self.TABLESAMPLE_REQUIRES_PARENS: 2674 expr = f"({expr})" 2675 2676 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2677 2678 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2679 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2680 # the stored column name differs from the target dialect's natural output. 2681 columns = expression.args.get("columns") 2682 if not columns or len(expression.fields) != 1: 2683 return None 2684 2685 args = expression.args 2686 parser_cls = self.dialect.parser_class 2687 2688 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2689 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2690 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2691 2692 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2693 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2694 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2695 2696 if ( 2697 src_identify_pivot_strings == tgt_identify_pivot_strings 2698 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2699 and src_pivot_column_naming == tgt_pivot_column_naming 2700 ): 2701 return None 2702 2703 in_exprs = expression.fields[0].expressions 2704 step = len(columns) // len(in_exprs) 2705 2706 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2707 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2708 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2709 first_stored = columns[0].name 2710 2711 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2712 if not first_stored.startswith(first_base): 2713 return None 2714 2715 suffix = first_stored[len(first_base) :] 2716 2717 # Whether the target dialect would append an agg-name suffix for this pivot. 2718 # Spark single-agg uniquely drops the agg alias entirely. 2719 target_has_suffix = ( 2720 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2721 ) and any(a.alias for a in expression.expressions) 2722 source_has_suffix = suffix != "" 2723 2724 new_exprs: list[exp.Expression] = [] 2725 modified = False 2726 for val_idx, e in enumerate(in_exprs): 2727 if isinstance(e, exp.PivotAlias): 2728 new_exprs.append(e) 2729 continue 2730 2731 i = val_idx * step 2732 stored_full = columns[i].name 2733 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2734 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2735 2736 # Source had a suffix, but target won't apply one 2737 if source_has_suffix and not target_has_suffix: 2738 new_exprs.append( 2739 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2740 ) 2741 modified = True 2742 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2743 elif stored_value != target_value: 2744 new_exprs.append( 2745 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2746 ) 2747 modified = True 2748 else: 2749 new_exprs.append(e) 2750 2751 return new_exprs if modified else None 2752 2753 def pivot_sql(self, expression: exp.Pivot) -> str: 2754 expressions = self.expressions(expression, flat=True) 2755 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2756 2757 group = self.sql(expression, "group") 2758 2759 if expression.this: 2760 this = self.sql(expression, "this") 2761 if not expressions: 2762 sql = f"UNPIVOT {this}" 2763 else: 2764 on = f"{self.seg('ON')} {expressions}" 2765 into = self.sql(expression, "into") 2766 into = f"{self.seg('INTO')} {into}" if into else "" 2767 using = self.expressions(expression, key="using", flat=True) 2768 using = f"{self.seg('USING')} {using}" if using else "" 2769 sql = f"{direction} {this}{on}{into}{using}{group}" 2770 return self.prepend_ctes(expression, sql) 2771 2772 if not expression.unpivot: 2773 # Wrap IN-list values with explicit aliases where the target dialect would differ 2774 new_field_exprs = self._pivot_in_value_aliases(expression) 2775 if new_field_exprs is not None: 2776 expression.fields[0].set("expressions", new_field_exprs) 2777 2778 alias = self.sql(expression, "alias") 2779 if alias: 2780 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2781 2782 fields = self.expressions( 2783 expression, 2784 "fields", 2785 sep=" ", 2786 dynamic=True, 2787 new_line=True, 2788 skip_first=True, 2789 skip_last=True, 2790 ) 2791 2792 include_nulls = expression.args.get("include_nulls") 2793 if include_nulls is not None: 2794 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2795 else: 2796 nulls = "" 2797 2798 default_on_null = self.sql(expression, "default_on_null") 2799 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2800 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2801 return self.prepend_ctes(expression, sql) 2802 2803 def version_sql(self, expression: exp.Version) -> str: 2804 this = f"FOR {expression.name}" 2805 kind = expression.text("kind") 2806 expr = self.sql(expression, "expression") 2807 return f"{this} {kind} {expr}" 2808 2809 def tuple_sql(self, expression: exp.Tuple) -> str: 2810 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2811 2812 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2813 """ 2814 Returns (join_sql, from_sql) for UPDATE statements. 2815 - join_sql: placed after UPDATE table, before SET 2816 - from_sql: placed after SET clause (standard position) 2817 Dialects like MySQL need to convert FROM to JOIN syntax. 2818 """ 2819 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2820 return ("", self.sql(expression, "from_")) 2821 2822 # Qualify unqualified columns in SET clause with the target table 2823 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2824 target_table = expression.this 2825 if isinstance(target_table, exp.Table): 2826 target_name = exp.to_identifier(target_table.alias_or_name) 2827 for eq in expression.expressions: 2828 col = eq.this 2829 if isinstance(col, exp.Column) and not col.table: 2830 col.set("table", target_name) 2831 2832 table = from_expr.this 2833 if nested_joins := table.args.get("joins", []): 2834 table.set("joins", None) 2835 2836 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2837 for nested in nested_joins: 2838 if not nested.args.get("on") and not nested.args.get("using"): 2839 nested.set("on", exp.true()) 2840 join_sql += self.sql(nested) 2841 2842 return (join_sql, "") 2843 2844 def update_sql(self, expression: exp.Update) -> str: 2845 hint = self.sql(expression, "hint") 2846 this = self.sql(expression, "this") 2847 join_sql, from_sql = self._update_from_joins_sql(expression) 2848 set_sql = self.expressions(expression, flat=True) 2849 where_sql = self.sql(expression, "where") 2850 returning = self.sql(expression, "returning") 2851 order = self.sql(expression, "order") 2852 limit = self.sql(expression, "limit") 2853 if self.RETURNING_END: 2854 expression_sql = f"{from_sql}{where_sql}{returning}" 2855 else: 2856 expression_sql = f"{returning}{from_sql}{where_sql}" 2857 options = self.expressions(expression, key="options") 2858 options = f" OPTION({options})" if options else "" 2859 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2860 return self.prepend_ctes(expression, sql) 2861 2862 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2863 values_as_table = values_as_table and self.VALUES_AS_TABLE 2864 2865 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2866 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2867 args = self.expressions(expression) 2868 alias = self.sql(expression, "alias") 2869 values = f"VALUES{self.seg('')}{args}" 2870 values = ( 2871 f"({values})" 2872 if self.WRAP_DERIVED_VALUES 2873 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2874 else values 2875 ) 2876 values = self.query_modifiers(expression, values) 2877 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2878 return f"{values} AS {alias}{pivots}" if alias else f"{values}{pivots}" 2879 2880 # Converts `VALUES...` expression into a series of select unions. 2881 alias_node = expression.args.get("alias") 2882 column_names = alias_node and alias_node.columns 2883 2884 selects: list[exp.Query] = [] 2885 2886 for i, tup in enumerate(expression.expressions): 2887 row = tup.expressions 2888 2889 if i == 0 and column_names: 2890 row = [ 2891 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2892 ] 2893 2894 selects.append(exp.Select(expressions=row)) 2895 2896 if self.pretty: 2897 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2898 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2899 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2900 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2901 for key in ("order", "limit", "offset"): 2902 if value := expression.args.get(key): 2903 query.set(key, value.pop()) 2904 subquery = query.subquery(alias_node and alias_node.this, copy=False) 2905 subquery.set("pivots", expression.args.pop("pivots", None)) 2906 return self.subquery_sql(subquery) 2907 2908 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2909 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2910 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2911 return f"({self.query_modifiers(expression, unions)}){alias}{pivots}" 2912 2913 def var_sql(self, expression: exp.Var) -> str: 2914 return self.sql(expression, "this") 2915 2916 @unsupported_args("expressions") 2917 def into_sql(self, expression: exp.Into) -> str: 2918 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2919 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2920 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2921 2922 def from_sql(self, expression: exp.From) -> str: 2923 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2924 2925 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2926 grouping_sets = self.expressions(expression, indent=False) 2927 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2928 2929 def rollup_sql(self, expression: exp.Rollup) -> str: 2930 expressions = self.expressions(expression, indent=False) 2931 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2932 2933 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2934 this = self.sql(expression, "this") 2935 2936 columns = self.expressions(expression, flat=True) 2937 2938 from_sql = self.sql(expression, "from_index") 2939 from_sql = f" FROM {from_sql}" if from_sql else "" 2940 2941 properties = expression.args.get("properties") 2942 properties_sql = ( 2943 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2944 ) 2945 2946 return f"{this}({columns}){from_sql}{properties_sql}" 2947 2948 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2949 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2950 2951 def cube_sql(self, expression: exp.Cube) -> str: 2952 expressions = self.expressions(expression, indent=False) 2953 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2954 2955 def group_sql(self, expression: exp.Group) -> str: 2956 group_by_all = expression.args.get("all") 2957 if group_by_all is True: 2958 modifier = " ALL" 2959 elif group_by_all is False: 2960 modifier = " DISTINCT" 2961 else: 2962 modifier = "" 2963 2964 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2965 2966 grouping_sets = self.expressions(expression, key="grouping_sets") 2967 cube = self.expressions(expression, key="cube") 2968 rollup = self.expressions(expression, key="rollup") 2969 2970 groupings = csv( 2971 self.seg(grouping_sets) if grouping_sets else "", 2972 self.seg(cube) if cube else "", 2973 self.seg(rollup) if rollup else "", 2974 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2975 sep=self.GROUPINGS_SEP, 2976 ) 2977 2978 if ( 2979 expression.expressions 2980 and groupings 2981 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2982 ): 2983 add_separator = True 2984 2985 if grouping_sets: 2986 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2987 add_separator = False 2988 else: 2989 self.unsupported( 2990 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2991 ) 2992 2993 if add_separator: 2994 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2995 2996 return f"{group_by}{groupings}" 2997 2998 def having_sql(self, expression: exp.Having) -> str: 2999 this = self.indent(self.sql(expression, "this")) 3000 return f"{self.seg('HAVING')}{self.sep()}{this}" 3001 3002 def connectto_sql(self, expression: exp.ConnectTo) -> str: 3003 return f"CONNECT {self.sql(expression, 'this')}" 3004 3005 def disconnect_sql(self, expression: exp.Disconnect) -> str: 3006 return "DISCONNECT" 3007 3008 def connect_sql(self, expression: exp.Connect) -> str: 3009 start = self.sql(expression, "start") 3010 start = self.seg(f"START WITH {start}") if start else "" 3011 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 3012 connect = self.sql(expression, "connect") 3013 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 3014 return start + connect 3015 3016 def prior_sql(self, expression: exp.Prior) -> str: 3017 return f"PRIOR {self.sql(expression, 'this')}" 3018 3019 def join_sql(self, expression: exp.Join, join_op: str | None = None) -> str: 3020 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 3021 side = None 3022 else: 3023 side = expression.side 3024 3025 op_sql = " ".join( 3026 op 3027 for op in ( 3028 expression.method, 3029 "GLOBAL" if expression.args.get("global_") else None, 3030 side, 3031 expression.kind, 3032 expression.hint if self.JOIN_HINTS else None, 3033 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 3034 ) 3035 if op 3036 ) 3037 match_cond = self.sql(expression, "match_condition") 3038 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 3039 nearest = self.sql(expression, "nearest") 3040 nearest = f" {nearest}" if nearest else "" 3041 on_sql = self.sql(expression, "on") 3042 using = expression.args.get("using") 3043 3044 if not on_sql and using: 3045 on_sql = csv(*(self.sql(column) for column in using)) 3046 3047 this = expression.this 3048 this_sql = self.sql(this) 3049 3050 exprs = self.expressions(expression) 3051 if exprs: 3052 this_sql = f"{this_sql},{self.seg(exprs)}" 3053 3054 if on_sql: 3055 on_sql = self.indent(on_sql, skip_first=True) 3056 space = self.seg(" " * self.pad) if self.pretty else " " 3057 if using: 3058 on_sql = f"{space}USING ({on_sql})" 3059 else: 3060 on_sql = f"{space}ON {on_sql}" 3061 elif not op_sql and not nearest: 3062 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 3063 return f" {this_sql}" 3064 3065 return f", {this_sql}" 3066 3067 if join_op: 3068 op_sql = join_op 3069 elif op_sql != "STRAIGHT_JOIN": 3070 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 3071 3072 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3073 return f"{self.seg(op_sql)} {this_sql}{match_cond}{nearest}{on_sql}{pivots}" 3074 3075 def nearest_sql(self, expression: exp.Nearest) -> str: 3076 if not self.SUPPORTS_NEAREST_JOIN: 3077 self.unsupported("NEAREST joins are not supported") 3078 3079 approximate = "APPROX " if expression.args.get("approximate") else "" 3080 metric = "SIMILARITY" if expression.args.get("similarity") else "DISTANCE" 3081 return f"{approximate}NEAREST {self.sql(expression, 'count')} BY {metric} {self.sql(expression, 'this')}" 3082 3083 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 3084 args = self.expressions(expression, flat=True) 3085 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 3086 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 3087 3088 def lateral_op(self, expression: exp.Lateral) -> str: 3089 cross_apply = expression.args.get("cross_apply") 3090 3091 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 3092 if cross_apply is True: 3093 op = "INNER JOIN " 3094 elif cross_apply is False: 3095 op = "LEFT JOIN " 3096 else: 3097 op = "" 3098 3099 return f"{op}LATERAL" 3100 3101 def lateral_sql(self, expression: exp.Lateral) -> str: 3102 this = self.sql(expression, "this") 3103 3104 if expression.args.get("view"): 3105 alias = expression.args["alias"] 3106 columns = self.expressions(alias, key="columns", flat=True) 3107 table = f" {alias.name}" if alias.name else "" 3108 columns = f" AS {columns}" if columns else "" 3109 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 3110 return f"{op_sql}{self.sep()}{this}{table}{columns}" 3111 3112 table_alias = expression.args.get("alias") 3113 offset = expression.this.args.get("offset") 3114 3115 if ( 3116 self.UNNEST_WITH_ORDINALITY 3117 and table_alias 3118 and isinstance(expression.this, exp.Unnest) 3119 and isinstance(offset, exp.Identifier) 3120 ): 3121 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3122 table_alias = table_alias.copy() 3123 table_alias.append("columns", offset.copy()) 3124 3125 alias = self.sql(table_alias) 3126 alias = f" AS {alias}" if alias else "" 3127 3128 ordinality = expression.args.get("ordinality") or "" 3129 if ordinality: 3130 ordinality = f" WITH ORDINALITY{alias}" 3131 alias = "" 3132 3133 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 3134 3135 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3136 this = self.sql(expression, "this") 3137 3138 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3139 return this 3140 3141 args = [ 3142 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3143 for e in (expression.args.get(k) for k in ("offset", "expression")) 3144 if e 3145 ] 3146 3147 args_sql = ", ".join(self.sql(e) for e in args) 3148 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3149 expressions = self.expressions(expression, flat=True) 3150 limit_options = self.sql(expression, "limit_options") 3151 expressions = f" BY {expressions}" if expressions else "" 3152 3153 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 3154 3155 def offset_sql(self, expression: exp.Offset) -> str: 3156 this = self.sql(expression, "this") 3157 value = expression.expression 3158 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3159 expressions = self.expressions(expression, flat=True) 3160 expressions = f" BY {expressions}" if expressions else "" 3161 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 3162 3163 def setitem_sql(self, expression: exp.SetItem) -> str: 3164 kind = self.sql(expression, "kind") 3165 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3166 kind = "" 3167 else: 3168 kind = f"{kind} " if kind else "" 3169 this = self.sql(expression, "this") 3170 expressions = self.expressions(expression) 3171 collate = self.sql(expression, "collate") 3172 collate = f" COLLATE {collate}" if collate else "" 3173 global_ = "GLOBAL " if expression.args.get("global_") else "" 3174 return f"{global_}{kind}{this}{expressions}{collate}" 3175 3176 def set_sql(self, expression: exp.Set) -> str: 3177 expressions = f" {self.expressions(expression, flat=True)}" 3178 tag = " TAG" if expression.args.get("tag") else "" 3179 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3180 3181 def queryband_sql(self, expression: exp.QueryBand) -> str: 3182 this = self.sql(expression, "this") 3183 update = " UPDATE" if expression.args.get("update") else "" 3184 scope = self.sql(expression, "scope") 3185 scope = f" FOR {scope}" if scope else "" 3186 3187 return f"QUERY_BAND = {this}{update}{scope}" 3188 3189 def pragma_sql(self, expression: exp.Pragma) -> str: 3190 return f"PRAGMA {self.sql(expression, 'this')}" 3191 3192 def lock_sql(self, expression: exp.Lock) -> str: 3193 if not self.LOCKING_READS_SUPPORTED: 3194 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3195 return "" 3196 3197 update = expression.args["update"] 3198 key = expression.args.get("key") 3199 if update: 3200 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3201 else: 3202 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3203 expressions = self.expressions(expression, flat=True) 3204 expressions = f" OF {expressions}" if expressions else "" 3205 wait = expression.args.get("wait") 3206 3207 if wait is not None: 3208 if isinstance(wait, exp.Literal): 3209 wait = f" WAIT {self.sql(wait)}" 3210 else: 3211 wait = " NOWAIT" if wait else " SKIP LOCKED" 3212 3213 return f"{lock_type}{expressions}{wait or ''}" 3214 3215 def literal_sql(self, expression: exp.Literal) -> str: 3216 text = expression.this or "" 3217 if expression.is_string: 3218 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3219 return text 3220 3221 def escape_str( 3222 self, 3223 text: str, 3224 escape_backslash: bool = True, 3225 delimiter: str | None = None, 3226 escaped_delimiter: str | None = None, 3227 is_byte_string: bool = False, 3228 is_bytes: bool = False, 3229 ) -> str: 3230 if is_byte_string: 3231 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3232 else: 3233 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3234 3235 if supports_escape_sequences: 3236 text = "".join( 3237 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3238 for ch in text 3239 ) 3240 3241 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3242 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3243 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3244 3245 delimiter = delimiter or self.dialect.QUOTE_END 3246 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3247 3248 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3249 3250 def loaddata_sql(self, expression: exp.LoadData) -> str: 3251 is_overwrite = expression.args.get("overwrite") 3252 overwrite = " OVERWRITE" if is_overwrite else "" 3253 this = self.sql(expression, "this") 3254 3255 files = expression.args.get("files") 3256 if files: 3257 files_sql = self.expressions(files, flat=True) 3258 files_sql = f"FILES{self.wrap(files_sql)}" 3259 if is_overwrite: 3260 this = f" {this}" 3261 elif expression.args.get("temp"): 3262 this = f" INTO TEMP TABLE {this}" 3263 else: 3264 this = f" INTO TABLE {this}" 3265 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3266 3267 local = " LOCAL" if expression.args.get("local") else "" 3268 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3269 this = f" INTO TABLE {this}" 3270 partition = self.sql(expression, "partition") 3271 partition = f" {partition}" if partition else "" 3272 input_format = self.sql(expression, "input_format") 3273 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3274 serde = self.sql(expression, "serde") 3275 serde = f" SERDE {serde}" if serde else "" 3276 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3277 3278 def null_sql(self, *_) -> str: 3279 return "NULL" 3280 3281 def boolean_sql(self, expression: exp.Boolean) -> str: 3282 return "TRUE" if expression.this else "FALSE" 3283 3284 def booland_sql(self, expression: exp.Booland) -> str: 3285 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3286 3287 def boolor_sql(self, expression: exp.Boolor) -> str: 3288 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3289 3290 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3291 this = self.sql(expression, "this") 3292 this = f"{this} " if this else this 3293 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3294 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3295 3296 def withfill_sql(self, expression: exp.WithFill) -> str: 3297 from_sql = self.sql(expression, "from_") 3298 from_sql = f" FROM {from_sql}" if from_sql else "" 3299 to_sql = self.sql(expression, "to") 3300 to_sql = f" TO {to_sql}" if to_sql else "" 3301 step_sql = self.sql(expression, "step") 3302 step_sql = f" STEP {step_sql}" if step_sql else "" 3303 interpolated_values = [ 3304 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3305 if isinstance(e, exp.Alias) 3306 else self.sql(e, "this") 3307 for e in expression.args.get("interpolate") or [] 3308 ] 3309 interpolate = ( 3310 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3311 ) 3312 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3313 3314 def cluster_sql(self, expression: exp.Cluster) -> str: 3315 return self.op_expressions("CLUSTER BY", expression) 3316 3317 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3318 if expression.this: 3319 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3320 return "" 3321 expressions = self.expressions(expression, flat=True) 3322 return f"CLUSTER BY ({expressions})" 3323 3324 def distribute_sql(self, expression: exp.Distribute) -> str: 3325 return self.op_expressions("DISTRIBUTE BY", expression) 3326 3327 def sort_sql(self, expression: exp.Sort) -> str: 3328 return self.op_expressions("SORT BY", expression) 3329 3330 def _resolve_ordered_for_null_ordering_simulation( 3331 self, expression: exp.Ordered 3332 ) -> exp.Expr | None: 3333 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3334 3335 Returns the underlying expression of the uniquely-matching projection 3336 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3337 simulation, since the CASE is evaluated in FROM-clause scope rather 3338 than alias scope (MySQL error 1052). Returns None if no safe 3339 substitution applies, leaving the original behaviour unchanged. 3340 """ 3341 this = expression.this 3342 if not (isinstance(this, exp.Column) and not this.table): 3343 return None 3344 3345 # Only a query's own ORDER BY can reference its projection aliases 3346 order = expression.parent 3347 ancestor = order.parent if isinstance(order, exp.Order) else None 3348 if not isinstance(ancestor, exp.Select): 3349 return None 3350 3351 column_name = this.name 3352 matched: list[exp.Expr] = [ 3353 p.this if isinstance(p, exp.Alias) else p 3354 for p in ancestor.selects 3355 if p.output_name == column_name 3356 ] 3357 match = matched[0] if len(matched) == 1 else None 3358 3359 # Skip the substitution when it would be identical to the existing 3360 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3361 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3362 return None 3363 3364 return match 3365 3366 def ordered_sql(self, expression: exp.Ordered) -> str: 3367 desc = expression.args.get("desc") 3368 asc = not desc 3369 3370 nulls_first = expression.args.get("nulls_first") 3371 nulls_last = not nulls_first 3372 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3373 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3374 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3375 3376 this = self.sql(expression, "this") 3377 3378 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3379 nulls_sort_change = "" 3380 if nulls_first and ( 3381 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3382 ): 3383 nulls_sort_change = " NULLS FIRST" 3384 elif ( 3385 nulls_last 3386 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3387 and not nulls_are_last 3388 ): 3389 nulls_sort_change = " NULLS LAST" 3390 3391 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3392 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3393 window = expression.find_ancestor(exp.Window, exp.Select) 3394 3395 if isinstance(window, exp.Window): 3396 window_this = window.this 3397 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3398 window_this = window_this.this 3399 spec = window.args.get("spec") 3400 else: 3401 window_this = None 3402 spec = None 3403 3404 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3405 # without a spec or with a ROWS spec, but not with RANGE 3406 if not ( 3407 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3408 and (not spec or spec.text("kind").upper() == "ROWS") 3409 ): 3410 if window_this and spec: 3411 self.unsupported( 3412 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3413 ) 3414 nulls_sort_change = "" 3415 elif self.NULL_ORDERING_SUPPORTED is False and ( 3416 (asc and nulls_sort_change == " NULLS LAST") 3417 or (desc and nulls_sort_change == " NULLS FIRST") 3418 ): 3419 # BigQuery does not allow these ordering/nulls combinations when used under 3420 # an aggregation func or under a window containing one 3421 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3422 3423 if isinstance(ancestor, exp.Window): 3424 ancestor = ancestor.this 3425 if isinstance(ancestor, exp.AggFunc): 3426 self.unsupported( 3427 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3428 ) 3429 nulls_sort_change = "" 3430 elif self.NULL_ORDERING_SUPPORTED is None: 3431 if expression.this.is_int: 3432 self.unsupported( 3433 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3434 ) 3435 elif not isinstance(expression.this, exp.Rand): 3436 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3437 target = self.sql(resolved) if resolved is not None else this 3438 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3439 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3440 nulls_sort_change = "" 3441 3442 with_fill = self.sql(expression, "with_fill") 3443 with_fill = f" {with_fill}" if with_fill else "" 3444 3445 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3446 3447 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3448 window_frame = self.sql(expression, "window_frame") 3449 window_frame = f"{window_frame} " if window_frame else "" 3450 3451 this = self.sql(expression, "this") 3452 3453 return f"{window_frame}{this}" 3454 3455 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3456 partition = self.partition_by_sql(expression) 3457 order = self.sql(expression, "order") 3458 measures = self.expressions(expression, key="measures") 3459 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3460 rows = self.sql(expression, "rows") 3461 rows = self.seg(rows) if rows else "" 3462 after = self.sql(expression, "after") 3463 after = self.seg(after) if after else "" 3464 pattern = self.sql(expression, "pattern") 3465 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3466 definition_sqls = [ 3467 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3468 for definition in expression.args.get("define", []) 3469 ] 3470 definitions = self.expressions(sqls=definition_sqls) 3471 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3472 body = "".join( 3473 ( 3474 partition, 3475 order, 3476 measures, 3477 rows, 3478 after, 3479 pattern, 3480 define, 3481 ) 3482 ) 3483 alias = self.sql(expression, "alias") 3484 alias = f" {alias}" if alias else "" 3485 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3486 3487 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3488 limit = expression.args.get("limit") 3489 3490 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3491 count = limit.args.get("count") 3492 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3493 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3494 limit = exp.Limit( 3495 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3496 ) 3497 elif ( 3498 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3499 ): 3500 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3501 3502 return csv( 3503 *sqls, 3504 *[self.sql(join) for join in expression.args.get("joins") or []], 3505 self.sql(expression, "match"), 3506 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3507 self.sql(expression, "prewhere"), 3508 self.sql(expression, "where"), 3509 self.sql(expression, "connect"), 3510 self.sql(expression, "group"), 3511 self.sql(expression, "having"), 3512 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3513 self.sql(expression, "order"), 3514 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3515 *self.after_limit_modifiers(expression), 3516 self.sql(expression, "for_"), 3517 self.options_modifier(expression), 3518 sep="", 3519 ) 3520 3521 def options_modifier(self, expression: exp.Expr) -> str: 3522 options = self.expressions(expression, key="options") 3523 return f" {options}" if options else "" 3524 3525 def forclause_sql(self, expression: exp.ForClause) -> str: 3526 kind = expression.args["kind"] 3527 if kind == "BROWSE": 3528 return f"{self.sep()}FOR BROWSE" 3529 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3530 # the target dialect doesn't support QueryOption, so we drop the clause. 3531 options = self.expressions(expression, key="expressions") 3532 if not options: 3533 return "" 3534 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3535 3536 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3537 self.unsupported("Unsupported query option.") 3538 return "" 3539 3540 def offset_limit_modifiers( 3541 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3542 ) -> list[str]: 3543 return [ 3544 self.sql(expression, "offset") if fetch else self.sql(limit), 3545 self.sql(limit) if fetch else self.sql(expression, "offset"), 3546 ] 3547 3548 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3549 locks = self.expressions(expression, key="locks", sep=" ") 3550 locks = f" {locks}" if locks else "" 3551 return [locks, self.sql(expression, "sample")] 3552 3553 def select_sql(self, expression: exp.Select) -> str: 3554 into = expression.args.get("into") 3555 if not self.SUPPORTS_SELECT_INTO and into: 3556 into.pop() 3557 3558 hint = self.sql(expression, "hint") 3559 distinct = self.sql(expression, "distinct") 3560 distinct = f" {distinct}" if distinct else "" 3561 kind = self.sql(expression, "kind") 3562 3563 limit = expression.args.get("limit") 3564 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3565 top = self.limit_sql(limit, top=True) 3566 limit.pop() 3567 else: 3568 top = "" 3569 3570 expressions = self.expressions(expression) 3571 3572 if kind: 3573 if kind in self.SELECT_KINDS: 3574 kind = f" AS {kind}" 3575 else: 3576 if kind == "STRUCT": 3577 expressions = self.expressions( 3578 sqls=[ 3579 self.sql( 3580 exp.Struct( 3581 expressions=[ 3582 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3583 if isinstance(e, exp.Alias) 3584 else e 3585 for e in expression.expressions 3586 ] 3587 ) 3588 ) 3589 ] 3590 ) 3591 kind = "" 3592 3593 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3594 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3595 3596 exclude = expression.args.get("exclude") 3597 3598 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3599 exclude_sql = self.expressions(sqls=exclude, flat=True) 3600 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3601 3602 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3603 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3604 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3605 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3606 sql = self.query_modifiers( 3607 expression, 3608 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3609 self.sql(expression, "into", comment=False), 3610 self.sql(expression, "from_", comment=False), 3611 ) 3612 3613 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3614 if expression.args.get("with_"): 3615 sql = self.maybe_comment(sql, expression) 3616 expression.pop_comments() 3617 3618 sql = self.prepend_ctes(expression, sql) 3619 3620 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3621 expression.set("exclude", None) 3622 subquery = expression.subquery(copy=False) 3623 star = exp.Star(except_=exclude) 3624 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3625 3626 if not self.SUPPORTS_SELECT_INTO and into: 3627 if into.args.get("temporary"): 3628 table_kind = " TEMPORARY" 3629 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3630 table_kind = " UNLOGGED" 3631 else: 3632 table_kind = "" 3633 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3634 3635 return sql 3636 3637 def schema_sql(self, expression: exp.Schema) -> str: 3638 this = self.sql(expression, "this") 3639 sql = self.schema_columns_sql(expression) 3640 return f"{this} {sql}" if this and sql else this or sql 3641 3642 def schema_columns_sql(self, expression: exp.Expr) -> str: 3643 if expression.expressions: 3644 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3645 return "" 3646 3647 def star_sql(self, expression: exp.Star) -> str: 3648 except_ = self.expressions(expression, key="except_", flat=True) 3649 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3650 replace = self.expressions(expression, key="replace", flat=True) 3651 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3652 rename = self.expressions(expression, key="rename", flat=True) 3653 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3654 ilike = self.sql(expression, "ilike") 3655 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3656 return f"*{ilike}{except_}{replace}{rename}" 3657 3658 def parameter_sql(self, expression: exp.Parameter) -> str: 3659 this = self.sql(expression, "this") 3660 return f"{self.PARAMETER_TOKEN}{this}" 3661 3662 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3663 this = self.sql(expression, "this") 3664 kind = expression.text("kind") 3665 if kind: 3666 kind = f"{kind}." 3667 return f"@@{kind}{this}" 3668 3669 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3670 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3671 3672 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3673 alias = self.sql(expression, "alias") 3674 alias = f"{sep}{alias}" if alias else "" 3675 sample = self.sql(expression, "sample") 3676 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3677 alias = f"{sample}{alias}" 3678 3679 # Set to None so it's not generated again by self.query_modifiers() 3680 expression.set("sample", None) 3681 3682 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3683 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3684 return self.prepend_ctes(expression, sql) 3685 3686 def qualify_sql(self, expression: exp.Qualify) -> str: 3687 this = self.indent(self.sql(expression, "this")) 3688 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3689 3690 def unnest_sql(self, expression: exp.Unnest) -> str: 3691 args = self.expressions(expression, flat=True) 3692 3693 alias = expression.args.get("alias") 3694 offset = expression.args.get("offset") 3695 3696 if self.UNNEST_WITH_ORDINALITY: 3697 if alias and isinstance(offset, exp.Expr): 3698 alias.append("columns", offset) 3699 expression.set("offset", None) 3700 3701 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3702 columns = alias.columns 3703 alias = self.sql(columns[0]) if columns else "" 3704 else: 3705 alias = self.sql(alias) 3706 3707 alias = f" AS {alias}" if alias else alias 3708 if self.UNNEST_WITH_ORDINALITY: 3709 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3710 else: 3711 if isinstance(offset, exp.Expr): 3712 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3713 elif offset: 3714 suffix = f"{alias} WITH OFFSET" 3715 else: 3716 suffix = alias 3717 3718 return f"UNNEST({args}){suffix}" 3719 3720 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3721 return "" 3722 3723 def where_sql(self, expression: exp.Where) -> str: 3724 this = self.indent(self.sql(expression, "this")) 3725 return f"{self.seg('WHERE')}{self.sep()}{this}" 3726 3727 def window_sql(self, expression: exp.Window) -> str: 3728 this = self.sql(expression, "this") 3729 partition = self.partition_by_sql(expression) 3730 order = expression.args.get("order") 3731 order = self.order_sql(order, flat=True) if order else "" 3732 spec = self.sql(expression, "spec") 3733 alias = self.sql(expression, "alias") 3734 over = self.sql(expression, "over") or "OVER" 3735 3736 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3737 3738 first = expression.args.get("first") 3739 if first is None: 3740 first = "" 3741 else: 3742 first = "FIRST" if first else "LAST" 3743 3744 if not partition and not order and not spec and alias: 3745 # WINDOW w2 AS w1 is unparseable in DuckDB/Postgres; parens are required. 3746 if expression.arg_key == "windows": 3747 return f"{this} ({alias})" 3748 return f"{this} {alias}" 3749 3750 args = self.format_args( 3751 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3752 ) 3753 return f"{this} ({args})" 3754 3755 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3756 partition = self.expressions(expression, key="partition_by", flat=True) 3757 return f"PARTITION BY {partition}" if partition else "" 3758 3759 def partitionbyexcluding_sql(self, expression: exp.PartitionByExcluding) -> str: 3760 return f"EXCLUDING {self.expressions(expression, flat=True)}" 3761 3762 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3763 kind = self.sql(expression, "kind") 3764 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3765 end = ( 3766 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3767 or "CURRENT ROW" 3768 ) 3769 3770 window_spec = f"{kind} BETWEEN {start} AND {end}" 3771 3772 exclude = self.sql(expression, "exclude") 3773 if exclude: 3774 if self.SUPPORTS_WINDOW_EXCLUDE: 3775 window_spec += f" EXCLUDE {exclude}" 3776 else: 3777 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3778 3779 return window_spec 3780 3781 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3782 this = self.sql(expression, "this") 3783 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3784 return f"{this} WITHIN GROUP ({expression_sql})" 3785 3786 def between_sql(self, expression: exp.Between) -> str: 3787 this = self.sql(expression, "this") 3788 low = self.sql(expression, "low") 3789 high = self.sql(expression, "high") 3790 symmetric = expression.args.get("symmetric") 3791 3792 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3793 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3794 3795 flag = ( 3796 " SYMMETRIC" 3797 if symmetric 3798 else " ASYMMETRIC" 3799 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3800 else "" # silently drop ASYMMETRIC – semantics identical 3801 ) 3802 return f"{this} BETWEEN{flag} {low} AND {high}" 3803 3804 def bracket_offset_expressions( 3805 self, expression: exp.Bracket, index_offset: int | None = None 3806 ) -> list[exp.Expr]: 3807 if expression.args.get("json_access"): 3808 return expression.expressions 3809 3810 return apply_index_offset( 3811 expression.this, 3812 expression.expressions, 3813 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3814 dialect=self.dialect, 3815 ) 3816 3817 def bracket_sql(self, expression: exp.Bracket) -> str: 3818 expressions = self.bracket_offset_expressions(expression) 3819 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3820 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3821 3822 def all_sql(self, expression: exp.All) -> str: 3823 this = self.sql(expression, "this") 3824 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3825 this = self.wrap(this) 3826 return f"ALL {this}" 3827 3828 def any_sql(self, expression: exp.Any) -> str: 3829 this = self.sql(expression, "this") 3830 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3831 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3832 this = self.wrap(this) 3833 return f"ANY{this}" 3834 return f"ANY {this}" 3835 3836 def exists_sql(self, expression: exp.Exists) -> str: 3837 return f"EXISTS{self.wrap(expression)}" 3838 3839 def case_sql(self, expression: exp.Case) -> str: 3840 this = self.sql(expression, "this") 3841 statements = [f"CASE {this}" if this else "CASE"] 3842 3843 for e in expression.args["ifs"]: 3844 statements.append(f"WHEN {self.sql(e, 'this')}") 3845 statements.append(f"THEN {self.sql(e, 'true')}") 3846 3847 default = self.sql(expression, "default") 3848 3849 if default: 3850 statements.append(f"ELSE {default}") 3851 3852 statements.append("END") 3853 3854 if self.pretty and self.too_wide(statements): 3855 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3856 3857 return " ".join(statements) 3858 3859 def constraint_sql(self, expression: exp.Constraint) -> str: 3860 this = self.sql(expression, "this") 3861 expressions = self.expressions(expression, flat=True) 3862 return f"CONSTRAINT {this} {expressions}" 3863 3864 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3865 order = expression.args.get("order") 3866 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3867 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3868 3869 def extract_sql(self, expression: exp.Extract) -> str: 3870 import sqlglot.dialects.dialect 3871 3872 this = ( 3873 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3874 if self.NORMALIZE_EXTRACT_DATE_PARTS 3875 else expression.this 3876 ) 3877 if self.EXTRACT_ALLOWS_QUOTES: 3878 this_sql = self.sql(this) 3879 elif isinstance(this, exp.WeekStart): 3880 this_sql = self.weekstart_name(this) 3881 else: 3882 this_sql = this.name 3883 expression_sql = self.sql(expression, "expression") 3884 3885 return f"EXTRACT({this_sql} FROM {expression_sql})" 3886 3887 def trim_sql(self, expression: exp.Trim) -> str: 3888 trim_type = self.sql(expression, "position") 3889 3890 if trim_type == "LEADING": 3891 func_name = "LTRIM" 3892 elif trim_type == "TRAILING": 3893 func_name = "RTRIM" 3894 else: 3895 func_name = "TRIM" 3896 3897 return self.func(func_name, expression.this, expression.expression) 3898 3899 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3900 args = expression.expressions 3901 if isinstance(expression, exp.ConcatWs): 3902 args = args[1:] # Skip the delimiter 3903 3904 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3905 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3906 3907 concat_coalesce = ( 3908 self.dialect.CONCAT_WS_COALESCE 3909 if isinstance(expression, exp.ConcatWs) 3910 else self.dialect.CONCAT_COALESCE 3911 ) 3912 3913 if not concat_coalesce and expression.args.get("coalesce"): 3914 3915 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3916 if not e.type: 3917 import sqlglot.optimizer.annotate_types 3918 3919 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3920 3921 if e.is_string or e.is_type(exp.DType.ARRAY): 3922 return e 3923 3924 return exp.func("coalesce", e, exp.Literal.string("")) 3925 3926 args = [_wrap_with_coalesce(e) for e in args] 3927 3928 return args 3929 3930 def concat_sql(self, expression: exp.Concat) -> str: 3931 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3932 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3933 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3934 # instead of coalescing them to empty string. 3935 import sqlglot.dialects.dialect 3936 3937 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3938 3939 expressions = self.convert_concat_args(expression) 3940 3941 # Some dialects don't allow a single-argument CONCAT call 3942 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3943 return self.sql(expressions[0]) 3944 3945 return self.func("CONCAT", *expressions) 3946 3947 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3948 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3949 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3950 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3951 all_args = expression.expressions 3952 expression.set("coalesce", True) 3953 return self.sql( 3954 exp.case() 3955 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3956 .else_(expression) 3957 ) 3958 3959 return self.func( 3960 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3961 ) 3962 3963 def check_sql(self, expression: exp.Check) -> str: 3964 this = self.sql(expression, key="this") 3965 return f"CHECK ({this})" 3966 3967 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3968 this = self.sql(expression, "this") 3969 this = f" {this}" if this else "" 3970 expressions = self.expressions(expression, flat=True) 3971 expressions = f" ({expressions})" if expressions else "" 3972 reference = self.sql(expression, "reference") 3973 reference = f" {reference}" if reference else "" 3974 delete = self.sql(expression, "delete") 3975 delete = f" ON DELETE {delete}" if delete else "" 3976 update = self.sql(expression, "update") 3977 update = f" ON UPDATE {update}" if update else "" 3978 options = self.expressions(expression, key="options", flat=True, sep=" ") 3979 options = f" {options}" if options else "" 3980 return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}" 3981 3982 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3983 this = self.sql(expression, "this") 3984 this = f" {this}" if this else "" 3985 expressions = self.expressions(expression, flat=True) 3986 include = self.sql(expression, "include") 3987 options = self.expressions(expression, key="options", flat=True, sep=" ") 3988 options = f" {options}" if options else "" 3989 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3990 3991 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3992 self.unsupported("TIMESERIES primary key columns are not supported") 3993 return self.sql(expression, "this") 3994 3995 def if_sql(self, expression: exp.If) -> str: 3996 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3997 3998 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3999 if self.MATCH_AGAINST_TABLE_PREFIX: 4000 expressions = [] 4001 for expr in expression.expressions: 4002 if isinstance(expr, exp.Table): 4003 expressions.append(f"TABLE {self.sql(expr)}") 4004 else: 4005 expressions.append(expr) 4006 else: 4007 expressions = expression.expressions 4008 4009 modifier = expression.args.get("modifier") 4010 modifier = f" {modifier}" if modifier else "" 4011 return ( 4012 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 4013 ) 4014 4015 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 4016 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 4017 4018 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 4019 path = self.expressions(expression, sep="", flat=True).lstrip(".") 4020 4021 if self.QUOTE_JSON_PATH: 4022 path = self.escape_str(path) 4023 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 4024 4025 return path 4026 4027 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 4028 if isinstance(expression, exp.JSONPathPart): 4029 transform = self.TRANSFORMS.get(expression.__class__) 4030 if not callable(transform): 4031 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 4032 return "" 4033 4034 return transform(self, expression) 4035 4036 if isinstance(expression, int): 4037 return str(expression) 4038 4039 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 4040 escaped = expression.replace("'", "\\'") 4041 escaped = f"'{escaped}'" 4042 else: 4043 escaped = expression.replace('"', '\\"') 4044 escaped = f'"{escaped}"' 4045 4046 return escaped 4047 4048 def formatjson_sql(self, expression: exp.FormatJson) -> str: 4049 return f"{self.sql(expression, 'this')} FORMAT JSON" 4050 4051 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 4052 # Output the Teradata column FORMAT override. 4053 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 4054 this = self.sql(expression, "this") 4055 fmt = self.sql(expression, "format") 4056 return f"{this} (FORMAT {fmt})" 4057 4058 def teradatatitle_sql(self, expression: exp.TeradataTitle) -> str: 4059 self.unsupported("Teradata TITLE display headings are not supported") 4060 return self.sql(expression, "this") 4061 4062 def _jsonobject_sql( 4063 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 4064 ) -> str: 4065 null_handling = expression.args.get("null_handling") 4066 null_handling = f" {null_handling}" if null_handling else "" 4067 4068 unique_keys = expression.args.get("unique_keys") 4069 if unique_keys is not None: 4070 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 4071 else: 4072 unique_keys = "" 4073 4074 return_type = self.sql(expression, "return_type") 4075 return_type = f" RETURNING {return_type}" if return_type else "" 4076 encoding = self.sql(expression, "encoding") 4077 encoding = f" ENCODING {encoding}" if encoding else "" 4078 4079 if not name: 4080 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 4081 4082 return self.func( 4083 name, 4084 *expression.expressions, 4085 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 4086 ) 4087 4088 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 4089 null_handling = expression.args.get("null_handling") 4090 null_handling = f" {null_handling}" if null_handling else "" 4091 return_type = self.sql(expression, "return_type") 4092 return_type = f" RETURNING {return_type}" if return_type else "" 4093 strict = " STRICT" if expression.args.get("strict") else "" 4094 return self.func( 4095 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 4096 ) 4097 4098 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 4099 this = self.sql(expression, "this") 4100 order = self.sql(expression, "order") 4101 null_handling = expression.args.get("null_handling") 4102 null_handling = f" {null_handling}" if null_handling else "" 4103 return_type = self.sql(expression, "return_type") 4104 return_type = f" RETURNING {return_type}" if return_type else "" 4105 strict = " STRICT" if expression.args.get("strict") else "" 4106 return self.func( 4107 "JSON_ARRAYAGG", 4108 this, 4109 suffix=f"{order}{null_handling}{return_type}{strict})", 4110 ) 4111 4112 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 4113 path = self.sql(expression, "path") 4114 path = f" PATH {path}" if path else "" 4115 nested_schema = self.sql(expression, "nested_schema") 4116 4117 if nested_schema: 4118 return f"NESTED{path} {nested_schema}" 4119 4120 this = self.sql(expression, "this") 4121 kind = self.sql(expression, "kind") 4122 kind = f" {kind}" if kind else "" 4123 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 4124 4125 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 4126 return f"{this}{kind}{format_json}{path}{ordinality}" 4127 4128 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 4129 return self.func("COLUMNS", *expression.expressions) 4130 4131 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4132 this = self.sql(expression, "this") 4133 path = self.sql(expression, "path") 4134 path = f", {path}" if path else "" 4135 error_handling = expression.args.get("error_handling") 4136 error_handling = f" {error_handling}" if error_handling else "" 4137 empty_handling = expression.args.get("empty_handling") 4138 empty_handling = f" {empty_handling}" if empty_handling else "" 4139 schema = self.sql(expression, "schema") 4140 return self.func( 4141 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4142 ) 4143 4144 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4145 this = self.sql(expression, "this") 4146 kind = self.sql(expression, "kind") 4147 path = self.sql(expression, "path") 4148 path = f" {path}" if path else "" 4149 as_json = " AS JSON" if expression.args.get("as_json") else "" 4150 return f"{this} {kind}{path}{as_json}" 4151 4152 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4153 this = self.sql(expression, "this") 4154 path = self.sql(expression, "path") 4155 path = f", {path}" if path else "" 4156 expressions = self.expressions(expression) 4157 with_ = ( 4158 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4159 if expressions 4160 else "" 4161 ) 4162 return f"OPENJSON({this}{path}){with_}" 4163 4164 def in_sql(self, expression: exp.In) -> str: 4165 query = expression.args.get("query") 4166 unnest = expression.args.get("unnest") 4167 field = expression.args.get("field") 4168 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4169 4170 if query: 4171 in_sql = self.sql(query) 4172 elif unnest: 4173 in_sql = self.in_unnest_op(unnest) 4174 elif field: 4175 in_sql = self.sql(field) 4176 else: 4177 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4178 4179 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 4180 4181 def in_unnest_op(self, unnest: exp.Unnest) -> str: 4182 return f"(SELECT {self.sql(unnest)})" 4183 4184 def interval_sql(self, expression: exp.Interval) -> str: 4185 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4186 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4187 exp.AutoRefreshProperty, 4188 ) 4189 interval_keyword = "INTERVAL" if include_keyword else "" 4190 unit_expression = expression.args.get("unit") 4191 unit = self.sql(unit_expression) if unit_expression else "" 4192 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4193 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4194 unit = f" {unit}" if unit else "" 4195 4196 if self.SINGLE_STRING_INTERVAL: 4197 this = expression.this.name if expression.this else "" 4198 if this: 4199 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4200 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4201 return f"{interval_keyword}{self.sql(exp.Literal.string(this))}{unit}" 4202 return f"{interval_keyword}{self.sql(exp.Literal.string(f'{this}{unit}'))}" 4203 return f"{interval_keyword}{unit}" 4204 4205 this = self.sql(expression, "this") 4206 if this: 4207 if not include_keyword and expression.this.is_string: 4208 this = expression.this.name 4209 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4210 this = f"({this})" 4211 if include_keyword: 4212 this = f" {this}" 4213 4214 return f"{interval_keyword}{this}{unit}" 4215 4216 def return_sql(self, expression: exp.Return) -> str: 4217 return f"RETURN {self.sql(expression, 'this')}" 4218 4219 def reference_sql(self, expression: exp.Reference) -> str: 4220 this = self.sql(expression, "this") 4221 expressions = self.expressions(expression, flat=True) 4222 expressions = f"({expressions})" if expressions else "" 4223 options = self.expressions(expression, key="options", flat=True, sep=" ") 4224 options = f" {options}" if options else "" 4225 return f"REFERENCES {this}{expressions}{options}" 4226 4227 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4228 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4229 parent = expression.parent 4230 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4231 4232 return self.func( 4233 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4234 ) 4235 4236 def paren_sql(self, expression: exp.Paren) -> str: 4237 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4238 return f"({sql}{self.seg(')', sep='')}" 4239 4240 def neg_sql(self, expression: exp.Neg) -> str: 4241 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4242 this_sql = self.sql(expression, "this") 4243 sep = " " if this_sql[0] == "-" else "" 4244 return f"-{sep}{this_sql}" 4245 4246 def not_sql(self, expression: exp.Not) -> str: 4247 return f"NOT {self.sql(expression, 'this')}" 4248 4249 def alias_sql(self, expression: exp.Alias) -> str: 4250 alias = self.sql(expression, "alias") 4251 alias = f" AS {alias}" if alias else "" 4252 return f"{self.sql(expression, 'this')}{alias}" 4253 4254 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4255 alias = expression.args["alias"] 4256 4257 parent = expression.parent 4258 pivot = parent and parent.parent 4259 4260 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4261 identifier_alias = isinstance(alias, exp.Identifier) 4262 literal_alias = isinstance(alias, exp.Literal) 4263 4264 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4265 alias.replace(exp.Literal.string(alias.output_name)) 4266 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4267 alias.replace(exp.to_identifier(alias.output_name)) 4268 4269 return self.alias_sql(expression) 4270 4271 def aliases_sql(self, expression: exp.Aliases) -> str: 4272 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4273 4274 def atindex_sql(self, expression: exp.AtIndex) -> str: 4275 this = self.sql(expression, "this") 4276 index = self.sql(expression, "expression") 4277 return f"{this} AT {index}" 4278 4279 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4280 this = self.sql(expression, "this") 4281 zone = self.sql(expression, "zone") 4282 return f"{this} AT TIME ZONE {zone}" 4283 4284 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4285 this = self.sql(expression, "this") 4286 zone = self.sql(expression, "zone") 4287 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4288 4289 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4290 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4291 4292 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4293 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4294 4295 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4296 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4297 4298 def add_sql(self, expression: exp.Add) -> str: 4299 return self.binary(expression, "+") 4300 4301 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4302 return self.connector_sql(expression, "AND", stack) 4303 4304 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4305 return self.connector_sql(expression, "OR", stack) 4306 4307 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4308 return self.connector_sql(expression, "XOR", stack) 4309 4310 def connector_sql( 4311 self, 4312 expression: exp.Connector, 4313 op: str, 4314 stack: list[str | exp.Expr] | None = None, 4315 ) -> str: 4316 if stack is not None: 4317 stack.append(expression.right) 4318 if expression.comments and self.comments: 4319 op = self.maybe_comment(op, comments=expression.comments) 4320 4321 stack.extend((op, expression.left)) 4322 return op 4323 4324 stack = [expression] 4325 sqls: list[str] = [] 4326 ops = set() 4327 4328 while stack: 4329 node = stack.pop() 4330 if isinstance(node, exp.Connector): 4331 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4332 else: 4333 sql = self.sql(node) 4334 if sqls and sqls[-1] in ops: 4335 sqls[-1] += f" {sql}" 4336 else: 4337 sqls.append(sql) 4338 4339 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4340 return sep.join(sqls) 4341 4342 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4343 return self.binary(expression, "&") 4344 4345 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4346 return self.binary(expression, "<<") 4347 4348 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4349 return f"~{self.sql(expression, 'this')}" 4350 4351 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4352 return self.binary(expression, "|") 4353 4354 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4355 return self.binary(expression, ">>") 4356 4357 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4358 return self.binary(expression, "^") 4359 4360 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4361 format_sql = self.sql(expression, "format") 4362 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4363 to_sql = self.sql(expression, "to") 4364 to_sql = f" {to_sql}" if to_sql else "" 4365 action = self.sql(expression, "action") 4366 action = f" {action}" if action else "" 4367 default = self.sql(expression, "default") 4368 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4369 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4370 4371 # Base implementation that excludes safe, zone, and target_type metadata args 4372 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4373 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4374 4375 # Base implementation that excludes the safe and default_year metadata args 4376 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4377 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4378 4379 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4380 return self.func( 4381 "PARSE_DATETIME", 4382 expression.this, 4383 expression.args.get("format"), 4384 expression.args.get("zone"), 4385 ) 4386 4387 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4388 zone = self.sql(expression, "this") 4389 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4390 4391 def collate_sql(self, expression: exp.Collate) -> str: 4392 if self.COLLATE_IS_FUNC: 4393 return self.function_fallback_sql(expression) 4394 return self.binary(expression, "COLLATE") 4395 4396 def command_sql(self, expression: exp.Command) -> str: 4397 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4398 4399 def comment_sql(self, expression: exp.Comment) -> str: 4400 this = self.sql(expression, "this") 4401 kind = expression.args["kind"] 4402 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4403 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4404 expression_sql = self.sql(expression, "expression") 4405 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4406 4407 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4408 this = self.sql(expression, "this") 4409 delete = " DELETE" if expression.args.get("delete") else "" 4410 recompress = self.sql(expression, "recompress") 4411 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4412 to_disk = self.sql(expression, "to_disk") 4413 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4414 to_volume = self.sql(expression, "to_volume") 4415 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4416 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4417 4418 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4419 where = self.sql(expression, "where") 4420 group = self.sql(expression, "group") 4421 aggregates = self.expressions(expression, key="aggregates") 4422 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4423 4424 if not (where or group or aggregates) and len(expression.expressions) == 1: 4425 return f"TTL {self.expressions(expression, flat=True)}" 4426 4427 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4428 4429 def transaction_sql(self, expression: exp.Transaction) -> str: 4430 modes = self.expressions(expression, key="modes") 4431 modes = f" {modes}" if modes else "" 4432 return f"BEGIN{modes}" 4433 4434 def commit_sql(self, expression: exp.Commit) -> str: 4435 chain = expression.args.get("chain") 4436 if chain is not None: 4437 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4438 4439 return f"COMMIT{chain or ''}" 4440 4441 def rollback_sql(self, expression: exp.Rollback) -> str: 4442 savepoint = expression.args.get("savepoint") 4443 savepoint = f" TO {savepoint}" if savepoint else "" 4444 return f"ROLLBACK{savepoint}" 4445 4446 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4447 this = self.sql(expression, "this") 4448 4449 exists = "" 4450 if expression.args.get("exists"): 4451 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4452 exists = " IF EXISTS" 4453 else: 4454 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4455 4456 dtype = self.sql(expression, "dtype") 4457 if dtype: 4458 collate = self.sql(expression, "collate") 4459 collate = f" COLLATE {collate}" if collate else "" 4460 using = self.sql(expression, "using") 4461 using = f" USING {using}" if using else "" 4462 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4463 null_constraint = self._alter_column_null_constraint_sql(expression) 4464 4465 return ( 4466 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4467 f"{collate}{using}{null_constraint}" 4468 ) 4469 4470 default = self.sql(expression, "default") 4471 if default: 4472 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4473 4474 comment = self.sql(expression, "comment") 4475 if comment: 4476 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4477 4478 visible = expression.args.get("visible") 4479 if visible: 4480 return f"ALTER COLUMN{exists} {this} SET {visible}" 4481 4482 allow_null = expression.args.get("allow_null") 4483 drop = expression.args.get("drop") 4484 4485 if not drop and not allow_null: 4486 self.unsupported("Unsupported ALTER COLUMN syntax") 4487 4488 if allow_null is not None: 4489 keyword = "DROP" if drop else "SET" 4490 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4491 4492 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4493 4494 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4495 allow_null = expression.args.get("allow_null") 4496 if allow_null is None: 4497 return "" 4498 4499 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4500 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4501 return "" 4502 4503 return " NULL" if allow_null else " NOT NULL" 4504 4505 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4506 this = self.sql(expression, "this") 4507 rename_from = self.sql(expression, "rename_from") 4508 if rename_from: 4509 if not self.SUPPORTS_CHANGE_COLUMN: 4510 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4511 return f"CHANGE COLUMN {rename_from} {this}" 4512 if not self.SUPPORTS_MODIFY_COLUMN: 4513 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4514 return f"MODIFY COLUMN {this}" 4515 4516 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4517 this = self.sql(expression, "this") 4518 4519 visible = expression.args.get("visible") 4520 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4521 4522 return f"ALTER INDEX {this} {visible_sql}" 4523 4524 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4525 this = self.sql(expression, "this") 4526 if not isinstance(expression.this, exp.Var): 4527 this = f"KEY DISTKEY {this}" 4528 return f"ALTER DISTSTYLE {this}" 4529 4530 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4531 compound = " COMPOUND" if expression.args.get("compound") else "" 4532 this = self.sql(expression, "this") 4533 expressions = self.expressions(expression, flat=True) 4534 expressions = f"({expressions})" if expressions else "" 4535 return f"ALTER{compound} SORTKEY {this or expressions}" 4536 4537 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4538 if not self.RENAME_TABLE_WITH_DB: 4539 # Remove db from tables 4540 expression = expression.transform( 4541 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4542 ).assert_is(exp.AlterRename) 4543 this = self.sql(expression, "this") 4544 to_kw = " TO" if include_to else "" 4545 return f"RENAME{to_kw} {this}" 4546 4547 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4548 exists = " IF EXISTS" if expression.args.get("exists") else "" 4549 old_column = self.sql(expression, "this") 4550 new_column = self.sql(expression, "to") 4551 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4552 4553 def alterset_sql(self, expression: exp.AlterSet) -> str: 4554 exprs = self.expressions(expression, flat=True) 4555 if self.ALTER_SET_WRAPPED: 4556 exprs = f"({exprs})" 4557 4558 return f"SET {exprs}" 4559 4560 def alter_sql(self, expression: exp.Alter) -> str: 4561 actions = expression.actions 4562 4563 if ( 4564 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4565 and actions 4566 and isinstance(actions[0], exp.ColumnDef) 4567 ): 4568 actions_sql = self.expressions(expression, key="actions", flat=True) 4569 actions_sql = f"ADD {actions_sql}" 4570 else: 4571 actions_list = [] 4572 for action in actions: 4573 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4574 action_sql = self.add_column_sql(action) 4575 else: 4576 action_sql = self.sql(action) 4577 if isinstance(action, exp.Query): 4578 action_sql = f"AS {action_sql}" 4579 4580 actions_list.append(action_sql) 4581 4582 actions_sql = self.format_args(*actions_list).lstrip("\n") 4583 4584 iceberg = ( 4585 "ICEBERG " 4586 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4587 else "" 4588 ) 4589 exists = " IF EXISTS" if expression.args.get("exists") else "" 4590 on_cluster = self.sql(expression, "cluster") 4591 on_cluster = f" {on_cluster}" if on_cluster else "" 4592 only = " ONLY" if expression.args.get("only") else "" 4593 options = expression.args.get("options") or [] 4594 options_sql = self.expressions( 4595 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4596 ) 4597 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4598 partitions_sql = self.expressions( 4599 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4600 sep=" ", 4601 flat=True, 4602 ) 4603 if partitions_sql and (actions_sql or options_sql): 4604 partitions_sql = f" {partitions_sql}" 4605 kind = self.sql(expression, "kind") 4606 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4607 check = " WITH CHECK" if expression.args.get("check") else "" 4608 cascade = ( 4609 " CASCADE" 4610 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4611 else "" 4612 ) 4613 this = self.sql(expression, "this") 4614 this = f" {this}" if this else "" 4615 4616 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}" 4617 4618 def altersession_sql(self, expression: exp.AlterSession) -> str: 4619 items_sql = self.expressions(expression, flat=True) 4620 keyword = "UNSET" if expression.args.get("unset") else "SET" 4621 return f"{keyword} {items_sql}" 4622 4623 def add_column_sql(self, expression: exp.Expr) -> str: 4624 column_text = "" 4625 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4626 if isinstance(expression, exp.ColumnDef): 4627 column_text = " COLUMN" 4628 elif isinstance(expression, exp.Schema): 4629 column_text = " COLUMNS" 4630 4631 if isinstance(expression, exp.Schema) and not self.ALTER_TABLE_ADD_COLUMNS_WRAPPED: 4632 sql = self.expressions(expression, flat=True) 4633 else: 4634 sql = self.sql(expression) 4635 4636 return f"ADD{column_text} {sql}" 4637 4638 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4639 expressions = self.expressions(expression) 4640 exists = " IF EXISTS " if expression.args.get("exists") else " " 4641 return f"DROP{exists}{expressions}" 4642 4643 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4644 return "DROP PRIMARY KEY" 4645 4646 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4647 return f"ADD {self.expressions(expression, indent=False)}" 4648 4649 def replacecolumns_sql(self, expression: exp.ReplaceColumns) -> str: 4650 return f"REPLACE COLUMNS ({self.expressions(expression, flat=True)})" 4651 4652 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4653 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4654 location = self.sql(expression, "location") 4655 location = f" {location}" if location else "" 4656 return f"ADD {exists}{self.sql(expression.this)}{location}" 4657 4658 def distinct_sql(self, expression: exp.Distinct) -> str: 4659 this = self.expressions(expression, flat=True) 4660 4661 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4662 case = exp.case() 4663 for arg in expression.expressions: 4664 case = case.when(arg.is_(exp.null()), exp.null()) 4665 this = self.sql(case.else_(f"({this})")) 4666 4667 this = f" {this}" if this else "" 4668 4669 on = self.sql(expression, "on") 4670 on = f" ON {on}" if on else "" 4671 return f"DISTINCT{this}{on}" 4672 4673 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4674 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4675 4676 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4677 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4678 4679 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4680 this_sql = self.sql(expression, "this") 4681 expression_sql = self.sql(expression, "expression") 4682 kind = "MAX" if expression.args.get("max") else "MIN" 4683 return f"{this_sql} HAVING {kind} {expression_sql}" 4684 4685 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4686 return self._truncated_div_sql(expression.this, expression.expression) 4687 4688 def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str: 4689 div: exp.Expr = exp.Div(this=this, expression=divisor) 4690 if self.CAST_TO_INT_ROUNDS: 4691 div = exp.Trunc(this=div) 4692 4693 return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT))) 4694 4695 def dpipe_sql(self, expression: exp.DPipe) -> str: 4696 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4697 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4698 return self.binary(expression, "||") 4699 4700 def nullif_zero_divisor(self, expression: exp.Div | exp.Mod) -> None: 4701 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4702 divisor = expression.right 4703 divisor.replace(exp.Nullif(this=divisor.copy(), expression=exp.Literal.number(0))) 4704 4705 def div_sql(self, expression: exp.Div) -> str: 4706 l, r = expression.left, expression.right 4707 self.nullif_zero_divisor(expression) 4708 4709 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4710 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4711 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4712 4713 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4714 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4715 # Native integer division depends on the target's operand types, which may differ 4716 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4717 # integer literals or casts 4718 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4719 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4720 ): 4721 return self.sql(exp.IntDiv(this=l, expression=expression.right)) 4722 return self._truncated_div_sql(l, expression.right) 4723 4724 return self.binary(expression, "/") 4725 4726 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4727 n = exp._wrap(expression.this, exp.Binary) 4728 d = exp._wrap(expression.expression, exp.Binary) 4729 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4730 4731 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4732 return self.binary(expression, "OVERLAPS") 4733 4734 def distance_sql(self, expression: exp.Distance) -> str: 4735 return self.binary(expression, "<->") 4736 4737 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4738 return self.binary(expression, "<<->>") 4739 4740 def dot_sql(self, expression: exp.Dot) -> str: 4741 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4742 4743 def eq_sql(self, expression: exp.EQ) -> str: 4744 return self.binary(expression, "=") 4745 4746 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4747 return self.binary(expression, ":=") 4748 4749 def escape_sql(self, expression: exp.Escape) -> str: 4750 this = expression.this 4751 if ( 4752 isinstance(this, (exp.Like, exp.ILike)) 4753 and isinstance(this.expression, (exp.All, exp.Any)) 4754 and not self.SUPPORTS_LIKE_QUANTIFIERS 4755 ): 4756 return self._like_sql(this, escape=expression) 4757 return self.binary(expression, "ESCAPE") 4758 4759 def glob_sql(self, expression: exp.Glob) -> str: 4760 return self.binary(expression, "GLOB") 4761 4762 def gt_sql(self, expression: exp.GT) -> str: 4763 return self.binary(expression, ">") 4764 4765 def gte_sql(self, expression: exp.GTE) -> str: 4766 return self.binary(expression, ">=") 4767 4768 def is_sql(self, expression: exp.Is) -> str: 4769 negate = expression.args.get("negate") 4770 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4771 positive = bool(expression.expression.this) != bool(negate) 4772 return self.sql(expression.this if positive else exp.not_(expression.this)) 4773 return self.binary(expression, "IS NOT" if negate else "IS") 4774 4775 def _like_sql( 4776 self, 4777 expression: exp.Like | exp.ILike, 4778 escape: exp.Escape | None = None, 4779 ) -> str: 4780 this = expression.this 4781 rhs = expression.expression 4782 4783 if isinstance(expression, exp.Like): 4784 exp_class: type[exp.Like | exp.ILike] = exp.Like 4785 op = "LIKE" 4786 else: 4787 exp_class = exp.ILike 4788 op = "ILIKE" 4789 4790 if expression.args.get("negate"): 4791 op = f"NOT {op}" 4792 4793 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4794 exprs = rhs.this.unnest() 4795 4796 if isinstance(exprs, exp.Tuple): 4797 exprs = exprs.expressions 4798 else: 4799 exprs = [exprs] 4800 4801 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4802 4803 def _make_like(expr: exp.Expression) -> exp.Expression: 4804 like: exp.Expression = exp_class( 4805 this=this, expression=expr, negate=expression.args.get("negate") 4806 ) 4807 if escape: 4808 like = exp.Escape(this=like, expression=escape.expression.copy()) 4809 return like 4810 4811 like_expr: exp.Expr = _make_like(exprs[0]) 4812 for expr in exprs[1:]: 4813 like_expr = connective(like_expr, _make_like(expr), copy=False) 4814 4815 parent = escape.parent if escape else expression.parent 4816 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4817 parent, exp.Condition 4818 ): 4819 like_expr = exp.paren(like_expr, copy=False) 4820 4821 return self.sql(like_expr) 4822 4823 return self.binary(expression, op) 4824 4825 def like_sql(self, expression: exp.Like) -> str: 4826 return self._like_sql(expression) 4827 4828 def ilike_sql(self, expression: exp.ILike) -> str: 4829 return self._like_sql(expression) 4830 4831 def match_sql(self, expression: exp.Match) -> str: 4832 if expression.args.get("kind") or expression.args.get("analyzer"): 4833 self.unsupported("MATCH variants and USING ANALYZER are not supported in this dialect") 4834 return self.binary(expression, "MATCH") 4835 4836 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4837 return self.binary(expression, "SIMILAR TO") 4838 4839 def lt_sql(self, expression: exp.LT) -> str: 4840 return self.binary(expression, "<") 4841 4842 def lte_sql(self, expression: exp.LTE) -> str: 4843 return self.binary(expression, "<=") 4844 4845 def mod_sql(self, expression: exp.Mod) -> str: 4846 self.nullif_zero_divisor(expression) 4847 this = self.sql(expression, "this") 4848 expr = self.sql(expression, "expression") 4849 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4850 4851 parent = expression.parent 4852 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4853 return f"({sql})" 4854 4855 return sql 4856 4857 def mul_sql(self, expression: exp.Mul) -> str: 4858 return self.binary(expression, "*") 4859 4860 def neq_sql(self, expression: exp.NEQ) -> str: 4861 return self.binary(expression, "<>") 4862 4863 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4864 return self.binary(expression, "IS NOT DISTINCT FROM") 4865 4866 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4867 return self.binary(expression, "IS DISTINCT FROM") 4868 4869 def sub_sql(self, expression: exp.Sub) -> str: 4870 return self.binary(expression, "-") 4871 4872 def trycast_sql(self, expression: exp.TryCast) -> str: 4873 return self.cast_sql(expression, safe_prefix="TRY_") 4874 4875 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4876 return self.cast_sql(expression) 4877 4878 def try_sql(self, expression: exp.Try) -> str: 4879 if not self.TRY_SUPPORTED: 4880 self.unsupported("Unsupported TRY function") 4881 return self.sql(expression, "this") 4882 4883 return self.func("TRY", expression.this) 4884 4885 def log_sql(self, expression: exp.Log) -> str: 4886 this = expression.this 4887 expr = expression.expression 4888 4889 if self.dialect.LOG_BASE_FIRST is False: 4890 this, expr = expr, this 4891 elif self.dialect.LOG_BASE_FIRST is None and expr: 4892 if this.name in ("2", "10"): 4893 return self.func(f"LOG{this.name}", expr) 4894 4895 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4896 4897 return self.func("LOG", this, expr) 4898 4899 def use_sql(self, expression: exp.Use) -> str: 4900 kind = self.sql(expression, "kind") 4901 kind = f" {kind}" if kind else "" 4902 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4903 this = f" {this}" if this else "" 4904 return f"USE{kind}{this}" 4905 4906 def binary(self, expression: exp.Binary, op: str) -> str: 4907 sqls: list[str] = [] 4908 stack: list[None | str | exp.Expr] = [expression] 4909 binary_type = type(expression) 4910 is_is = binary_type is exp.Is and op in ("IS", "IS NOT") 4911 negate = expression.args.get("negate") 4912 4913 while stack: 4914 node = stack.pop() 4915 4916 if type(node) is binary_type and ( 4917 node is expression 4918 or (is_is and not isinstance(node.expression, exp.Boolean)) 4919 or node.args.get("negate") == negate 4920 ): 4921 op_func = node.args.get("operator") 4922 if op_func: 4923 op = f"OPERATOR({self.sql(op_func)})" 4924 elif is_is: 4925 op = "IS NOT" if node.args.get("negate") else "IS" 4926 4927 stack.append(node.args.get("expression")) 4928 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4929 stack.append(node.args.get("this")) 4930 else: 4931 sqls.append(self.sql(node)) 4932 4933 return "".join(sqls) 4934 4935 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4936 to_clause = self.sql(expression, "to") 4937 if to_clause: 4938 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4939 4940 return self.function_fallback_sql(expression) 4941 4942 def function_fallback_sql(self, expression: exp.Func) -> str: 4943 args = [] 4944 4945 for key in expression.arg_types: 4946 arg_value = expression.args.get(key) 4947 4948 if isinstance(arg_value, list): 4949 for value in arg_value: 4950 args.append(value) 4951 elif arg_value is not None: 4952 args.append(arg_value) 4953 4954 return self.func(self.function_name(expression), *args) 4955 4956 def function_name(self, expression: exp.Func) -> str: 4957 name_key = self.dialect.ORIGINAL_NAME_META_KEY 4958 return (expression.meta_get(name_key) if name_key else None) or expression.sql_name() 4959 4960 def func( 4961 self, 4962 name: str, 4963 *args: t.Any, 4964 prefix: str = "(", 4965 suffix: str = ")", 4966 normalize: bool = True, 4967 ) -> str: 4968 name = self.normalize_func(name) if normalize else name 4969 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4970 4971 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4972 arg_sqls = tuple( 4973 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4974 ) 4975 if self.pretty and self.too_wide(arg_sqls): 4976 return self.indent( 4977 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4978 ) 4979 return sep.join(arg_sqls) 4980 4981 def too_wide(self, args: t.Iterable) -> bool: 4982 return sum(len(arg) for arg in args) > self.max_text_width 4983 4984 def format_time( 4985 self, 4986 expression: exp.Expr, 4987 inverse_time_mapping: dict[str, str] | None = None, 4988 inverse_time_trie: dict | None = None, 4989 ) -> str | None: 4990 return format_time( 4991 self.sql(expression, "format"), 4992 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4993 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4994 ) 4995 4996 def expressions( 4997 self, 4998 expression: exp.Expr | None = None, 4999 key: str | None = None, 5000 sqls: t.Collection[str | exp.Expr] | None = None, 5001 flat: bool = False, 5002 indent: bool = True, 5003 skip_first: bool = False, 5004 skip_last: bool = False, 5005 sep: str = ", ", 5006 prefix: str = "", 5007 dynamic: bool = False, 5008 new_line: bool = False, 5009 ) -> str: 5010 expressions = expression.args.get(key or "expressions") if expression else sqls 5011 5012 if not expressions: 5013 return "" 5014 5015 if flat: 5016 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 5017 5018 num_sqls = len(expressions) 5019 result_sqls = [] 5020 5021 for i, e in enumerate(expressions): 5022 sql = self.sql(e, comment=False) 5023 if not sql: 5024 continue 5025 5026 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 5027 5028 if self.pretty: 5029 if self.leading_comma: 5030 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 5031 else: 5032 result_sqls.append( 5033 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 5034 ) 5035 else: 5036 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 5037 5038 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 5039 if new_line: 5040 result_sqls.insert(0, "") 5041 result_sqls.append("") 5042 result_sql = "\n".join(s.rstrip() for s in result_sqls) 5043 else: 5044 result_sql = "".join(result_sqls) 5045 5046 return ( 5047 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 5048 if indent 5049 else result_sql 5050 ) 5051 5052 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 5053 flat = flat or isinstance(expression.parent, exp.Properties) 5054 expressions_sql = self.expressions(expression, flat=flat) 5055 if flat: 5056 return f"{op} {expressions_sql}" 5057 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 5058 5059 def naked_property(self, expression: exp.Property) -> str: 5060 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 5061 if not property_name: 5062 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 5063 return f"{property_name} {self.sql(expression, 'this')}" 5064 5065 def tag_sql(self, expression: exp.Tag) -> str: 5066 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 5067 5068 def token_sql(self, token_type: TokenType) -> str: 5069 return self.TOKEN_MAPPING.get(token_type, token_type.name) 5070 5071 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 5072 this = self.sql(expression, "this") 5073 expressions = self.no_identify(self.expressions, expression) 5074 expressions = ( 5075 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 5076 ) 5077 return f"{this}{expressions}" if expressions.strip() != "" else this 5078 5079 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 5080 return self.expressions(expression, flat=True) 5081 5082 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 5083 params = self.no_identify(self.expressions, expression, flat=True) 5084 body = self.sql(expression, "this") 5085 prefix = "TABLE " if expression.args.get("is_table") else "" 5086 return f"({params}) AS {prefix}{body}" 5087 5088 def joinhint_sql(self, expression: exp.JoinHint) -> str: 5089 this = self.sql(expression, "this") 5090 expressions = self.expressions(expression, flat=True) 5091 return f"{this}({expressions})" 5092 5093 def kwarg_sql(self, expression: exp.Kwarg) -> str: 5094 return self.binary(expression, "=>") 5095 5096 def when_sql(self, expression: exp.When) -> str: 5097 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 5098 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 5099 condition = self.sql(expression, "condition") 5100 condition = f" AND {condition}" if condition else "" 5101 5102 then_expression = expression.args.get("then") 5103 if isinstance(then_expression, exp.Insert): 5104 this = ( 5105 "ALL BY NAME" 5106 if self.MERGE_ALL_BY_NAME and isinstance(then_expression.this, exp.Star) 5107 else self.sql(then_expression, "this") 5108 ) 5109 this = f"INSERT {this}" if this else "INSERT" 5110 then = self.sql(then_expression, "expression") 5111 then = f"{this} VALUES {then}" if then else this 5112 elif isinstance(then_expression, exp.Update): 5113 if isinstance(then_expression.args.get("expressions"), exp.Star): 5114 then = ( 5115 "UPDATE ALL BY NAME" 5116 if self.MERGE_ALL_BY_NAME 5117 else f"UPDATE {self.sql(then_expression, 'expressions')}" 5118 ) 5119 elif ( 5120 self.MERGE_ALL_BY_NAME 5121 and len(then_expression.expressions) == 1 5122 and isinstance(then_expression.expressions[0], exp.Star) 5123 ): 5124 then = "UPDATE ALL BY NAME" 5125 else: 5126 expressions_sql = self.expressions(then_expression) 5127 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 5128 else: 5129 then = self.sql(then_expression) 5130 5131 if isinstance(then_expression, (exp.Insert, exp.Update)): 5132 where = self.sql(then_expression, "where") 5133 if where and not self.SUPPORTS_MERGE_WHERE: 5134 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 5135 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 5136 where = "" 5137 then = f"{then}{where}" 5138 return f"WHEN {matched}{source}{condition} THEN {then}" 5139 5140 def whens_sql(self, expression: exp.Whens) -> str: 5141 return self.expressions(expression, sep=" ", indent=False) 5142 5143 def merge_sql(self, expression: exp.Merge) -> str: 5144 table = expression.this 5145 table_alias = "" 5146 5147 hints = table.args.get("hints") 5148 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 5149 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 5150 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 5151 5152 this = self.sql(table) 5153 using = f"USING {self.sql(expression, 'using')}" 5154 whens = self.sql(expression, "whens") 5155 5156 on = self.sql(expression, "on") 5157 on = f"ON {on}" if on else "" 5158 5159 if not on: 5160 on = self.expressions(expression, key="using_cond") 5161 on = f"USING ({on})" if on else "" 5162 5163 returning = self.sql(expression, "returning") 5164 if returning: 5165 whens = f"{whens}{returning}" 5166 5167 sep = self.sep() 5168 5169 return self.prepend_ctes( 5170 expression, 5171 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5172 ) 5173 5174 @unsupported_args("format") 5175 def tochar_sql(self, expression: exp.ToChar) -> str: 5176 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 5177 5178 @unsupported_args("default") 5179 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5180 if not self.SUPPORTS_TO_NUMBER: 5181 self.unsupported("Unsupported TO_NUMBER function") 5182 return self._tonumber_cast_sql(expression) 5183 5184 fmt = expression.args.get("format") 5185 if not fmt: 5186 self.unsupported("Conversion format is required for TO_NUMBER") 5187 return self._tonumber_cast_sql(expression) 5188 5189 return self.func("TO_NUMBER", expression.this, fmt) 5190 5191 def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str: 5192 if expression.args.get("safe"): 5193 return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE"))) 5194 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 5195 5196 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5197 this = self.sql(expression, "this") 5198 kind = self.sql(expression, "kind") 5199 settings_sql = self.expressions(expression, key="settings", sep=" ") 5200 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5201 return f"{this}({kind}{args})" 5202 5203 def dictrange_sql(self, expression: exp.DictRange) -> str: 5204 this = self.sql(expression, "this") 5205 max = self.sql(expression, "max") 5206 min = self.sql(expression, "min") 5207 return f"{this}(MIN {min} MAX {max})" 5208 5209 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 5210 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 5211 5212 def aggregatekeyproperty_sql(self, expression: exp.AggregateKeyProperty) -> str: 5213 return f"AGGREGATE KEY ({self.expressions(expression, flat=True)})" 5214 5215 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 5216 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 5217 5218 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 5219 def uniquekeyproperty_sql( 5220 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 5221 ) -> str: 5222 return f"{prefix} ({self.expressions(expression, flat=True)})" 5223 5224 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 5225 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5226 expressions = self.expressions(expression, flat=True) 5227 expressions = f" {self.wrap(expressions)}" if expressions else "" 5228 buckets = self.sql(expression, "buckets") 5229 kind = self.sql(expression, "kind") 5230 buckets = f" BUCKETS {buckets}" if buckets else "" 5231 order = self.sql(expression, "order") 5232 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 5233 5234 def oncluster_sql(self, expression: exp.OnCluster) -> str: 5235 return "" 5236 5237 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5238 expressions = self.expressions(expression, key="expressions", flat=True) 5239 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5240 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5241 buckets = self.sql(expression, "buckets") 5242 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 5243 5244 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5245 this = self.sql(expression, "this") 5246 having = self.sql(expression, "having") 5247 5248 if having: 5249 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5250 5251 return self.func("ANY_VALUE", this) 5252 5253 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5254 transform = self.func("TRANSFORM", *expression.expressions) 5255 row_format_before = self.sql(expression, "row_format_before") 5256 row_format_before = f" {row_format_before}" if row_format_before else "" 5257 record_writer = self.sql(expression, "record_writer") 5258 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5259 using = f" USING {self.sql(expression, 'command_script')}" 5260 schema = self.sql(expression, "schema") 5261 schema = f" AS {schema}" if schema else "" 5262 row_format_after = self.sql(expression, "row_format_after") 5263 row_format_after = f" {row_format_after}" if row_format_after else "" 5264 record_reader = self.sql(expression, "record_reader") 5265 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5266 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5267 5268 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5269 key_block_size = self.sql(expression, "key_block_size") 5270 if key_block_size: 5271 return f"KEY_BLOCK_SIZE = {key_block_size}" 5272 5273 using = self.sql(expression, "using") 5274 if using: 5275 return f"USING {using}" 5276 5277 parser = self.sql(expression, "parser") 5278 if parser: 5279 return f"WITH PARSER {parser}" 5280 5281 comment = self.sql(expression, "comment") 5282 if comment: 5283 return f"COMMENT {comment}" 5284 5285 visible = expression.args.get("visible") 5286 if visible is not None: 5287 return "VISIBLE" if visible else "INVISIBLE" 5288 5289 engine_attr = self.sql(expression, "engine_attr") 5290 if engine_attr: 5291 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5292 5293 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5294 if secondary_engine_attr: 5295 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5296 5297 self.unsupported("Unsupported index constraint option.") 5298 return "" 5299 5300 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5301 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5302 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5303 5304 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5305 kind = self.sql(expression, "kind") 5306 kind = f"{kind} INDEX" if kind else "INDEX" 5307 this = self.sql(expression, "this") 5308 this = f" {this}" if this else "" 5309 index_type = self.sql(expression, "index_type") 5310 index_type = f" USING {index_type}" if index_type else "" 5311 expressions = self.expressions(expression, flat=True) 5312 expressions = f" ({expressions})" if expressions else "" 5313 options = self.expressions(expression, key="options", sep=" ") 5314 options = f" {options}" if options else "" 5315 return f"{kind}{this}{index_type}{expressions}{options}" 5316 5317 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5318 if self.NVL2_SUPPORTED: 5319 return self.function_fallback_sql(expression) 5320 5321 case = exp.Case().when( 5322 expression.this.is_(exp.null()).not_(copy=False), 5323 expression.args["true"], 5324 copy=False, 5325 ) 5326 else_cond = expression.args.get("false") 5327 if else_cond: 5328 case.else_(else_cond, copy=False) 5329 5330 return self.sql(case) 5331 5332 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5333 if expression.args.get("from_first") is False: 5334 self.unsupported("NTH_VALUE FROM LAST is not supported") 5335 5336 return self.function_fallback_sql(expression) 5337 5338 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5339 this = self.sql(expression, "this") 5340 expr = self.sql(expression, "expression") 5341 position = self.sql(expression, "position") 5342 position = f", {position}" if position else "" 5343 iterator = self.sql(expression, "iterator") 5344 condition = self.sql(expression, "condition") 5345 condition = f" IF {condition}" if condition else "" 5346 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5347 5348 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5349 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5350 5351 def opclass_sql(self, expression: exp.Opclass) -> str: 5352 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5353 5354 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5355 model = self.sql(expression, "this") 5356 model = f"MODEL {model}" 5357 expr = expression.expression 5358 if expr: 5359 expr_sql = self.sql(expression, "expression") 5360 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5361 else: 5362 expr_sql = None 5363 5364 parameters = self.sql(expression, "params_struct") or None 5365 5366 return self.func(name, model, expr_sql, parameters) 5367 5368 def predict_sql(self, expression: exp.Predict) -> str: 5369 return self._ml_sql(expression, "PREDICT") 5370 5371 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5372 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5373 return self._ml_sql(expression, name) 5374 5375 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5376 return self._ml_sql(expression, "GENERATE_TEXT") 5377 5378 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5379 return self._ml_sql(expression, "GENERATE_TABLE") 5380 5381 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5382 return self._ml_sql(expression, "GENERATE_BOOL") 5383 5384 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5385 return self._ml_sql(expression, "GENERATE_INT") 5386 5387 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5388 return self._ml_sql(expression, "GENERATE_DOUBLE") 5389 5390 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5391 return self._ml_sql(expression, "TRANSLATE") 5392 5393 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5394 return self._ml_sql(expression, "FORECAST") 5395 5396 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5397 this_sql = self.sql(expression, "this") 5398 if isinstance(expression.this, exp.Table): 5399 this_sql = f"TABLE {this_sql}" 5400 5401 return self.func( 5402 "FORECAST", 5403 this_sql, 5404 expression.args.get("data_col"), 5405 expression.args.get("timestamp_col"), 5406 expression.args.get("model"), 5407 expression.args.get("id_cols"), 5408 expression.args.get("horizon"), 5409 expression.args.get("forecast_end_timestamp"), 5410 expression.args.get("confidence_level"), 5411 expression.args.get("output_historical_time_series"), 5412 expression.args.get("context_window"), 5413 ) 5414 5415 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5416 this_sql = self.sql(expression, "this") 5417 if isinstance(expression.this, exp.Table): 5418 this_sql = f"TABLE {this_sql}" 5419 5420 return self.func( 5421 "FEATURES_AT_TIME", 5422 this_sql, 5423 expression.args.get("time"), 5424 expression.args.get("num_rows"), 5425 expression.args.get("ignore_feature_nulls"), 5426 ) 5427 5428 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5429 this_sql = self.sql(expression, "this") 5430 if isinstance(expression.this, exp.Table): 5431 this_sql = f"TABLE {this_sql}" 5432 5433 query_table = self.sql(expression, "query_table") 5434 if isinstance(expression.args["query_table"], exp.Table): 5435 query_table = f"TABLE {query_table}" 5436 5437 return self.func( 5438 "VECTOR_SEARCH", 5439 this_sql, 5440 expression.args.get("column_to_search"), 5441 query_table, 5442 expression.args.get("query_column_to_search"), 5443 expression.args.get("top_k"), 5444 expression.args.get("distance_type"), 5445 expression.args.get("options"), 5446 ) 5447 5448 def forin_sql(self, expression: exp.ForIn) -> str: 5449 this = self.sql(expression, "this") 5450 expression_sql = self.sql(expression, "expression") 5451 return f"FOR {this} DO {expression_sql}" 5452 5453 @unsupported_args("partition_start", "partition_end", "force", "mode") 5454 def refresh_sql(self, expression: exp.Refresh) -> str: 5455 this = self.sql(expression, "this") 5456 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5457 return f"REFRESH {kind}{this}" 5458 5459 def toarray_sql(self, expression: exp.ToArray) -> str: 5460 arg = expression.this 5461 if not arg.type: 5462 import sqlglot.optimizer.annotate_types 5463 5464 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5465 5466 if arg.is_type(exp.DType.ARRAY): 5467 return self.sql(arg) 5468 5469 cond_for_null = arg.is_(exp.null()) 5470 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5471 5472 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5473 this = expression.this 5474 time_format = self.format_time(expression) 5475 5476 if time_format: 5477 return self.sql( 5478 exp.cast( 5479 exp.StrToTime(this=this, format=expression.args["format"]), 5480 exp.DType.TIME, 5481 ) 5482 ) 5483 5484 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5485 return self.sql(this) 5486 5487 return self.sql(exp.cast(this, exp.DType.TIME)) 5488 5489 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5490 this = expression.this 5491 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5492 return self.sql(this) 5493 5494 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5495 5496 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5497 this = expression.this 5498 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5499 return self.sql(this) 5500 5501 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5502 5503 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5504 this = expression.this 5505 time_format = self.format_time(expression) 5506 safe = expression.args.get("safe") 5507 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5508 return self.sql( 5509 exp.cast( 5510 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5511 exp.DType.DATE, 5512 ) 5513 ) 5514 5515 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5516 return self.sql(this) 5517 5518 if safe: 5519 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5520 5521 return self.sql(exp.cast(this, exp.DType.DATE)) 5522 5523 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5524 return self.sql( 5525 exp.func( 5526 "DATEDIFF", 5527 expression.this, 5528 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5529 "day", 5530 ) 5531 ) 5532 5533 def lastday_sql(self, expression: exp.LastDay) -> str: 5534 if self.LAST_DAY_SUPPORTS_DATE_PART: 5535 return self.function_fallback_sql(expression) 5536 5537 unit = expression.args.get("unit") 5538 if unit and unit.name.upper() != "MONTH": 5539 self.unsupported("Date parts are not supported in LAST_DAY.") 5540 5541 return self.func("LAST_DAY", expression.this) 5542 5543 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5544 import sqlglot.dialects.dialect 5545 5546 return self.func( 5547 "DATE_ADD", 5548 expression.this, 5549 expression.expression, 5550 sqlglot.dialects.dialect.unit_to_str(expression), 5551 ) 5552 5553 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5554 this = expression.this 5555 position = expression.args["position"] 5556 offset = index_offset - (expression.args.get("offset") or 0) 5557 5558 if offset: 5559 if position.is_int: 5560 value = position.to_py() 5561 if value >= 0: 5562 position = exp.Literal.number(value + offset) 5563 elif offset < 0 and value == -1: 5564 # 1-based -1 appends, which a 0-based position can only express as the size 5565 position = exp.ArraySize(this=this.copy()) 5566 else: 5567 # Negative positions count from the end, so they shift in the opposite 5568 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5569 position = exp.Literal.number(value - offset) 5570 else: 5571 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5572 5573 return self.func("ARRAY_INSERT", this, position, expression.expression) 5574 5575 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5576 if self.CAN_IMPLEMENT_ARRAY_ANY: 5577 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5578 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5579 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5580 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5581 5582 import sqlglot.dialects.dialect 5583 5584 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5585 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5586 self.unsupported("ARRAY_ANY is unsupported") 5587 5588 return self.function_fallback_sql(expression) 5589 5590 def struct_sql(self, expression: exp.Struct) -> str: 5591 expression.set( 5592 "expressions", 5593 [ 5594 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5595 if isinstance(e, exp.PropertyEQ) 5596 else e 5597 for e in expression.expressions 5598 ], 5599 ) 5600 5601 return self.function_fallback_sql(expression) 5602 5603 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5604 low = self.sql(expression, "this") 5605 high = self.sql(expression, "expression") 5606 5607 return f"{low} TO {high}" 5608 5609 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5610 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5611 tables = f" {self.expressions(expression)}" 5612 5613 exists = " IF EXISTS" if expression.args.get("exists") else "" 5614 5615 on_cluster = self.sql(expression, "cluster") 5616 on_cluster = f" {on_cluster}" if on_cluster else "" 5617 5618 identity = self.sql(expression, "identity") 5619 identity = f" {identity} IDENTITY" if identity else "" 5620 5621 option = self.sql(expression, "option") 5622 option = f" {option}" if option else "" 5623 5624 partition = self.sql(expression, "partition") 5625 partition = f" {partition}" if partition else "" 5626 5627 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5628 5629 # This transpiles T-SQL's CONVERT function 5630 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5631 def convert_sql(self, expression: exp.Convert) -> str: 5632 to = expression.this 5633 value = expression.expression 5634 style = expression.args.get("style") 5635 safe = expression.args.get("safe") 5636 strict = expression.args.get("strict") 5637 5638 if not to or not value: 5639 return "" 5640 5641 # Retrieve length of datatype and override to default if not specified 5642 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5643 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5644 5645 transformed: exp.Expr | None = None 5646 cast = exp.Cast if strict else exp.TryCast 5647 5648 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5649 if isinstance(style, exp.Literal) and style.is_int: 5650 import sqlglot.dialects.tsql 5651 5652 style_value = style.name 5653 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5654 if not converted_style: 5655 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5656 5657 fmt = exp.Literal.string(converted_style) 5658 5659 if to.this == exp.DType.DATE: 5660 transformed = exp.StrToDate(this=value, format=fmt) 5661 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5662 transformed = exp.StrToTime(this=value, format=fmt) 5663 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5664 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5665 elif to.this == exp.DType.TEXT: 5666 transformed = exp.TimeToStr(this=value, format=fmt) 5667 5668 if not transformed: 5669 transformed = cast(this=value, to=to, safe=safe) 5670 5671 return self.sql(transformed) 5672 5673 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5674 this = expression.this 5675 if isinstance(this, exp.JSONPathWildcard): 5676 this = self.json_path_part(this) 5677 return f".{this}" if this else "" 5678 5679 quoted = expression.args.get("quoted") 5680 if not ( 5681 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5682 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5683 return f".{this}" 5684 5685 this = self.json_path_part(this) 5686 5687 return ( 5688 f"[{this}]" 5689 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5690 else f".{this}" 5691 ) 5692 5693 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5694 this = self.json_path_part(expression.this) 5695 return f"[{this}]" if this else "" 5696 5697 def _simplify_unless_literal(self, expression: E) -> E: 5698 if not isinstance(expression, exp.Literal): 5699 import sqlglot.optimizer.simplify 5700 5701 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5702 5703 return expression 5704 5705 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5706 this = expression.this 5707 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5708 self.unsupported( 5709 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5710 ) 5711 return self.sql(this) 5712 5713 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5714 if self.IGNORE_NULLS_BEFORE_ORDER: 5715 from sqlglot.optimizer.scope import find_all_in_scope 5716 5717 # The first modifier here will be the one closest to the AggFunc's arg 5718 mods = sorted( 5719 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5720 key=lambda x: ( 5721 0 5722 if isinstance(x, exp.HavingMax) 5723 else (1 if isinstance(x, exp.Order) else 2) 5724 ), 5725 ) 5726 5727 if mods: 5728 mod = mods[0] 5729 this = expression.__class__(this=mod.this.copy()) 5730 this.meta["inline"] = True 5731 mod.this.replace(this) 5732 return self.sql(expression.this) 5733 5734 agg_func = expression.find(exp.AggFunc) 5735 5736 if agg_func: 5737 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5738 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5739 5740 return f"{self.sql(expression, 'this')} {text}" 5741 5742 def _replace_line_breaks(self, string: str) -> str: 5743 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5744 if self.pretty: 5745 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5746 return string 5747 5748 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5749 option = self.sql(expression, "this") 5750 5751 if expression.expressions: 5752 upper = option.upper() 5753 5754 # Snowflake FILE_FORMAT options are separated by whitespace 5755 sep = " " if upper == "FILE_FORMAT" else ", " 5756 5757 # Databricks copy/format options do not set their list of values with EQ 5758 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5759 values = self.expressions(expression, flat=True, sep=sep) 5760 return f"{option}{op}({values})" 5761 5762 value = self.sql(expression, "expression") 5763 5764 if not value: 5765 return option 5766 5767 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5768 5769 return f"{option}{op}{value}" 5770 5771 def credentials_sql(self, expression: exp.Credentials) -> str: 5772 cred_expr = expression.args.get("credentials") 5773 if isinstance(cred_expr, exp.Literal): 5774 # Redshift case: CREDENTIALS <string> 5775 credentials = self.sql(expression, "credentials") 5776 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5777 else: 5778 # Snowflake case: CREDENTIALS = (...) 5779 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5780 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5781 5782 storage = self.sql(expression, "storage") 5783 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5784 5785 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5786 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5787 5788 iam_role = self.sql(expression, "iam_role") 5789 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5790 5791 region = self.sql(expression, "region") 5792 region = f" REGION {region}" if region else "" 5793 5794 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5795 5796 def copy_sql(self, expression: exp.Copy) -> str: 5797 this = self.sql(expression, "this") 5798 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5799 5800 credentials = self.sql(expression, "credentials") 5801 credentials = self.seg(credentials) if credentials else "" 5802 files = self.expressions(expression, key="files", flat=True) 5803 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5804 5805 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5806 params = self.expressions( 5807 expression, 5808 key="params", 5809 sep=sep, 5810 new_line=True, 5811 skip_last=True, 5812 skip_first=True, 5813 indent=self.COPY_PARAMS_ARE_WRAPPED, 5814 ) 5815 5816 if params: 5817 if self.COPY_PARAMS_ARE_WRAPPED: 5818 params = f" WITH ({params})" 5819 elif not self.pretty and (files or credentials): 5820 params = f" {params}" 5821 5822 return f"COPY{this}{kind} {files}{credentials}{params}" 5823 5824 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5825 return "" 5826 5827 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5828 on_sql = "ON" if expression.args.get("on") else "OFF" 5829 filter_col: str | None = self.sql(expression, "filter_column") 5830 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5831 retention_period: str | None = self.sql(expression, "retention_period") 5832 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5833 5834 if filter_col or retention_period: 5835 on_sql = self.func("ON", filter_col, retention_period) 5836 5837 return f"DATA_DELETION={on_sql}" 5838 5839 def maskingpolicycolumnconstraint_sql( 5840 self, expression: exp.MaskingPolicyColumnConstraint 5841 ) -> str: 5842 this = self.sql(expression, "this") 5843 expressions = self.expressions(expression, flat=True) 5844 expressions = f" USING ({expressions})" if expressions else "" 5845 return f"MASKING POLICY {this}{expressions}" 5846 5847 def gapfill_sql(self, expression: exp.GapFill) -> str: 5848 this = self.sql(expression, "this") 5849 this = f"TABLE {this}" 5850 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5851 5852 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5853 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5854 5855 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5856 this = self.sql(expression, "this") 5857 expr = expression.expression 5858 5859 if isinstance(expr, exp.Func): 5860 # T-SQL's CLR functions are case sensitive 5861 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5862 else: 5863 expr = self.sql(expression, "expression") 5864 5865 return self.scope_resolution(expr, this) 5866 5867 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5868 if self.PARSE_JSON_NAME is None: 5869 return self.sql(expression.this) 5870 5871 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5872 5873 def rand_sql(self, expression: exp.Rand) -> str: 5874 lower = self.sql(expression, "lower") 5875 upper = self.sql(expression, "upper") 5876 5877 if lower and upper: 5878 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5879 return self.func("RAND", expression.this) 5880 5881 def changes_sql(self, expression: exp.Changes) -> str: 5882 information = self.sql(expression, "information") 5883 information = f"INFORMATION => {information}" 5884 at_before = self.sql(expression, "at_before") 5885 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5886 end = self.sql(expression, "end") 5887 end = f"{self.seg('')}{end}" if end else "" 5888 5889 return f"CHANGES ({information}){at_before}{end}" 5890 5891 def pad_sql(self, expression: exp.Pad) -> str: 5892 prefix = "L" if expression.args.get("is_left") else "R" 5893 5894 fill_pattern = self.sql(expression, "fill_pattern") or None 5895 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5896 fill_pattern = "' '" 5897 5898 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5899 5900 def summarize_sql(self, expression: exp.Summarize) -> str: 5901 table = " TABLE" if expression.args.get("table") else "" 5902 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5903 5904 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5905 generate_series = exp.GenerateSeries(**expression.args) 5906 5907 parent = expression.parent 5908 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5909 parent = parent.parent 5910 5911 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5912 return self.sql(exp.Unnest(expressions=[generate_series])) 5913 5914 if isinstance(parent, exp.Select): 5915 self.unsupported("GenerateSeries projection unnesting is not supported.") 5916 5917 return self.sql(generate_series) 5918 5919 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5920 if self.SUPPORTS_CONVERT_TIMEZONE: 5921 return self.function_fallback_sql(expression) 5922 5923 source_tz = expression.args.get("source_tz") 5924 target_tz = expression.args.get("target_tz") 5925 timestamp = expression.args.get("timestamp") 5926 5927 if source_tz and timestamp: 5928 timestamp = exp.AtTimeZone( 5929 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5930 ) 5931 5932 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5933 5934 return self.sql(expr) 5935 5936 def json_sql(self, expression: exp.JSON) -> str: 5937 this = self.sql(expression, "this") 5938 this = f" {this}" if this else "" 5939 5940 _with = expression.args.get("with_") 5941 5942 if _with is None: 5943 with_sql = "" 5944 elif not _with: 5945 with_sql = " WITHOUT" 5946 else: 5947 with_sql = " WITH" 5948 5949 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5950 5951 return f"JSON{this}{with_sql}{unique_sql}" 5952 5953 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5954 path = self.sql(expression, "path") 5955 returning = self.sql(expression, "returning") 5956 returning = f" RETURNING {returning}" if returning else "" 5957 5958 on_condition = self.sql(expression, "on_condition") 5959 on_condition = f" {on_condition}" if on_condition else "" 5960 5961 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5962 5963 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5964 regexp = " REGEXP" if expression.args.get("regexp") else "" 5965 return f"SKIP{regexp} {self.sql(expression.expression)}" 5966 5967 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5968 else_ = "ELSE " if expression.args.get("else_") else "" 5969 condition = self.sql(expression, "expression") 5970 condition = f"WHEN {condition} THEN " if condition else else_ 5971 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5972 return f"{condition}{insert}" 5973 5974 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5975 kind = self.sql(expression, "kind") 5976 source = self.sql(expression, "source") 5977 5978 if kind: 5979 expressions = self.seg(self.expressions(expression, sep=" ")) 5980 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5981 else: 5982 # If kind isn't present, we have Hive's FROM-first form: 5983 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5984 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5985 sql = f"FROM {source}{self.seg(inserts)}" 5986 5987 return self.prepend_ctes(expression, sql) 5988 5989 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5990 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5991 empty = expression.args.get("empty") 5992 empty = ( 5993 f"DEFAULT {empty} ON EMPTY" 5994 if isinstance(empty, exp.Expr) 5995 else self.sql(expression, "empty") 5996 ) 5997 5998 error = expression.args.get("error") 5999 error = ( 6000 f"DEFAULT {error} ON ERROR" 6001 if isinstance(error, exp.Expr) 6002 else self.sql(expression, "error") 6003 ) 6004 6005 if error and empty: 6006 error = ( 6007 f"{empty} {error}" 6008 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 6009 else f"{error} {empty}" 6010 ) 6011 empty = "" 6012 6013 null = self.sql(expression, "null") 6014 6015 return f"{empty}{error}{null}" 6016 6017 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 6018 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 6019 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 6020 6021 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 6022 this = self.sql(expression, "this") 6023 path = self.sql(expression, "path") 6024 6025 passing = self.expressions(expression, "passing") 6026 passing = f" PASSING {passing}" if passing else "" 6027 6028 on_condition = self.sql(expression, "on_condition") 6029 on_condition = f" {on_condition}" if on_condition else "" 6030 6031 path = f"{path}{passing}{on_condition}" 6032 6033 return self.func("JSON_EXISTS", this, path) 6034 6035 def _add_arrayagg_null_filter( 6036 self, 6037 array_agg_sql: str, 6038 array_agg_expr: exp.ArrayAgg, 6039 column_expr: exp.Expr, 6040 ) -> str: 6041 """ 6042 Add NULL filter to ARRAY_AGG if dialect requires it. 6043 6044 Args: 6045 array_agg_sql: The generated ARRAY_AGG SQL string 6046 array_agg_expr: The ArrayAgg expression node 6047 column_expr: The column/expression to filter (before ORDER BY wrapping) 6048 6049 Returns: 6050 SQL string with FILTER clause added if needed 6051 """ 6052 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 6053 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 6054 if not ( 6055 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 6056 ): 6057 return array_agg_sql 6058 6059 parent = array_agg_expr.parent 6060 if isinstance(parent, exp.Filter): 6061 parent_cond = parent.expression.this 6062 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 6063 elif column_expr.find(exp.Column): 6064 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 6065 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 6066 this_sql = ( 6067 self.expressions(column_expr) 6068 if isinstance(column_expr, exp.Distinct) 6069 else self.sql(column_expr) 6070 ) 6071 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 6072 6073 return array_agg_sql 6074 6075 @unsupported_args("count") 6076 def min_sql(self, expression: exp.Min) -> str: 6077 return self.function_fallback_sql(expression) 6078 6079 @unsupported_args("count") 6080 def max_sql(self, expression: exp.Max) -> str: 6081 return self.function_fallback_sql(expression) 6082 6083 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 6084 array_agg = self.function_fallback_sql(expression) 6085 column_expr = expression.this 6086 if isinstance(column_expr, exp.Order): 6087 column_expr = column_expr.this 6088 6089 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 6090 6091 def timeslice_sql(self, expression: exp.TimeSlice) -> str: 6092 # Only the SQLGlot dialect serializes the internal origin for round-tripping. 6093 origin = expression.args.get("origin") if self.dialect == "" else None 6094 kind = expression.args.get("kind") 6095 if origin is not None and kind is None: 6096 kind = exp.Literal.string("START") 6097 6098 return self.func( 6099 "TIME_SLICE", expression.this, expression.expression, expression.unit, kind, origin 6100 ) 6101 6102 def slice_sql(self, expression: exp.Slice) -> str: 6103 if "[" in self.dialect.tokenizer_class._IDENTIFIERS: 6104 self.unsupported("Slices are not supported") 6105 6106 step = self.sql(expression, "step") 6107 # An omitted end is written as `-` before a step, since `::` is the cast operator 6108 end = self.sql(expression.expression) or ("-" if step else "") 6109 begin = self.sql(expression.this) 6110 6111 sql = f"{end}:{step}" if step else end 6112 return f"{begin}:{sql}" if sql else f"{begin}:" 6113 6114 def apply_sql(self, expression: exp.Apply) -> str: 6115 this = self.sql(expression, "this") 6116 expr = self.sql(expression, "expression") 6117 6118 return f"{this} APPLY({expr})" 6119 6120 def _grant_or_revoke_sql( 6121 self, 6122 expression: exp.Grant | exp.Revoke, 6123 keyword: str, 6124 preposition: str, 6125 grant_option_prefix: str = "", 6126 grant_option_suffix: str = "", 6127 ) -> str: 6128 privileges_sql = self.expressions(expression, key="privileges", flat=True) 6129 6130 kind = self.sql(expression, "kind") 6131 kind = f" {kind}" if kind else "" 6132 6133 securable = self.sql(expression, "securable") 6134 securable = f" {securable}" if securable else "" 6135 6136 principals = self.expressions(expression, key="principals", flat=True) 6137 6138 if not expression.args.get("grant_option"): 6139 grant_option_prefix = grant_option_suffix = "" 6140 6141 # cascade for revoke only 6142 cascade = self.sql(expression, "cascade") 6143 cascade = f" {cascade}" if cascade else "" 6144 6145 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 6146 6147 def grant_sql(self, expression: exp.Grant) -> str: 6148 return self._grant_or_revoke_sql( 6149 expression, 6150 keyword="GRANT", 6151 preposition="TO", 6152 grant_option_suffix=" WITH GRANT OPTION", 6153 ) 6154 6155 def revoke_sql(self, expression: exp.Revoke) -> str: 6156 return self._grant_or_revoke_sql( 6157 expression, 6158 keyword="REVOKE", 6159 preposition="FROM", 6160 grant_option_prefix="GRANT OPTION FOR ", 6161 ) 6162 6163 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 6164 this = self.sql(expression, "this") 6165 columns = self.expressions(expression, flat=True) 6166 columns = f"({columns})" if columns else "" 6167 6168 return f"{this}{columns}" 6169 6170 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 6171 this = self.sql(expression, "this") 6172 6173 kind = self.sql(expression, "kind") 6174 kind = f"{kind} " if kind else "" 6175 6176 return f"{kind}{this}" 6177 6178 def columns_sql(self, expression: exp.Columns) -> str: 6179 func = self.function_fallback_sql(expression) 6180 if expression.args.get("unpack"): 6181 func = f"*{func}" 6182 6183 return func 6184 6185 def overlay_sql(self, expression: exp.Overlay) -> str: 6186 this = self.sql(expression, "this") 6187 expr = self.sql(expression, "expression") 6188 from_sql = self.sql(expression, "from_") 6189 for_sql = self.sql(expression, "for_") 6190 for_sql = f" FOR {for_sql}" if for_sql else "" 6191 6192 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 6193 6194 @unsupported_args("format") 6195 def todouble_sql(self, expression: exp.ToDouble) -> str: 6196 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 6197 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 6198 6199 def string_sql(self, expression: exp.String) -> str: 6200 this = expression.this 6201 zone = expression.args.get("zone") 6202 6203 if zone: 6204 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6205 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6206 # set for source_tz to transpile the time conversion before the STRING cast 6207 this = exp.ConvertTimezone( 6208 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6209 ) 6210 6211 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 6212 6213 def median_sql(self, expression: exp.Median) -> str: 6214 if not self.SUPPORTS_MEDIAN: 6215 return self.sql( 6216 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 6217 ) 6218 6219 return self.function_fallback_sql(expression) 6220 6221 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6222 filler = self.sql(expression, "this") 6223 filler = f" {filler}" if filler else "" 6224 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6225 return f"TRUNCATE{filler} {with_count}" 6226 6227 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6228 if self.SUPPORTS_UNIX_SECONDS: 6229 return self.function_fallback_sql(expression) 6230 6231 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6232 6233 return self.sql( 6234 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6235 ) 6236 6237 def arraysort_sql(self, expression: exp.ArraySort) -> str: 6238 asc = expression.args.get("asc") 6239 nulls_first = expression.args.get("nulls_first") 6240 if (asc and asc != exp.true()) or (nulls_first and nulls_first != exp.false()): 6241 self.unsupported("ARRAY_SORT only sorts ascending with NULLs last") 6242 return self.func("ARRAY_SORT", expression.this, expression.expression) 6243 6244 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6245 dim = expression.expression 6246 6247 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6248 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6249 if not (dim.is_int and dim.name == "1"): 6250 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6251 dim = None 6252 6253 # If dimension is required but not specified, default initialize it 6254 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6255 dim = exp.Literal.number(1) 6256 6257 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 6258 6259 def attach_sql(self, expression: exp.Attach) -> str: 6260 this = self.sql(expression, "this") 6261 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6262 expressions = self.expressions(expression) 6263 expressions = f" ({expressions})" if expressions else "" 6264 6265 return f"ATTACH{exists_sql} {this}{expressions}" 6266 6267 def detach_sql(self, expression: exp.Detach) -> str: 6268 kind = self.sql(expression, "kind") 6269 kind = f" {kind}" if kind else "" 6270 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6271 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6272 exists = " IF EXISTS" if expression.args.get("exists") else "" 6273 if exists: 6274 kind = kind or " DATABASE" 6275 6276 this = self.sql(expression, "this") 6277 this = f" {this}" if this else "" 6278 cluster = self.sql(expression, "cluster") 6279 cluster = f" {cluster}" if cluster else "" 6280 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6281 sync = " SYNC" if expression.args.get("sync") else "" 6282 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 6283 6284 def attachoption_sql(self, expression: exp.AttachOption) -> str: 6285 this = self.sql(expression, "this") 6286 value = self.sql(expression, "expression") 6287 value = f" {value}" if value else "" 6288 return f"{this}{value}" 6289 6290 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 6291 return ( 6292 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 6293 ) 6294 6295 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6296 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6297 encode = f"{encode} {self.sql(expression, 'this')}" 6298 6299 properties = expression.args.get("properties") 6300 if properties: 6301 encode = f"{encode} {self.properties(properties)}" 6302 6303 return encode 6304 6305 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6306 this = self.sql(expression, "this") 6307 include = f"INCLUDE {this}" 6308 6309 column_def = self.sql(expression, "column_def") 6310 if column_def: 6311 include = f"{include} {column_def}" 6312 6313 alias = self.sql(expression, "alias") 6314 if alias: 6315 include = f"{include} AS {alias}" 6316 6317 return include 6318 6319 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6320 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6321 name = f"{prefix} {self.sql(expression, 'this')}" 6322 return self.func("XMLELEMENT", name, *expression.expressions) 6323 6324 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6325 this = self.sql(expression, "this") 6326 expr = self.sql(expression, "expression") 6327 expr = f"({expr})" if expr else "" 6328 return f"{this}{expr}" 6329 6330 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6331 partitions = self.expressions(expression, "partition_expressions") 6332 create = self.expressions(expression, "create_expressions") 6333 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6334 6335 def partitionbyrangepropertydynamic_sql( 6336 self, expression: exp.PartitionByRangePropertyDynamic 6337 ) -> str: 6338 start = self.sql(expression, "start") 6339 end = self.sql(expression, "end") 6340 6341 every = expression.args["every"] 6342 if isinstance(every, exp.Interval) and every.this.is_string: 6343 every.this.replace(exp.Literal.number(every.name)) 6344 6345 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6346 6347 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6348 name = self.sql(expression, "this") 6349 values = self.expressions(expression, flat=True) 6350 6351 return f"NAME {name} VALUE {values}" 6352 6353 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6354 kind = self.sql(expression, "kind") 6355 sample = self.sql(expression, "sample") 6356 return f"SAMPLE {sample} {kind}" 6357 6358 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6359 kind = self.sql(expression, "kind") 6360 option = self.sql(expression, "option") 6361 option = f" {option}" if option else "" 6362 this = self.sql(expression, "this") 6363 this = f" {this}" if this else "" 6364 columns = self.expressions(expression) 6365 columns = f" {columns}" if columns else "" 6366 return f"{kind}{option} STATISTICS{this}{columns}" 6367 6368 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6369 this = self.sql(expression, "this") 6370 columns = self.expressions(expression) 6371 inner_expression = self.sql(expression, "expression") 6372 inner_expression = f" {inner_expression}" if inner_expression else "" 6373 update_options = self.sql(expression, "update_options") 6374 update_options = f" {update_options} UPDATE" if update_options else "" 6375 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6376 6377 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6378 kind = self.sql(expression, "kind") 6379 kind = f" {kind}" if kind else "" 6380 return f"DELETE{kind} STATISTICS" 6381 6382 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6383 inner_expression = self.sql(expression, "expression") 6384 return f"LIST CHAINED ROWS{inner_expression}" 6385 6386 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6387 kind = self.sql(expression, "kind") 6388 this = self.sql(expression, "this") 6389 this = f" {this}" if this else "" 6390 inner_expression = self.sql(expression, "expression") 6391 return f"VALIDATE {kind}{this}{inner_expression}" 6392 6393 def analyze_sql(self, expression: exp.Analyze) -> str: 6394 options = self.expressions(expression, key="options", sep=" ") 6395 options = f" {options}" if options else "" 6396 kind = self.sql(expression, "kind") 6397 kind = f" {kind}" if kind else "" 6398 tables = self.expressions(expression, key="tables", flat=True) 6399 tables = f" {tables}" if tables else "" 6400 mode = self.sql(expression, "mode") 6401 mode = f" {mode}" if mode else "" 6402 properties = self.sql(expression, "properties") 6403 properties = f" {properties}" if properties else "" 6404 partition = self.sql(expression, "partition") 6405 partition = f" {partition}" if partition else "" 6406 inner_expression = self.sql(expression, "expression") 6407 inner_expression = f" {inner_expression}" if inner_expression else "" 6408 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6409 6410 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6411 this = self.sql(expression, "this") 6412 namespaces = self.expressions(expression, key="namespaces") 6413 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6414 passing = self.expressions(expression, key="passing") 6415 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6416 columns = self.expressions(expression, key="columns") 6417 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6418 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6419 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6420 6421 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6422 this = self.sql(expression, "this") 6423 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6424 6425 def export_sql(self, expression: exp.Export) -> str: 6426 this = self.sql(expression, "this") 6427 connection = self.sql(expression, "connection") 6428 connection = f"WITH CONNECTION {connection} " if connection else "" 6429 options = self.sql(expression, "options") 6430 return f"EXPORT DATA {connection}{options} AS {this}" 6431 6432 def declare_sql(self, expression: exp.Declare) -> str: 6433 replace = "OR REPLACE " if expression.args.get("replace") else "" 6434 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6435 6436 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6437 variables = self.expressions(expression, "this") 6438 default = self.sql(expression, "default") 6439 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6440 6441 kind = self.sql(expression, "kind") 6442 if isinstance(expression.args.get("kind"), exp.Schema): 6443 kind = f"TABLE {kind}" 6444 6445 kind = f" {kind}" if kind else "" 6446 6447 return f"{variables}{kind}{default}" 6448 6449 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6450 kind = self.sql(expression, "kind") 6451 this = self.sql(expression, "this") 6452 set = self.sql(expression, "expression") 6453 to = self.sql(expression, "to") 6454 to = f" TO {to}" if to else "" 6455 default = self.sql(expression, "default") 6456 default = f" DEFAULT {default}" if default else "" 6457 using = self.sql(expression, "using") 6458 using = f" USING {using}" if using else "" 6459 6460 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6461 6462 return f"{kind_sql} {this} SET {set}{to}{default}{using}" 6463 6464 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6465 params = self.expressions(expression, key="params", flat=True) 6466 return self.func(expression.name, *expression.expressions) + f"({params})" 6467 6468 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6469 return self.func(expression.name, *expression.expressions) 6470 6471 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6472 return self.anonymousaggfunc_sql(expression) 6473 6474 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6475 return self.parameterizedagg_sql(expression) 6476 6477 def show_sql(self, expression: exp.Show) -> str: 6478 self.unsupported("Unsupported SHOW statement") 6479 return "" 6480 6481 def install_sql(self, expression: exp.Install) -> str: 6482 self.unsupported("Unsupported INSTALL statement") 6483 return "" 6484 6485 def liststage_sql(self, expression: exp.ListStage) -> str: 6486 pattern = self.sql(expression, "pattern") 6487 pattern = f" PATTERN = {pattern}" if pattern else "" 6488 return f"LIST {self.sql(expression, 'this')}{pattern}" 6489 6490 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6491 # Snowflake GET/PUT statements: 6492 # PUT <file> <internalStage> <properties> 6493 # GET <internalStage> <file> <properties> 6494 props = expression.args.get("properties") 6495 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6496 this = self.sql(expression, "this") 6497 target = self.sql(expression, "target") 6498 6499 if isinstance(expression, exp.Put): 6500 return f"PUT {this} {target}{props_sql}" 6501 else: 6502 return f"GET {target} {this}{props_sql}" 6503 6504 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6505 this = self.sql(expression, "this") 6506 expr = self.sql(expression, "expression") 6507 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6508 return f"TRANSLATE({this} USING {expr}{with_error})" 6509 6510 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6511 if self.SUPPORTS_DECODE_CASE: 6512 return self.func("DECODE", *expression.expressions) 6513 6514 decode_expr, *expressions = expression.expressions 6515 6516 ifs = [] 6517 for search, result in zip(expressions[::2], expressions[1::2]): 6518 if isinstance(search, exp.Literal): 6519 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6520 elif isinstance(search, exp.Null): 6521 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6522 else: 6523 if isinstance(search, exp.Binary): 6524 search = exp.paren(search) 6525 6526 cond = exp.or_( 6527 decode_expr.eq(search), 6528 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6529 copy=False, 6530 ) 6531 ifs.append(exp.If(this=cond, true=result)) 6532 6533 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6534 return self.sql(case) 6535 6536 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6537 this = self.sql(expression, "this") 6538 this = self.seg(this, sep="") 6539 dimensions = self.expressions( 6540 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6541 ) 6542 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6543 metrics = self.expressions( 6544 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6545 ) 6546 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6547 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6548 facts = self.seg(f"FACTS {facts}") if facts else "" 6549 where = self.sql(expression, "where") 6550 where = self.seg(f"WHERE {where}") if where else "" 6551 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6552 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6553 6554 def getextract_sql(self, expression: exp.GetExtract) -> str: 6555 this = expression.this 6556 expr = expression.expression 6557 6558 if not this.type or not expression.type: 6559 import sqlglot.optimizer.annotate_types 6560 6561 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6562 6563 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6564 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6565 6566 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6567 6568 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6569 return self.sql( 6570 exp.DateAdd( 6571 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6572 expression=expression.this, 6573 unit=exp.var("DAY"), 6574 ) 6575 ) 6576 6577 def space_sql(self: Generator, expression: exp.Space) -> str: 6578 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6579 6580 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6581 return f"BUILD {self.sql(expression, 'this')}" 6582 6583 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6584 method = self.sql(expression, "method") 6585 kind = expression.args.get("kind") 6586 if not kind: 6587 return f"REFRESH {method}" 6588 6589 every = self.sql(expression, "every") 6590 unit = self.sql(expression, "unit") 6591 every = f" EVERY {every} {unit}" if every else "" 6592 starts = self.sql(expression, "starts") 6593 starts = f" STARTS {starts}" if starts else "" 6594 6595 return f"REFRESH {method} ON {kind}{every}{starts}" 6596 6597 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6598 self.unsupported("The model!attribute syntax is not supported") 6599 return "" 6600 6601 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6602 return self.func("DIRECTORY", expression.this) 6603 6604 def uuid_sql(self, expression: exp.Uuid) -> str: 6605 is_string = expression.args.get("is_string", False) 6606 uuid_func_sql = self.func("UUID") 6607 6608 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6609 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6610 6611 return uuid_func_sql 6612 6613 def initcap_sql(self, expression: exp.Initcap) -> str: 6614 delimiters = expression.expression 6615 6616 if delimiters: 6617 # do not generate delimiters arg if we are round-tripping from default delimiters 6618 if ( 6619 delimiters.is_string 6620 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6621 ): 6622 delimiters = None 6623 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6624 self.unsupported("INITCAP does not support custom delimiters") 6625 delimiters = None 6626 6627 return self.func("INITCAP", expression.this, delimiters) 6628 6629 def localtime_sql(self, expression: exp.Localtime) -> str: 6630 this = expression.this 6631 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6632 6633 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6634 this = expression.this 6635 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6636 6637 def weekstart_name(self, expression: exp.WeekStart) -> str: 6638 import sqlglot.dialects.dialect 6639 6640 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6641 this = expression.this.name.upper() 6642 6643 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6644 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6645 6646 if dow_from_week_start_day != dow_from_week_offset: 6647 self.unsupported( 6648 f"WEEK({this}) is not supported; falling back to the default week start day" 6649 ) 6650 6651 return "WEEK" 6652 6653 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6654 name = self.weekstart_name(expression) 6655 6656 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6657 if isinstance(expression.parent, exp.DateTrunc): 6658 return self.sql(exp.Literal.string(name)) 6659 6660 return name 6661 6662 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6663 this = self.expressions(expression) 6664 charset = self.sql(expression, "charset") 6665 using = f" USING {charset}" if charset else "" 6666 return self.func(name, this + using) 6667 6668 def weightstring_sql(self, expression: exp.WeightString) -> str: 6669 to = self.sql(expression, "to") 6670 to = f" AS {to}" if to else "" 6671 return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}") 6672 6673 def block_sql(self, expression: exp.Block) -> str: 6674 expressions = self.expressions(expression, sep="; ", flat=True) 6675 begin = "BEGIN " if expression.args.get("begin") else "" 6676 return f"{begin}{expressions}" if expressions else "" 6677 6678 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6679 self.unsupported("Unsupported Inline UDFs syntax") 6680 return "" 6681 6682 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6683 self.unsupported("Unsupported Stored Procedure syntax") 6684 return "" 6685 6686 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6687 self.unsupported("Unsupported If block syntax") 6688 return "" 6689 6690 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6691 self.unsupported("Unsupported Case statement syntax") 6692 return "" 6693 6694 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6695 self.unsupported("Unsupported While block syntax") 6696 return "" 6697 6698 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6699 self.unsupported("Unsupported Loop block syntax") 6700 return "" 6701 6702 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6703 self.unsupported("Unsupported Repeat block syntax") 6704 return "" 6705 6706 def leave_sql(self, expression: exp.Leave) -> str: 6707 self.unsupported("Unsupported Leave syntax") 6708 return "" 6709 6710 def iterate_sql(self, expression: exp.Iterate) -> str: 6711 self.unsupported("Unsupported Iterate syntax") 6712 return "" 6713 6714 def execute_sql(self, expression: exp.Execute) -> str: 6715 self.unsupported("Unsupported Execute syntax") 6716 return "" 6717 6718 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6719 self.unsupported("Unsupported Execute syntax") 6720 return "" 6721 6722 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6723 props = self.expressions(expression, sep=" ") 6724 return f"MODIFY {props}" 6725 6726 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6727 kind = expression.args.get("kind") 6728 return f"USING {kind} {self.sql(expression, 'this')}" 6729 6730 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6731 this = self.sql(expression, "this") 6732 to = self.sql(expression, "to") 6733 return f"RENAME INDEX {this} TO {to}"
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: str | tuple[str, str]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
35def unsupported_args( 36 *args: str | tuple[str, str], 37) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 38 """ 39 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 40 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 41 """ 42 diagnostic_by_arg: dict[str, str | None] = {} 43 for arg in args: 44 if isinstance(arg, str): 45 diagnostic_by_arg[arg] = None 46 else: 47 diagnostic_by_arg[arg[0]] = arg[1] 48 49 def decorator(func: GeneratorMethod) -> GeneratorMethod: 50 @wraps(func) 51 def _func(generator: G, expression: E) -> str: 52 expression_name = expression.__class__.__name__ 53 dialect_name = generator.dialect.__class__.__name__ 54 55 for arg_name, diagnostic in diagnostic_by_arg.items(): 56 if expression.args.get(arg_name): 57 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 58 arg_name, expression_name, dialect_name 59 ) 60 generator.unsupported(diagnostic) 61 62 return func(generator, expression) 63 64 return _func 65 66 return decorator
Decorator that can be used to mark certain args of an Expr subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, typing.Any] =
{'windows': <function <lambda>>, 'qualify': <function <lambda>>}
class
Generator:
100class Generator: 101 """ 102 Generator converts a given syntax tree to the corresponding SQL string. 103 104 Args: 105 pretty: Whether to format the produced SQL string. 106 Default: False. 107 identify: Determines when an identifier should be quoted. Possible values are: 108 False (default): Never quote, except in cases where it's mandatory by the dialect. 109 True: Always quote except for specials cases. 110 'safe': Only quote identifiers that are case insensitive. 111 normalize: Whether to normalize identifiers to lowercase. 112 Default: False. 113 pad: The pad size in a formatted string. For example, this affects the indentation of 114 a projection in a query, relative to its nesting level. 115 Default: 2. 116 indent: The indentation size in a formatted string. For example, this affects the 117 indentation of subqueries and filters under a `WHERE` clause. 118 Default: 2. 119 normalize_functions: How to normalize function names. Possible values are: 120 "upper" or True (default): Convert names to uppercase. 121 "lower": Convert names to lowercase. 122 False: Disables function name normalization. 123 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 124 Default ErrorLevel.WARN. 125 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 126 This is only relevant if unsupported_level is ErrorLevel.RAISE. 127 Default: 3 128 leading_comma: Whether the comma is leading or trailing in select expressions. 129 This is only relevant when generating in pretty mode. 130 Default: False 131 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 132 The default is on the smaller end because the length only represents a segment and not the true 133 line length. 134 Default: 80 135 comments: Whether to preserve comments in the output SQL code. 136 Default: True 137 """ 138 139 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 140 **JSON_PATH_PART_TRANSFORMS, 141 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 142 exp.AllowedValuesProperty: lambda self, e: ( 143 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 144 ), 145 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 146 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 147 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 148 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 149 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 150 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 151 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 152 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 153 exp.BinaryColumnConstraint: lambda *_: "BINARY", 154 exp.CaseSpecificColumnConstraint: lambda _, e: ( 155 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 156 ), 157 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 158 exp.Ceil: lambda self, e: self.ceil_floor(e), 159 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 160 exp.CharacterSetProperty: lambda self, e: ( 161 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 162 ), 163 exp.ClusteredColumnConstraint: lambda self, e: ( 164 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 165 ), 166 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 167 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 168 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 169 exp.ConvertToCharset: lambda self, e: self.func( 170 "CONVERT", e.this, e.args["dest"], e.args.get("source") 171 ), 172 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 173 exp.CopyTagsProperty: lambda *_: "COPY TAGS", 174 exp.CredentialsProperty: lambda self, e: ( 175 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 176 ), 177 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 178 exp.SessionUser: lambda *_: "SESSION_USER", 179 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 180 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 181 exp.ApiProperty: lambda *_: "API", 182 exp.ApplicationProperty: lambda *_: "APPLICATION", 183 exp.CatalogProperty: lambda *_: "CATALOG", 184 exp.ComputeProperty: lambda *_: "COMPUTE", 185 exp.DatabaseProperty: lambda *_: "DATABASE", 186 exp.DynamicProperty: lambda *_: "DYNAMIC", 187 exp.EmptyProperty: lambda *_: "EMPTY", 188 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 189 exp.EndStatement: lambda *_: "END", 190 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 191 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 192 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 193 exp.EphemeralColumnConstraint: lambda self, e: ( 194 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 195 ), 196 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 197 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 198 exp.Except: lambda self, e: self.set_operations(e), 199 exp.ExternalProperty: lambda *_: "EXTERNAL", 200 exp.Floor: lambda self, e: self.ceil_floor(e), 201 exp.Get: lambda self, e: self.get_put_sql(e), 202 exp.GlobalProperty: lambda *_: "GLOBAL", 203 exp.HeapProperty: lambda *_: "HEAP", 204 exp.HybridProperty: lambda *_: "HYBRID", 205 exp.IcebergProperty: lambda *_: "ICEBERG", 206 exp.IgnoreGrantsProperty: lambda *_: "IGNORE GRANTS", 207 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 208 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 209 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 210 exp.Intersect: lambda self, e: self.set_operations(e), 211 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 212 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 213 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 214 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 215 exp.JSONBContainsTopKey: lambda self, e: self.binary(e, "?"), 216 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 217 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 218 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 219 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 220 exp.LanguageProperty: lambda self, e: self.naked_property(e), 221 exp.LocationProperty: lambda self, e: self.naked_property(e), 222 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 223 exp.MaskingProperty: lambda *_: "MASKING", 224 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 225 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 226 exp.NetworkProperty: lambda *_: "NETWORK", 227 exp.NonClusteredColumnConstraint: lambda self, e: ( 228 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 229 ), 230 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 231 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 232 exp.OnCommitProperty: lambda _, e: ( 233 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 234 ), 235 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 236 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 237 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 238 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 239 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 240 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 241 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 242 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 243 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 244 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 245 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 246 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 247 f"PROJECTION POLICY {self.sql(e, 'this')}" 248 ), 249 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 250 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 251 exp.Put: lambda self, e: self.get_put_sql(e), 252 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 253 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 254 ), 255 exp.ReturnsProperty: lambda self, e: ( 256 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 257 ), 258 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 259 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 260 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 261 exp.SecureProperty: lambda *_: "SECURE", 262 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 263 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 264 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 265 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 266 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 267 exp.SqlReadWriteProperty: lambda _, e: e.name, 268 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 269 exp.StabilityProperty: lambda _, e: e.name, 270 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 271 exp.StreamingTableProperty: lambda *_: "STREAMING", 272 exp.StrictProperty: lambda *_: "STRICT", 273 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 274 exp.TableColumn: lambda self, e: self.sql(e.this), 275 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 276 exp.TemporaryProperty: lambda *_: "TEMPORARY", 277 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 278 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 279 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 280 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 281 exp.TransientProperty: lambda *_: "TRANSIENT", 282 exp.VirtualProperty: lambda *_: "VIRTUAL", 283 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 284 exp.Union: lambda self, e: self.set_operations(e), 285 exp.UnloggedProperty: lambda *_: "UNLOGGED", 286 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 287 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 288 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 289 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 290 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 291 exp.UtcTimestamp: lambda self, e: self.sql( 292 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 293 ), 294 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 295 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 296 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 297 exp.VolatileProperty: lambda *_: "VOLATILE", 298 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 299 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 300 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 301 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 302 exp.WithoutRowidProperty: lambda *_: "WITHOUT ROWID", 303 exp.ForceProperty: lambda *_: "FORCE", 304 } 305 306 # Whether null ordering is supported in order by 307 # True: Full Support, None: No support, False: No support for certain cases 308 # such as window specifications, aggregate functions etc 309 NULL_ORDERING_SUPPORTED: bool | None = True 310 311 # Window functions that support NULLS FIRST/LAST 312 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 313 314 # Whether ignore nulls is inside the agg or outside. 315 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 316 IGNORE_NULLS_IN_FUNC = False 317 318 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 319 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 320 IGNORE_NULLS_BEFORE_ORDER = True 321 322 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 323 LOCKING_READS_SUPPORTED = False 324 325 # Whether the EXCEPT and INTERSECT operations can return duplicates 326 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 327 328 # Wrap derived values in parens, usually standard but spark doesn't support it 329 WRAP_DERIVED_VALUES = True 330 331 # Whether create function uses an AS before the RETURN 332 CREATE_FUNCTION_RETURN_AS = True 333 334 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 335 MATCHED_BY_SOURCE = True 336 337 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 338 SUPPORTS_MERGE_WHERE = False 339 340 # Whether MERGE uses ALL BY NAME instead of a star to update or insert all columns 341 MERGE_ALL_BY_NAME = False 342 343 # Whether the INTERVAL expression works only with values like '1 day' 344 SINGLE_STRING_INTERVAL = False 345 346 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 347 INTERVAL_ALLOWS_PLURAL_FORM = True 348 349 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 350 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 351 AUTO_REFRESH_BARE_INTERVALS = False 352 353 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 354 LIMIT_FETCH = "ALL" 355 356 # Whether limit and fetch allows expressions or just limits 357 LIMIT_ONLY_LITERALS = False 358 359 # Whether a table is allowed to be renamed with a db 360 RENAME_TABLE_WITH_DB = True 361 362 # The separator for grouping sets and rollups 363 GROUPINGS_SEP = "," 364 365 # Whether GROUPING SETS can follow GROUP BY expressions without a comma 366 SUPPORTS_GROUPING_SETS_AS_SUFFIX = False 367 368 # The string used for creating an index on a table 369 INDEX_ON = "ON" 370 371 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 372 INOUT_SEPARATOR = " " 373 374 # Whether join hints should be generated 375 JOIN_HINTS = True 376 377 # Whether directed joins are supported 378 DIRECTED_JOINS = False 379 380 # Whether top-k distance/similarity joins are supported 381 SUPPORTS_NEAREST_JOIN = False 382 383 # Whether table hints should be generated 384 TABLE_HINTS = True 385 386 # Whether query hints should be generated 387 QUERY_HINTS = True 388 389 # What kind of separator to use for query hints 390 QUERY_HINT_SEP = ", " 391 392 # Whether comparing against booleans (e.g. x IS TRUE) is supported 393 IS_BOOL_ALLOWED = True 394 395 # Whether casting a fractional number to an integer rounds it instead of truncating it 396 CAST_TO_INT_ROUNDS = False 397 398 # Whether the native integer division operator returns NULL for a zero divisor instead of raising 399 SAFE_INT_DIVISION = False 400 401 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 402 DUPLICATE_KEY_UPDATE_WITH_SET = True 403 404 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 405 LIMIT_IS_TOP = False 406 407 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 408 RETURNING_END = True 409 410 # Whether to generate an unquoted value for EXTRACT's date part argument 411 EXTRACT_ALLOWS_QUOTES = True 412 413 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 414 TZ_TO_WITH_TIME_ZONE = False 415 416 # Whether the NVL2 function is supported 417 NVL2_SUPPORTED = True 418 419 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 420 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 421 422 # Whether VALUES statements can be used as derived tables. 423 # MySQL 5 and Redshift do not allow this, so when False, it will convert 424 # SELECT * VALUES into SELECT UNION 425 VALUES_AS_TABLE = True 426 427 # Whether COLUMN/COLUMNS keywords are included when adding columns with ALTER TABLE. 428 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 429 430 # Whether ALTER TABLE mutli-column additions are wrapped in parentheses. 431 ALTER_TABLE_ADD_COLUMNS_WRAPPED = True 432 433 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 434 UNNEST_WITH_ORDINALITY = True 435 436 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 437 SEMI_ANTI_JOIN_WITH_SIDE = True 438 439 # Whether to include the type of a computed column in the CREATE DDL 440 COMPUTED_COLUMN_WITH_TYPE = True 441 442 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 443 SUPPORTS_TABLE_COPY = True 444 445 # Whether parentheses are required around the table sample's expression 446 TABLESAMPLE_REQUIRES_PARENS = True 447 448 # Whether a table sample clause's size needs to be followed by the ROWS keyword 449 TABLESAMPLE_SIZE_IS_ROWS = True 450 451 # The keyword(s) to use when generating a sample clause 452 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 453 454 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 455 TABLESAMPLE_WITH_METHOD = True 456 457 # The keyword to use when specifying the seed of a sample clause 458 TABLESAMPLE_SEED_KEYWORD = "SEED" 459 460 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 461 HISTORICAL_DATA_POST_ALIAS = False 462 463 # Whether COLLATE is a function instead of a binary operator 464 COLLATE_IS_FUNC = False 465 466 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 467 DATA_TYPE_SPECIFIERS_ALLOWED = False 468 469 # Whether conditions require booleans WHERE x = 0 vs WHERE x 470 ENSURE_BOOLS = False 471 472 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 473 CTE_RECURSIVE_KEYWORD_REQUIRED = True 474 475 # Whether CONCAT requires >1 arguments 476 SUPPORTS_SINGLE_ARG_CONCAT = True 477 478 # Whether LAST_DAY function supports a date part argument 479 LAST_DAY_SUPPORTS_DATE_PART = True 480 481 # Whether named columns are allowed in table aliases 482 SUPPORTS_TABLE_ALIAS_COLUMNS = True 483 484 # Whether named columns are allowed in CTE definitions 485 SUPPORTS_NAMED_CTE_COLUMNS = True 486 487 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 488 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 489 490 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 491 PIVOT_ALIAS_WITH_AS = True 492 493 # What delimiter to use for separating JSON key/value pairs 494 JSON_KEY_VALUE_PAIR_SEP = ":" 495 496 # INSERT OVERWRITE TABLE x override 497 INSERT_OVERWRITE = " OVERWRITE TABLE" 498 499 # Whether the SELECT .. INTO syntax is used instead of CTAS 500 SUPPORTS_SELECT_INTO = False 501 502 # Whether UNLOGGED tables can be created 503 SUPPORTS_UNLOGGED_TABLES = False 504 505 # Whether the CREATE TABLE LIKE statement is supported 506 SUPPORTS_CREATE_TABLE_LIKE = True 507 508 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 509 SUPPORTS_MODIFY_COLUMN = False 510 511 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 512 SUPPORTS_CHANGE_COLUMN = False 513 514 # Whether ALTER COLUMN can set a column's nullability together with its type 515 SUPPORTS_ALTER_COLUMN_NULLABILITY = False 516 517 # Whether ALTER COLUMN IF EXISTS is supported 518 SUPPORTS_ALTER_COLUMN_IF_EXISTS = False 519 520 # Whether the LikeProperty needs to be specified inside of the schema clause 521 LIKE_PROPERTY_INSIDE_SCHEMA = False 522 523 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 524 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 525 MULTI_ARG_DISTINCT = True 526 527 # Whether the JSON extraction operators expect a value of type JSON 528 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 529 530 # Whether bracketed keys like ["foo"] are supported in JSON paths 531 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 532 533 # Whether to escape keys using single quotes in JSON paths 534 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 535 536 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 537 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 538 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 539 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 540 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 541 542 # The JSONPathPart expressions supported by this dialect 543 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 544 545 # Whether any(f(x) for x in array) can be implemented by this dialect 546 CAN_IMPLEMENT_ARRAY_ANY = False 547 548 # Whether the function TO_NUMBER is supported 549 SUPPORTS_TO_NUMBER = True 550 551 # Whether EXCLUDE in window specification is supported 552 SUPPORTS_WINDOW_EXCLUDE = False 553 554 # Whether or not set op modifiers apply to the outer set op or select. 555 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 556 # True means limit 1 happens after the set op, False means it it happens on y. 557 SET_OP_MODIFIERS = True 558 559 # Whether a SELECT operand can have a branch-local LIMIT/TOP without parentheses. 560 SET_OP_LIMITS = False 561 562 # Whether set operation operands can be parenthesized without a SELECT wrapper. 563 SET_OP_PARENTHESIZED_OPERANDS = True 564 565 # Whether parameters from COPY statement are wrapped in parentheses 566 COPY_PARAMS_ARE_WRAPPED = True 567 568 # Whether values of params are set with "=" token or empty space 569 COPY_PARAMS_EQ_REQUIRED = False 570 571 # Whether COPY statement has INTO keyword 572 COPY_HAS_INTO_KEYWORD = True 573 574 # Whether the conditional TRY(expression) function is supported 575 TRY_SUPPORTED = True 576 577 # Whether the UESCAPE syntax in unicode strings is supported 578 SUPPORTS_UESCAPE = True 579 580 # Function used to replace escaped unicode codes in unicode strings 581 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 582 583 # The keyword to use when generating a star projection with excluded columns 584 STAR_EXCEPT = "EXCEPT" 585 586 # The HEX function name 587 HEX_FUNC = "HEX" 588 589 # Function that constructs a binary value from a hex-encoded byte string. 590 BYTE_STRING_FUNCTION = "" 591 592 # The keywords to use when prefixing & separating WITH based properties 593 WITH_PROPERTIES_PREFIX = "WITH" 594 595 # Whether to quote the generated expression of exp.JsonPath 596 QUOTE_JSON_PATH = True 597 598 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 599 PAD_FILL_PATTERN_IS_REQUIRED = False 600 601 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 602 SUPPORTS_EXPLODING_PROJECTIONS = True 603 604 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 605 ARRAY_CONCAT_IS_VAR_LEN = True 606 607 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 608 SUPPORTS_CONVERT_TIMEZONE = False 609 610 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 611 SUPPORTS_MEDIAN = True 612 613 # Whether UNIX_SECONDS(timestamp) is supported 614 SUPPORTS_UNIX_SECONDS = False 615 616 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 617 ALTER_SET_WRAPPED = False 618 619 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 620 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 621 # TODO: The normalization should be done by default once we've tested it across all dialects. 622 NORMALIZE_EXTRACT_DATE_PARTS = False 623 624 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 625 PARSE_JSON_NAME: str | None = "PARSE_JSON" 626 627 # The function name of the exp.ArraySize expression 628 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 629 630 # The syntax to use when altering the type of a column 631 ALTER_SET_TYPE = "SET DATA TYPE" 632 633 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 634 # None -> Doesn't support it at all 635 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 636 # True (Postgres) -> Explicitly requires it 637 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 638 639 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 640 SUPPORTS_DECODE_CASE = True 641 642 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 643 SUPPORTS_BETWEEN_FLAGS = False 644 645 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 646 SUPPORTS_LIKE_QUANTIFIERS = True 647 648 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 649 MATCH_AGAINST_TABLE_PREFIX: str | None = None 650 651 # Whether to include the VARIABLE keyword for SET assignments 652 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 653 654 # The keyword to use for default value assignment in DECLARE statements 655 DECLARE_DEFAULT_ASSIGNMENT = "=" 656 657 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 658 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 659 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 660 UPDATE_STATEMENT_SUPPORTS_FROM = True 661 662 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 663 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 664 665 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 666 # - Snowflake: DROP ICEBERG TABLE a.b; 667 # - DuckDB: DROP TABLE a.b; 668 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 669 670 TYPE_MAPPING: t.ClassVar = { 671 exp.DType.DATETIME2: "TIMESTAMP", 672 exp.DType.NCHAR: "CHAR", 673 exp.DType.NVARCHAR: "VARCHAR", 674 exp.DType.MEDIUMTEXT: "TEXT", 675 exp.DType.LONGTEXT: "TEXT", 676 exp.DType.TINYTEXT: "TEXT", 677 exp.DType.BLOB: "VARBINARY", 678 exp.DType.MEDIUMBLOB: "BLOB", 679 exp.DType.LONGBLOB: "BLOB", 680 exp.DType.TINYBLOB: "BLOB", 681 exp.DType.INET: "INET", 682 exp.DType.ROWVERSION: "VARBINARY", 683 exp.DType.SMALLDATETIME: "TIMESTAMP", 684 } 685 686 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 687 688 # mapping of DType to its default parameters, bounds 689 TYPE_PARAM_SETTINGS: t.ClassVar[ 690 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 691 ] = {} 692 693 TIME_PART_SINGULARS: t.ClassVar = { 694 "MICROSECONDS": "MICROSECOND", 695 "SECONDS": "SECOND", 696 "MINUTES": "MINUTE", 697 "HOURS": "HOUR", 698 "DAYS": "DAY", 699 "WEEKS": "WEEK", 700 "MONTHS": "MONTH", 701 "QUARTERS": "QUARTER", 702 "YEARS": "YEAR", 703 } 704 705 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 706 "cluster": lambda self, e: self.sql(e, "cluster"), 707 "distribute": lambda self, e: self.sql(e, "distribute"), 708 "sort": lambda self, e: self.sql(e, "sort"), 709 **AFTER_HAVING_MODIFIER_TRANSFORMS, 710 } 711 712 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 713 714 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 715 716 PARAMETER_TOKEN = "@" 717 NAMED_PLACEHOLDER_TOKEN = ":" 718 719 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 720 721 PROPERTIES_LOCATION: t.ClassVar = { 722 exp.AggregateKeyProperty: exp.Properties.Location.POST_SCHEMA, 723 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 725 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 726 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 727 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 729 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 730 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 731 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 733 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 735 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 736 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 737 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.CopyTagsProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 740 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 745 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 746 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 747 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 748 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 749 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 750 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 751 exp.DistKeyProperty: exp.Properties.Location.UNSUPPORTED, 752 exp.DistStyleProperty: exp.Properties.Location.UNSUPPORTED, 753 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 754 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 755 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 759 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 760 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 761 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 762 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 763 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 764 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 765 exp.HeapProperty: exp.Properties.Location.POST_WITH, 766 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 767 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 768 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 769 exp.IgnoreGrantsProperty: exp.Properties.Location.POST_SCHEMA, 770 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 771 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 772 exp.InteractiveProperty: exp.Properties.Location.UNSUPPORTED, 773 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 774 exp.JournalProperty: exp.Properties.Location.POST_NAME, 775 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 776 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 777 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 778 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 779 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 780 exp.LogProperty: exp.Properties.Location.POST_NAME, 781 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 782 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 783 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 784 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 785 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 786 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 787 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 788 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 789 exp.OptionsProperty: exp.Properties.Location.UNSUPPORTED, 790 exp.Order: exp.Properties.Location.POST_SCHEMA, 791 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 792 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 793 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 794 exp.PhysicalProperty: exp.Properties.Location.UNSUPPORTED, 795 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 796 exp.Property: exp.Properties.Location.POST_WITH, 797 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 798 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 799 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 800 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 801 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 802 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 803 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 804 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 805 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 806 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 807 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 808 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 809 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 810 exp.Set: exp.Properties.Location.POST_SCHEMA, 811 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 812 exp.SetProperty: exp.Properties.Location.POST_CREATE, 813 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 814 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 815 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 816 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 817 exp.SortKeyProperty: exp.Properties.Location.UNSUPPORTED, 818 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 819 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 820 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 821 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 822 exp.StorageProperty: exp.Properties.Location.UNSUPPORTED, 823 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 824 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 825 exp.Tags: exp.Properties.Location.POST_WITH, 826 exp.TableSpaceProperty: exp.Properties.Location.UNSUPPORTED, 827 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 828 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 829 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 830 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 831 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 832 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 833 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 834 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 835 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 836 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 837 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 838 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 839 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 840 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 841 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 842 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 843 exp.WithoutRowidProperty: exp.Properties.Location.UNSUPPORTED, 844 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 845 } 846 847 # Keywords that can't be used as unquoted identifier names 848 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 849 850 # Exprs whose comments are separated from them for better formatting 851 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 852 exp.Command, 853 exp.Create, 854 exp.Describe, 855 exp.Delete, 856 exp.Drop, 857 exp.From, 858 exp.Insert, 859 exp.Join, 860 exp.MultitableInserts, 861 exp.Order, 862 exp.Group, 863 exp.Having, 864 exp.Select, 865 exp.SetOperation, 866 exp.Update, 867 exp.Where, 868 exp.With, 869 ) 870 871 # Exprs that should not have their comments generated in maybe_comment 872 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 873 exp.Binary, 874 exp.SetOperation, 875 ) 876 877 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 878 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 879 exp.Column, 880 exp.Literal, 881 exp.Neg, 882 exp.Paren, 883 ) 884 885 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 886 exp.DType.NVARCHAR, 887 exp.DType.VARCHAR, 888 exp.DType.CHAR, 889 exp.DType.NCHAR, 890 } 891 892 # Exprs that need to have all CTEs under them bubbled up to them 893 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 894 895 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 896 897 MOD_OPERATOR = "%" 898 899 # Infix operators that bind at least as tightly as %, so a Mod on their right side needs parentheses 900 MOD_PAREN_PARENT_TYPES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 901 exp.Mul, 902 exp.Div, 903 exp.IntDiv, 904 exp.Mod, 905 ) 906 907 # ALTER options that follow the others without a comma, e.g. MySQL's 908 # ENGINE=InnoDB PARTITION BY LIST (...) or StarRocks' PARTITION BY ... DISTRIBUTED BY ... 909 ALTER_PARTITION_OPTIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 910 911 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 912 913 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 914 915 __slots__ = ( 916 "pretty", 917 "identify", 918 "normalize", 919 "pad", 920 "_indent", 921 "normalize_functions", 922 "unsupported_level", 923 "max_unsupported", 924 "leading_comma", 925 "max_text_width", 926 "comments", 927 "dialect", 928 "unsupported_messages", 929 "_escaped_quote_end", 930 "_escaped_byte_quote_end", 931 "_escaped_identifier_end", 932 "_identifier_escaped_sequences", 933 "_next_name", 934 "_identifier_start", 935 "_identifier_end", 936 "_quote_json_path_key_using_brackets", 937 "_dispatch", 938 ) 939 940 def __init__( 941 self, 942 pretty: bool | int | None = None, 943 identify: str | bool = False, 944 normalize: bool = False, 945 pad: int = 2, 946 indent: int = 2, 947 normalize_functions: str | bool | None = None, 948 unsupported_level: ErrorLevel = ErrorLevel.WARN, 949 max_unsupported: int = 3, 950 leading_comma: bool = False, 951 max_text_width: int = 80, 952 comments: bool = True, 953 dialect: DialectType = None, 954 ): 955 import sqlglot 956 import sqlglot.dialects.dialect 957 958 self.pretty = pretty if pretty is not None else sqlglot.pretty 959 self.identify = identify 960 self.normalize = normalize 961 self.pad = pad 962 self._indent = indent 963 self.unsupported_level = unsupported_level 964 self.max_unsupported = max_unsupported 965 self.leading_comma = leading_comma 966 self.max_text_width = max_text_width 967 self.comments = comments 968 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 969 970 # This is both a Dialect property and a Generator argument, so we prioritize the latter 971 self.normalize_functions = ( 972 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 973 ) 974 975 self.unsupported_messages: list[str] = [] 976 self._escaped_quote_end: str = ( 977 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 978 ) 979 self._escaped_byte_quote_end: str = ( 980 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 981 if self.dialect.BYTE_END 982 else "" 983 ) 984 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 985 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 986 987 self._next_name = name_sequence("_t") 988 989 self._identifier_start = self.dialect.IDENTIFIER_START 990 self._identifier_end = self.dialect.IDENTIFIER_END 991 992 self._quote_json_path_key_using_brackets = True 993 994 cls = type(self) 995 dispatch = _DISPATCH_CACHE.get(cls) 996 if dispatch is None: 997 dispatch = _build_dispatch(cls) 998 _DISPATCH_CACHE[cls] = dispatch 999 self._dispatch = dispatch 1000 1001 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 1002 """ 1003 Generates the SQL string corresponding to the given syntax tree. 1004 1005 Args: 1006 expression: The syntax tree. 1007 copy: Whether to copy the expression. The generator performs mutations so 1008 it is safer to copy. 1009 1010 Returns: 1011 The SQL string corresponding to `expression`. 1012 """ 1013 if copy: 1014 expression = expression.copy() 1015 1016 expression = self.preprocess(expression) 1017 1018 self.unsupported_messages = [] 1019 sql = self.sql(expression).strip() 1020 1021 if self.pretty: 1022 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1023 1024 if self.unsupported_level == ErrorLevel.IGNORE: 1025 return sql 1026 1027 if self.unsupported_level == ErrorLevel.WARN: 1028 for msg in self.unsupported_messages: 1029 logger.warning(msg) 1030 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1031 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1032 1033 return sql 1034 1035 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1036 """Apply generic preprocessing transformations to a given expression.""" 1037 expression = self._move_ctes_to_top_level(expression) 1038 1039 if self.ENSURE_BOOLS: 1040 import sqlglot.transforms 1041 1042 expression = sqlglot.transforms.ensure_bools(expression) 1043 1044 return expression 1045 1046 def _move_ctes_to_top_level(self, expression: E) -> E: 1047 if ( 1048 not expression.parent 1049 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 1050 and any(node.parent is not expression for node in expression.find_all(exp.With)) 1051 ): 1052 import sqlglot.transforms 1053 1054 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 1055 return expression 1056 1057 def unsupported(self, message: str) -> None: 1058 if self.unsupported_level == ErrorLevel.IMMEDIATE: 1059 raise UnsupportedError(message) 1060 self.unsupported_messages.append(message) 1061 1062 def sep(self, sep: str = " ") -> str: 1063 return f"{sep.strip()}\n" if self.pretty else sep 1064 1065 def seg(self, sql: str, sep: str = " ") -> str: 1066 return f"{self.sep(sep)}{sql}" 1067 1068 def sanitize_comment(self, comment: str) -> str: 1069 comment = " " + comment if comment[0].strip() else comment 1070 comment = comment + " " if comment[-1].strip() else comment 1071 1072 # Escape block comment markers to prevent premature closure or unintended nesting. 1073 # This is necessary because single-line comments (--) are converted to block comments 1074 # (/* */) on output, and any */ in the original text would close the comment early. 1075 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1076 1077 return comment 1078 1079 def maybe_comment( 1080 self, 1081 sql: str, 1082 expression: exp.Expr | None = None, 1083 comments: list[str] | None = None, 1084 separated: bool = False, 1085 ) -> str: 1086 comments = ( 1087 ((expression and expression.comments) if comments is None else comments) # type: ignore 1088 if self.comments 1089 else None 1090 ) 1091 1092 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1093 return sql 1094 1095 comments_list = [ 1096 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1097 for comment in comments 1098 if comment 1099 ] 1100 1101 if not comments_list: 1102 return sql 1103 1104 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1105 comments_sql = self.sep().join(comments_list) 1106 return ( 1107 f"{self.sep()}{comments_sql}{sql}" 1108 if not sql or sql[0].isspace() 1109 else f"{comments_sql}{self.sep()}{sql}" 1110 ) 1111 1112 return f"{sql} {' '.join(comments_list)}" 1113 1114 def wrap(self, expression: exp.Expr | str) -> str: 1115 this_sql = ( 1116 self.sql(expression) 1117 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1118 else self.sql(expression, "this") 1119 ) 1120 if not this_sql: 1121 return "()" 1122 1123 this_sql = self.indent(this_sql, level=1, pad=0) 1124 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1125 1126 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1127 original = self.identify 1128 self.identify = False 1129 result = func(*args, **kwargs) 1130 self.identify = original 1131 return result 1132 1133 def normalize_func(self, name: str) -> str: 1134 if self.normalize_functions == "upper" or self.normalize_functions is True: 1135 return name.upper() 1136 if self.normalize_functions == "lower": 1137 return name.lower() 1138 return name 1139 1140 def indent( 1141 self, 1142 sql: str, 1143 level: int = 0, 1144 pad: int | None = None, 1145 skip_first: bool = False, 1146 skip_last: bool = False, 1147 ) -> str: 1148 if not self.pretty or not sql: 1149 return sql 1150 1151 pad = self.pad if pad is None else pad 1152 lines = sql.split("\n") 1153 1154 return "\n".join( 1155 ( 1156 line 1157 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1158 else f"{' ' * (level * self._indent + pad)}{line}" 1159 ) 1160 for i, line in enumerate(lines) 1161 ) 1162 1163 def sql( 1164 self, 1165 expression: str | exp.Expr | None, 1166 key: str | None = None, 1167 comment: bool = True, 1168 ) -> str: 1169 if not expression: 1170 return "" 1171 1172 if isinstance(expression, str): 1173 return expression 1174 1175 if key: 1176 value = expression.args.get(key) 1177 if value: 1178 return self.sql(value) 1179 return "" 1180 1181 handler = self._dispatch.get(expression.__class__) 1182 1183 if handler: 1184 sql = handler(self, expression) 1185 elif isinstance(expression, exp.Func): 1186 sql = self.function_fallback_sql(expression) 1187 elif isinstance(expression, exp.Property): 1188 sql = self.property_sql(expression) 1189 else: 1190 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1191 1192 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1193 1194 def uncache_sql(self, expression: exp.Uncache) -> str: 1195 table = self.sql(expression, "this") 1196 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1197 return f"UNCACHE TABLE{exists_sql} {table}" 1198 1199 def cache_sql(self, expression: exp.Cache) -> str: 1200 lazy = " LAZY" if expression.args.get("lazy") else "" 1201 table = self.sql(expression, "this") 1202 options = expression.args.get("options") 1203 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1204 sql = self.sql(expression, "expression") 1205 sql = f" AS{self.sep()}{sql}" if sql else "" 1206 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1207 return self.prepend_ctes(expression, sql) 1208 1209 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1210 default = "DEFAULT " if expression.args.get("default") else "" 1211 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1212 1213 def column_parts(self, expression: exp.Column) -> str: 1214 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1215 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1216 return self.sql(expression, "this") 1217 1218 return ".".join( 1219 self.sql(part) 1220 for part in ( 1221 expression.args.get("catalog"), 1222 expression.args.get("db"), 1223 expression.args.get("table"), 1224 expression.args.get("this"), 1225 ) 1226 if part 1227 ) 1228 1229 def column_sql(self, expression: exp.Column) -> str: 1230 join_mark = " (+)" if expression.args.get("join_mark") else "" 1231 1232 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1233 join_mark = "" 1234 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1235 1236 return f"{self.column_parts(expression)}{join_mark}" 1237 1238 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1239 return self.column_sql(expression) 1240 1241 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1242 this = self.sql(expression, "this") 1243 this = f" {this}" if this else "" 1244 position = self.sql(expression, "position") 1245 return f"{position}{this}" 1246 1247 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1248 column = self.sql(expression, "this") 1249 kind = self.sql(expression, "kind") 1250 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1251 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1252 kind = f"{sep}{kind}" if kind else "" 1253 constraints = f" {constraints}" if constraints else "" 1254 position = self.sql(expression, "position") 1255 position = f" {position}" if position else "" 1256 1257 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1258 kind = "" 1259 1260 return f"{exists}{column}{kind}{constraints}{position}" 1261 1262 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1263 this = self.sql(expression, "this") 1264 kind_sql = self.sql(expression, "kind").strip() 1265 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1266 1267 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1268 this = self.sql(expression, "this") 1269 if expression.args.get("not_null"): 1270 persisted = " PERSISTED NOT NULL" 1271 elif expression.args.get("persisted"): 1272 persisted = " PERSISTED" 1273 else: 1274 persisted = "" 1275 1276 return f"AS {this}{persisted}" 1277 1278 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1279 return self.token_sql(TokenType.AUTO_INCREMENT) 1280 1281 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1282 if isinstance(expression.this, list): 1283 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1284 else: 1285 this = self.sql(expression, "this") 1286 1287 return f"COMPRESS {this}" 1288 1289 def generatedasidentitycolumnconstraint_sql( 1290 self, expression: exp.GeneratedAsIdentityColumnConstraint 1291 ) -> str: 1292 this = "" 1293 if expression.this is not None: 1294 on_null = " ON NULL" if expression.args.get("on_null") else "" 1295 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1296 1297 start = expression.args.get("start") 1298 start = f"START WITH {start}" if start else "" 1299 increment = expression.args.get("increment") 1300 increment = f" INCREMENT BY {increment}" if increment else "" 1301 minvalue = expression.args.get("minvalue") 1302 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1303 maxvalue = expression.args.get("maxvalue") 1304 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1305 cycle = expression.args.get("cycle") 1306 cycle_sql = "" 1307 1308 if cycle is not None: 1309 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1310 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1311 1312 sequence_opts = "" 1313 if start or increment or cycle_sql: 1314 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1315 sequence_opts = f" ({sequence_opts.strip()})" 1316 1317 expr = self.sql(expression, "expression") 1318 expr = f"({expr})" if expr else "IDENTITY" 1319 1320 return f"GENERATED{this} AS {expr}{sequence_opts}" 1321 1322 def generatedasrowcolumnconstraint_sql( 1323 self, expression: exp.GeneratedAsRowColumnConstraint 1324 ) -> str: 1325 start = "START" if expression.args.get("start") else "END" 1326 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1327 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1328 1329 def periodforsystemtimeconstraint_sql( 1330 self, expression: exp.PeriodForSystemTimeConstraint 1331 ) -> str: 1332 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1333 1334 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1335 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1336 1337 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1338 desc = expression.args.get("desc") 1339 if desc is not None: 1340 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1341 options = self.expressions(expression, key="options", flat=True, sep=" ") 1342 options = f" {options}" if options else "" 1343 include = self.sql(expression, "include") 1344 return f"PRIMARY KEY{include}{options}" 1345 1346 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1347 this = self.sql(expression, "this") 1348 this = f" {this}" if this else "" 1349 index_type = expression.args.get("index_type") 1350 index_type = f" USING {index_type}" if index_type else "" 1351 on_conflict = self.sql(expression, "on_conflict") 1352 on_conflict = f" {on_conflict}" if on_conflict else "" 1353 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1354 options = self.expressions(expression, key="options", flat=True, sep=" ") 1355 options = f" {options}" if options else "" 1356 include = self.sql(expression, "include") 1357 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{include}{options}" 1358 1359 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1360 input_ = expression.args.get("input_") 1361 output = expression.args.get("output") 1362 variadic = expression.args.get("variadic") 1363 1364 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1365 if variadic: 1366 return "VARIADIC" 1367 1368 if input_ and output: 1369 return f"IN{self.INOUT_SEPARATOR}OUT" 1370 if input_: 1371 return "IN" 1372 if output: 1373 return "OUT" 1374 1375 return "" 1376 1377 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1378 return self.sql(expression, "this") 1379 1380 def create_sql(self, expression: exp.Create) -> str: 1381 kind = self.sql(expression, "kind") 1382 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1383 1384 properties = expression.args.get("properties") 1385 1386 if ( 1387 kind == "TRIGGER" 1388 and properties 1389 and properties.expressions 1390 and isinstance(properties.expressions[0], exp.TriggerProperties) 1391 and properties.expressions[0].args.get("constraint") 1392 ): 1393 kind = f"CONSTRAINT {kind}" 1394 1395 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1396 1397 this = self.createable_sql(expression, properties_locs) 1398 1399 properties_sql = "" 1400 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1401 exp.Properties.Location.POST_WITH 1402 ): 1403 props_ast = exp.Properties( 1404 expressions=[ 1405 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1406 *properties_locs[exp.Properties.Location.POST_WITH], 1407 ] 1408 ) 1409 props_ast.parent = expression 1410 properties_sql = self.sql(props_ast) 1411 1412 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1413 properties_sql = self.sep() + properties_sql 1414 elif not self.pretty: 1415 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1416 properties_sql = f" {properties_sql}" 1417 1418 begin = " BEGIN" if expression.args.get("begin") else "" 1419 1420 expression_sql = self.sql(expression, "expression") 1421 if expression_sql: 1422 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1423 1424 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1425 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1426 ): 1427 postalias_props_sql = "" 1428 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1429 postalias_props_sql = self.properties( 1430 exp.Properties( 1431 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1432 ), 1433 wrapped=False, 1434 ) 1435 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1436 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1437 1438 postindex_props_sql = "" 1439 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1440 postindex_props_sql = self.properties( 1441 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1442 wrapped=False, 1443 prefix=" ", 1444 ) 1445 1446 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1447 indexes = f" {indexes}" if indexes else "" 1448 index_sql = indexes + postindex_props_sql 1449 1450 replace = " OR REPLACE" if expression.args.get("replace") else "" 1451 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1452 unique = " UNIQUE" if expression.args.get("unique") else "" 1453 1454 clustered = expression.args.get("clustered") 1455 if clustered is None: 1456 clustered_sql = "" 1457 elif clustered: 1458 clustered_sql = " CLUSTERED COLUMNSTORE" 1459 else: 1460 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1461 1462 postcreate_props_sql = "" 1463 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1464 postcreate_props_sql = self.properties( 1465 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1466 sep=" ", 1467 prefix=" ", 1468 wrapped=False, 1469 ) 1470 1471 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1472 1473 postexpression_props_sql = "" 1474 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1475 postexpression_props_sql = self.properties( 1476 exp.Properties( 1477 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1478 ), 1479 sep=" ", 1480 prefix=" ", 1481 wrapped=False, 1482 ) 1483 1484 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1485 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1486 no_schema_binding = ( 1487 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1488 ) 1489 1490 clone = self.sql(expression, "clone") 1491 clone = f" {clone}" if clone else "" 1492 1493 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1494 properties_expression = f"{expression_sql}{properties_sql}" 1495 else: 1496 properties_expression = f"{properties_sql}{expression_sql}" 1497 1498 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1499 return self.prepend_ctes(expression, expression_sql) 1500 1501 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1502 start = self.sql(expression, "start") 1503 start = f"START WITH {start}" if start else "" 1504 increment = self.sql(expression, "increment") 1505 increment = f" INCREMENT BY {increment}" if increment else "" 1506 minvalue = self.sql(expression, "minvalue") 1507 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1508 maxvalue = self.sql(expression, "maxvalue") 1509 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1510 owned = self.sql(expression, "owned") 1511 owned = f" OWNED BY {owned}" if owned else "" 1512 1513 cache = expression.args.get("cache") 1514 if cache is None: 1515 cache_str = "" 1516 elif cache is True: 1517 cache_str = " CACHE" 1518 else: 1519 cache_str = f" CACHE {cache}" 1520 1521 options = self.expressions(expression, key="options", flat=True, sep=" ") 1522 options = f" {options}" if options else "" 1523 1524 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1525 1526 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1527 timing = expression.args.get("timing", "") 1528 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1529 timing_events = f"{timing} {events}".strip() if timing or events else "" 1530 1531 parts = [timing_events, "ON", self.sql(expression, "table")] 1532 1533 if referenced_table := expression.args.get("referenced_table"): 1534 parts.extend(["FROM", self.sql(referenced_table)]) 1535 1536 if deferrable := expression.args.get("deferrable"): 1537 parts.append(deferrable) 1538 1539 if initially := expression.args.get("initially"): 1540 parts.append(f"INITIALLY {initially}") 1541 1542 if referencing := expression.args.get("referencing"): 1543 parts.append(self.sql(referencing)) 1544 1545 if for_each := expression.args.get("for_each"): 1546 parts.append(f"FOR EACH {for_each}") 1547 1548 if when := expression.args.get("when"): 1549 parts.append(f"WHEN ({self.sql(when)})") 1550 1551 parts.append(self.sql(expression, "execute")) 1552 1553 return self.sep().join(parts) 1554 1555 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1556 parts = [] 1557 1558 if old_alias := expression.args.get("old"): 1559 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1560 1561 if new_alias := expression.args.get("new"): 1562 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1563 1564 return f"REFERENCING {' '.join(parts)}" 1565 1566 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1567 columns = expression.args.get("columns") 1568 if columns: 1569 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1570 1571 return self.sql(expression, "this") 1572 1573 def clone_sql(self, expression: exp.Clone) -> str: 1574 this = self.sql(expression, "this") 1575 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1576 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1577 return f"{shallow}{keyword} {this}" 1578 1579 def describe_sql(self, expression: exp.Describe) -> str: 1580 style = expression.args.get("style") 1581 style = f" {style}" if style else "" 1582 partition = self.sql(expression, "partition") 1583 partition = f" {partition}" if partition else "" 1584 format = self.sql(expression, "format") 1585 format = f" {format}" if format else "" 1586 as_json = " AS JSON" if expression.args.get("as_json") else "" 1587 1588 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1589 1590 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1591 tag = self.sql(expression, "tag") 1592 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1593 1594 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1595 with_ = self.sql(expression, "with_") 1596 if with_: 1597 sql = f"{with_}{self.sep()}{sql}" 1598 return sql 1599 1600 def with_sql(self, expression: exp.With) -> str: 1601 udfs = self.expressions(expression, key="udfs", flat=True) 1602 udfs = f"WITH {udfs}" if udfs else "" 1603 1604 sql = self.expressions(expression, flat=True) 1605 1606 recursive = ( 1607 "RECURSIVE " 1608 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1609 else "" 1610 ) 1611 search = self.sql(expression, "search") 1612 search = f" {search}" if search else "" 1613 cycle = self.sql(expression, "cycle") 1614 cycle = f" {cycle}" if cycle else "" 1615 1616 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1617 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1618 1619 def cte_sql(self, expression: exp.CTE) -> str: 1620 alias = expression.args.get("alias") 1621 if alias: 1622 alias.add_comments(expression.pop_comments()) 1623 1624 alias_sql = self.sql(expression, "alias") 1625 1626 materialized = expression.args.get("materialized") 1627 if materialized is False: 1628 materialized = "NOT MATERIALIZED " 1629 elif materialized: 1630 materialized = "MATERIALIZED " 1631 1632 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1633 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1634 1635 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1636 1637 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1638 alias = self.sql(expression, "this") 1639 columns = self.expressions(expression, key="columns", flat=True) 1640 columns = f"({columns})" if columns else "" 1641 1642 if ( 1643 columns 1644 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1645 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1646 ): 1647 columns = "" 1648 self.unsupported("Named columns are not supported in table alias.") 1649 1650 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1651 alias = self._next_name() 1652 1653 return f"{alias}{columns}" 1654 1655 def bitstring_sql(self, expression: exp.BitString) -> str: 1656 this = self.sql(expression, "this") 1657 if self.dialect.BIT_START: 1658 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1659 return f"{int(this, 2)}" 1660 1661 def hexstring_sql( 1662 self, expression: exp.HexString, binary_function_repr: str | None = None 1663 ) -> str: 1664 this = self.sql(expression, "this") 1665 is_integer_type = expression.args.get("is_integer") 1666 1667 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1668 not self.dialect.HEX_START and not binary_function_repr 1669 ): 1670 # Integer representation will be returned if: 1671 # - The read dialect treats the hex value as integer literal but not the write 1672 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1673 return f"{int(this, 16)}" 1674 1675 if not is_integer_type: 1676 # Read dialect treats the hex value as BINARY/BLOB 1677 if binary_function_repr: 1678 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1679 return self.func(binary_function_repr, exp.Literal.string(this)) 1680 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1681 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1682 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1683 1684 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1685 1686 def bytestring_sql(self, expression: exp.ByteString) -> str: 1687 this = self.sql(expression, "this") 1688 is_bytes = bool(expression.args.get("is_bytes")) 1689 if is_bytes and self.BYTE_STRING_FUNCTION: 1690 # Hex digits can be quoted directly without escaping. 1691 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1692 1693 if self.dialect.BYTE_START: 1694 escaped_byte_string = self.escape_str( 1695 this, 1696 escape_backslash=bool( 1697 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1698 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1699 ), 1700 delimiter=self.dialect.BYTE_END, 1701 escaped_delimiter=self._escaped_byte_quote_end, 1702 is_byte_string=True, 1703 is_bytes=is_bytes, 1704 ) 1705 delimited_byte_string = ( 1706 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1707 ) 1708 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1709 return self.sql( 1710 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1711 ) 1712 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1713 return self.sql( 1714 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1715 ) 1716 1717 return delimited_byte_string 1718 1719 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1720 return self.sql(exp.Literal.string(this)) 1721 1722 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1723 return "" 1724 1725 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1726 this = self.sql(expression, "this") 1727 escape = expression.args.get("escape") 1728 unicode_start = self.dialect.UNICODE_START 1729 1730 if unicode_start: 1731 escape_substitute = r"\\\1" 1732 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1733 else: 1734 escape_substitute = r"\\u\1" 1735 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1736 1737 if escape: 1738 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1739 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1740 else: 1741 escape_pattern = ESCAPED_UNICODE_RE 1742 escape_sql = "" 1743 1744 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1745 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1746 1747 if unicode_start: 1748 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1749 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1750 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1751 else: 1752 this = self.escape_str(this, escape_backslash=False) 1753 1754 return f"{left_quote}{this}{right_quote}{escape_sql}" 1755 1756 def rawstring_sql(self, expression: exp.RawString) -> str: 1757 string = expression.this 1758 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1759 string = string.replace("\\", "\\\\") 1760 1761 string = self.escape_str(string, escape_backslash=False) 1762 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1763 1764 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1765 this = self.sql(expression, "this") 1766 specifier = self.sql(expression, "expression") 1767 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1768 return f"{this}{specifier}" 1769 1770 def datatype_param_bound_limiter( 1771 self, 1772 expression: exp.DataType, 1773 type_value: exp.DType, 1774 defaults: tuple[int, ...], 1775 bounds: tuple[int | None, ...], 1776 ) -> exp.DataType: 1777 params = expression.expressions 1778 1779 if not params: 1780 if defaults: 1781 expression.set( 1782 "expressions", 1783 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1784 ) 1785 return expression 1786 1787 if not bounds: 1788 return expression 1789 1790 for i, param in enumerate(params): 1791 bound = bounds[i] if i < len(bounds) else None 1792 if bound is None: 1793 continue 1794 1795 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1796 value = ( 1797 param_value.to_py() 1798 if isinstance(param_value, exp.Literal) and param_value.is_number 1799 else None 1800 ) 1801 if isinstance(value, (int, Decimal)) and value > bound: 1802 self.unsupported( 1803 f"{type_value.value} parameter {param_value.name} exceeds " 1804 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1805 ) 1806 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1807 1808 return expression 1809 1810 def datatype_sql(self, expression: exp.DataType) -> str: 1811 nested = "" 1812 values = "" 1813 1814 expr_nested = expression.args.get("nested") 1815 type_value = expression.this 1816 1817 if ( 1818 not expr_nested 1819 and isinstance(type_value, exp.DType) 1820 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1821 ): 1822 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1823 1824 interior = ( 1825 self.expressions( 1826 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1827 ) 1828 if expr_nested and self.pretty 1829 else self.expressions(expression, flat=True) 1830 ) 1831 1832 if type_value in self.UNSUPPORTED_TYPES: 1833 self.unsupported( 1834 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1835 ) 1836 1837 type_sql: t.Any = "" 1838 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1839 type_sql = self.sql(expression, "kind") 1840 elif type_value == exp.DType.CHARACTER_SET: 1841 char = f"CHAR({interior})" if interior else "CHAR" 1842 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1843 else: 1844 type_sql = ( 1845 self.TYPE_MAPPING.get(type_value, type_value.value) 1846 if isinstance(type_value, exp.DType) 1847 else type_value 1848 ) 1849 1850 if interior: 1851 if expr_nested: 1852 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1853 if expression.args.get("values") is not None: 1854 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1855 values = self.expressions(expression, key="values", flat=True) 1856 values = f"{delimiters[0]}{values}{delimiters[1]}" 1857 elif type_value == exp.DType.INTERVAL: 1858 nested = f" {interior}" 1859 else: 1860 nested = f"({interior})" 1861 1862 type_sql = f"{type_sql}{nested}{values}" 1863 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1864 exp.DType.TIMETZ, 1865 exp.DType.TIMESTAMPTZ, 1866 ): 1867 type_sql = f"{type_sql} WITH TIME ZONE" 1868 1869 collate = self.sql(expression, "collate") 1870 if collate: 1871 type_sql = f"{type_sql} COLLATE {collate}" 1872 1873 return type_sql 1874 1875 def directory_sql(self, expression: exp.Directory) -> str: 1876 local = "LOCAL " if expression.args.get("local") else "" 1877 row_format = self.sql(expression, "row_format") 1878 row_format = f" {row_format}" if row_format else "" 1879 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1880 1881 def delete_sql(self, expression: exp.Delete) -> str: 1882 hint = self.sql(expression, "hint") 1883 this = self.sql(expression, "this") 1884 this = f" FROM {this}" if this else "" 1885 using = self.expressions(expression, key="using") 1886 using = f" USING {using}" if using else "" 1887 cluster = self.sql(expression, "cluster") 1888 cluster = f" {cluster}" if cluster else "" 1889 where = self.sql(expression, "where") 1890 returning = self.sql(expression, "returning") 1891 order = self.sql(expression, "order") 1892 limit = self.sql(expression, "limit") 1893 tables = self.expressions(expression, key="tables") 1894 tables = f" {tables}" if tables else "" 1895 if self.RETURNING_END: 1896 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1897 else: 1898 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1899 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1900 1901 def drop_sql(self, expression: exp.Drop) -> str: 1902 tables = self.expressions(expression, key="tables", flat=True) 1903 expressions = self.expressions(expression, flat=True) 1904 expressions = f" ({expressions})" if expressions else "" 1905 kind = expression.args["kind"] 1906 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1907 iceberg = ( 1908 " ICEBERG" 1909 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1910 else "" 1911 ) 1912 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1913 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1914 on_cluster = self.sql(expression, "cluster") 1915 on_cluster = f" {on_cluster}" if on_cluster else "" 1916 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1917 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1918 cascade = " CASCADE" if expression.args.get("cascade") else "" 1919 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1920 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1921 purge = " PURGE" if expression.args.get("purge") else "" 1922 sync = " SYNC" if expression.args.get("sync") else "" 1923 force = " FORCE" if expression.args.get("force") else "" 1924 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1925 1926 def set_operation(self, expression: exp.SetOperation) -> str: 1927 op_type = type(expression) 1928 op_name = op_type.key.upper() 1929 1930 distinct = expression.args.get("distinct") 1931 if ( 1932 distinct is False 1933 and op_type in (exp.Except, exp.Intersect) 1934 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1935 ): 1936 self.unsupported(f"{op_name} ALL is not supported") 1937 1938 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1939 1940 if distinct is None: 1941 distinct = default_distinct 1942 if distinct is None: 1943 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1944 1945 if distinct is default_distinct: 1946 distinct_or_all = "" 1947 else: 1948 distinct_or_all = " DISTINCT" if distinct else " ALL" 1949 1950 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1951 side_kind = f"{side_kind} " if side_kind else "" 1952 1953 by_name = " BY NAME" if expression.args.get("by_name") else "" 1954 on = self.expressions(expression, key="on", flat=True) 1955 on = f" ON ({on})" if on else "" 1956 1957 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1958 1959 def set_operations(self, expression: exp.SetOperation) -> str: 1960 if not self.SET_OP_MODIFIERS: 1961 limit = expression.args.get("limit") 1962 order = expression.args.get("order") 1963 offset = expression.args.get("offset") 1964 1965 if limit or order or offset: 1966 select = self._move_ctes_to_top_level( 1967 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1968 ) 1969 1970 for arg in ("limit", "order", "offset"): 1971 if value := expression.args.get(arg): 1972 select.set(arg, value.pop()) 1973 return self.sql(select) 1974 1975 sqls: list[str] = [] 1976 stack: list[str | exp.Expr | int | None] = [expression] 1977 depth = 0 1978 open_parens = "" 1979 1980 while stack: 1981 node = stack.pop() 1982 1983 if isinstance(node, int): 1984 # Closes a plain grouping opened below, restoring its depth 1985 depth = node 1986 sqls[-1] += self.sep("") + self.indent(")", level=depth, pad=0) 1987 elif isinstance(node, exp.SetOperation): 1988 stack.append(node.expression) 1989 stack.append( 1990 self.maybe_comment( 1991 self.set_operation(node), comments=node.comments, separated=True 1992 ) 1993 ) 1994 left = node.this 1995 if ( 1996 not self.SET_OP_PARENTHESIZED_OPERANDS 1997 and not self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT 1998 ): 1999 # Left grouping is implicit, so skip the `SELECT * FROM (...)` wrapper: 2000 # it renames duplicate columns and deepens recursion. Inner CTEs and 2001 # modifiers still need the wrapper. 2002 wrappers: list[exp.Subquery] = [] 2003 inner = left 2004 while isinstance(inner, exp.Subquery) and inner.is_wrapper: 2005 wrappers.append(inner) 2006 inner = inner.this 2007 2008 if ( 2009 isinstance(inner, exp.SetOperation) 2010 and not inner.args.get("with_") 2011 and not any(inner.args.get(key) for key in exp.QUERY_MODIFIERS) 2012 ): 2013 for wrapper in wrappers: 2014 inner.add_comments(wrapper.pop_comments()) 2015 left = inner 2016 elif ( 2017 self.SET_OP_PARENTHESIZED_OPERANDS 2018 and isinstance(left, exp.Subquery) 2019 and left.is_wrapper 2020 and not left.comments 2021 and isinstance(left.this, exp.SetOperation) 2022 and not left.this.args.get("with_") 2023 and not any(left.this.args.get(key) for key in exp.QUERY_MODIFIERS) 2024 ): 2025 # Write a plain grouping's parentheses here rather than recursing through 2026 # subquery_sql: left-to-right chains nest one grouping per INTERSECT, and 2027 # long ones would exhaust the stack. The depth pushed here marks the close. 2028 open_parens += self.indent("(", level=depth, pad=0) + self.sep("") 2029 stack.append(depth) 2030 depth += 1 2031 left = left.this 2032 stack.append(left) 2033 else: 2034 if ( 2035 not self.SET_OP_LIMITS 2036 and isinstance(node, exp.Select) 2037 and node.args.get("limit") 2038 ): 2039 node = node.subquery(copy=False) 2040 if not self.SET_OP_PARENTHESIZED_OPERANDS: 2041 node = exp.select("*").from_(node, copy=False) 2042 elif ( 2043 not self.SET_OP_PARENTHESIZED_OPERANDS 2044 and isinstance(node, exp.Subquery) 2045 and isinstance(node.unnest(), exp.SetOperation) 2046 ): 2047 node = exp.select("*").from_(node, copy=False) 2048 sqls.append(open_parens + self.indent(self.sql(node), level=depth, pad=0)) 2049 open_parens = "" 2050 2051 this = self.sep().join(sqls) 2052 this = self.query_modifiers(expression, this) 2053 return self.prepend_ctes(expression, this) 2054 2055 def fetch_sql(self, expression: exp.Fetch) -> str: 2056 direction = expression.args.get("direction") 2057 direction = f" {direction}" if direction else "" 2058 count = self.sql(expression, "count") 2059 count = f" {count}" if count else "" 2060 limit_options = self.sql(expression, "limit_options") 2061 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 2062 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 2063 2064 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 2065 percent = " PERCENT" if expression.args.get("percent") else "" 2066 rows = " ROWS" if expression.args.get("rows") else "" 2067 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 2068 if not with_ties and rows: 2069 with_ties = " ONLY" 2070 return f"{percent}{rows}{with_ties}" 2071 2072 def filter_sql(self, expression: exp.Filter) -> str: 2073 this = self.sql(expression, "this") 2074 where = self.sql(expression, "expression").strip() 2075 return f"{this} FILTER({where})" 2076 2077 def per_sql(self, expression: exp.Per) -> str: 2078 keys = ", ".join( 2079 f"{self.sql(key, 'this')} {self.sql(key, 'with_fill')}" 2080 if isinstance(key, exp.Ordered) and key.args.get("with_fill") 2081 else self.sql(key) 2082 for key in expression.expressions 2083 ) 2084 return f"{self.sql(expression, 'this')} PER ({keys})" 2085 2086 def hint_sql(self, expression: exp.Hint) -> str: 2087 if not self.QUERY_HINTS: 2088 self.unsupported("Hints are not supported") 2089 return "" 2090 2091 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 2092 2093 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2094 using = self.sql(expression, "using") 2095 using = f" USING {using}" if using else "" 2096 columns = self.expressions(expression, key="columns", flat=True) 2097 columns = f"({columns})" if columns else "" 2098 partition_by = self.expressions(expression, key="partition_by", flat=True) 2099 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2100 where = self.sql(expression, "where") 2101 include = self.expressions(expression, key="include", flat=True) 2102 if include: 2103 include = f" INCLUDE ({include})" 2104 with_storage = self.expressions(expression, key="with_storage", flat=True) 2105 with_storage = f" WITH ({with_storage})" if with_storage else "" 2106 tablespace = self.sql(expression, "tablespace") 2107 if tablespace: 2108 prefix = "" if isinstance(expression.parent, exp.Index) else " USING INDEX" 2109 tablespace = f"{prefix} TABLESPACE {tablespace}" 2110 on = self.sql(expression, "on") 2111 on = f" ON {on}" if on else "" 2112 2113 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 2114 2115 def index_sql(self, expression: exp.Index) -> str: 2116 unique = "UNIQUE " if expression.args.get("unique") else "" 2117 primary = "PRIMARY " if expression.args.get("primary") else "" 2118 amp = "AMP " if expression.args.get("amp") else "" 2119 name = self.sql(expression, "this") 2120 name = f"{name} " if name else "" 2121 table = self.sql(expression, "table") 2122 table = f"{self.INDEX_ON} {table}" if table else "" 2123 2124 index = "INDEX " if not table else "" 2125 2126 params = self.sql(expression, "params") 2127 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 2128 2129 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2130 this = expression.this 2131 if this and this.is_string: 2132 resolved = maybe_parse(this.name).sql(self.dialect) 2133 if "expressions" in expression.args: 2134 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2135 # We can't safely emit the call to other dialects since name/arg semantics may differ 2136 self.unsupported( 2137 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2138 ) 2139 return resolved 2140 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2141 return self.func("IDENTIFIER", this) 2142 2143 def identifier_sql(self, expression: exp.Identifier) -> str: 2144 text = expression.name 2145 lower = text.lower() 2146 quoted = expression.quoted 2147 text = lower if self.normalize and not quoted else text 2148 if self._identifier_escaped_sequences: 2149 for char, escaped in self._identifier_escaped_sequences.items(): 2150 text = text.replace(char, escaped) 2151 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2152 if ( 2153 quoted 2154 or self.dialect.can_quote(expression, self.identify) 2155 or lower in self.RESERVED_KEYWORDS 2156 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2157 ): 2158 text = ( 2159 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2160 ) 2161 return text 2162 2163 def hex_sql(self, expression: exp.Hex) -> str: 2164 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2165 if self.dialect.HEX_LOWERCASE: 2166 text = self.func("LOWER", text) 2167 2168 return text 2169 2170 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2171 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2172 if not self.dialect.HEX_LOWERCASE: 2173 text = self.func("LOWER", text) 2174 return text 2175 2176 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2177 input_format = self.sql(expression, "input_format") 2178 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2179 output_format = self.sql(expression, "output_format") 2180 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2181 return self.sep().join((input_format, output_format)) 2182 2183 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2184 string = self.sql(exp.Literal.string(expression.name)) 2185 return f"{prefix}{string}" 2186 2187 def partition_sql(self, expression: exp.Partition) -> str: 2188 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2189 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2190 2191 def properties_sql(self, expression: exp.Properties) -> str: 2192 root_properties = [] 2193 with_properties = [] 2194 2195 for p in expression.expressions: 2196 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2197 if p_loc == exp.Properties.Location.POST_WITH: 2198 with_properties.append(p) 2199 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2200 root_properties.append(p) 2201 2202 root_props_ast = exp.Properties(expressions=root_properties) 2203 root_props_ast.parent = expression.parent 2204 2205 with_props_ast = exp.Properties(expressions=with_properties) 2206 with_props_ast.parent = expression.parent 2207 2208 root_props = self.root_properties(root_props_ast) 2209 with_props = self.with_properties(with_props_ast) 2210 2211 if root_props and with_props and not self.pretty: 2212 with_props = " " + with_props 2213 2214 return root_props + with_props 2215 2216 def root_properties(self, properties: exp.Properties) -> str: 2217 if properties.expressions: 2218 return self.expressions(properties, indent=False, sep=" ") 2219 return "" 2220 2221 def properties( 2222 self, 2223 properties: exp.Properties, 2224 prefix: str = "", 2225 sep: str = ", ", 2226 suffix: str = "", 2227 wrapped: bool = True, 2228 ) -> str: 2229 if properties.expressions: 2230 expressions = self.expressions(properties, sep=sep, indent=False) 2231 if expressions: 2232 expressions = self.wrap(expressions) if wrapped else expressions 2233 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2234 return "" 2235 2236 def with_properties(self, properties: exp.Properties) -> str: 2237 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2238 2239 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2240 properties_locs = defaultdict(list) 2241 for p in properties.expressions: 2242 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2243 if p_loc != exp.Properties.Location.UNSUPPORTED: 2244 properties_locs[p_loc].append(p) 2245 else: 2246 self.unsupported(f"Unsupported property {p.key}") 2247 2248 return properties_locs 2249 2250 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2251 if isinstance(expression.this, exp.Dot): 2252 return self.sql(expression, "this") 2253 return self.sql(exp.Literal.string(expression.name)) if string_key else expression.name 2254 2255 def property_sql(self, expression: exp.Property) -> str: 2256 property_cls = expression.__class__ 2257 if property_cls == exp.Property: 2258 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2259 2260 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2261 if not property_name: 2262 self.unsupported(f"Unsupported property {expression.key}") 2263 return "" 2264 2265 return f"{property_name}={self.sql(expression, 'this')}" 2266 2267 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2268 return f"UUID {self.sql(expression, 'this')}" 2269 2270 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2271 if self.SUPPORTS_CREATE_TABLE_LIKE: 2272 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2273 options = f" {options}" if options else "" 2274 2275 like = f"LIKE {self.sql(expression, 'this')}{options}" 2276 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2277 like = f"({like})" 2278 2279 return like 2280 2281 if expression.expressions: 2282 self.unsupported("Transpilation of LIKE property options is unsupported") 2283 2284 select = exp.select("*").from_(expression.this).limit(0) 2285 return f"AS {self.sql(select)}" 2286 2287 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2288 no = "NO " if expression.args.get("no") else "" 2289 protection = " PROTECTION" if expression.args.get("protection") else "" 2290 return f"{no}FALLBACK{protection}" 2291 2292 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2293 no = "NO " if expression.args.get("no") else "" 2294 local = expression.args.get("local") 2295 local = f"{local} " if local else "" 2296 dual = "DUAL " if expression.args.get("dual") else "" 2297 before = "BEFORE " if expression.args.get("before") else "" 2298 after = "AFTER " if expression.args.get("after") else "" 2299 return f"{no}{local}{dual}{before}{after}JOURNAL" 2300 2301 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2302 freespace = self.sql(expression, "this") 2303 percent = " PERCENT" if expression.args.get("percent") else "" 2304 return f"FREESPACE={freespace}{percent}" 2305 2306 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2307 if expression.args.get("default"): 2308 property = "DEFAULT" 2309 elif expression.args.get("on"): 2310 property = "ON" 2311 else: 2312 property = "OFF" 2313 return f"CHECKSUM={property}" 2314 2315 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2316 if expression.args.get("no"): 2317 return "NO MERGEBLOCKRATIO" 2318 if expression.args.get("default"): 2319 return "DEFAULT MERGEBLOCKRATIO" 2320 2321 percent = " PERCENT" if expression.args.get("percent") else "" 2322 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2323 2324 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2325 expressions = self.expressions(expression, flat=True) 2326 expressions = f"({expressions})" if expressions else "" 2327 return f"USING {self.sql(expression, 'this')}{expressions}" 2328 2329 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2330 default = expression.args.get("default") 2331 minimum = expression.args.get("minimum") 2332 maximum = expression.args.get("maximum") 2333 if default or minimum or maximum: 2334 if default: 2335 prop = "DEFAULT" 2336 elif minimum: 2337 prop = "MINIMUM" 2338 else: 2339 prop = "MAXIMUM" 2340 return f"{prop} DATABLOCKSIZE" 2341 units = expression.args.get("units") 2342 units = f" {units}" if units else "" 2343 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2344 2345 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2346 autotemp = expression.args.get("autotemp") 2347 always = expression.args.get("always") 2348 default = expression.args.get("default") 2349 manual = expression.args.get("manual") 2350 never = expression.args.get("never") 2351 2352 if autotemp is not None: 2353 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2354 elif always: 2355 prop = "ALWAYS" 2356 elif default: 2357 prop = "DEFAULT" 2358 elif manual: 2359 prop = "MANUAL" 2360 elif never: 2361 prop = "NEVER" 2362 return f"BLOCKCOMPRESSION={prop}" 2363 2364 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2365 no = expression.args.get("no") 2366 no = " NO" if no else "" 2367 concurrent = expression.args.get("concurrent") 2368 concurrent = " CONCURRENT" if concurrent else "" 2369 target = self.sql(expression, "target") 2370 target = f" {target}" if target else "" 2371 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2372 2373 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2374 if isinstance(expression.this, list): 2375 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2376 if expression.this: 2377 modulus = self.sql(expression, "this") 2378 remainder = self.sql(expression, "expression") 2379 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2380 2381 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2382 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2383 return f"FROM ({from_expressions}) TO ({to_expressions})" 2384 2385 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2386 this = self.sql(expression, "this") 2387 2388 for_values_or_default = expression.expression 2389 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2390 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2391 else: 2392 for_values_or_default = " DEFAULT" 2393 2394 return f"PARTITION OF {this}{for_values_or_default}" 2395 2396 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2397 kind = expression.args.get("kind") 2398 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2399 for_or_in = expression.args.get("for_or_in") 2400 for_or_in = f" {for_or_in}" if for_or_in else "" 2401 lock_type = expression.args.get("lock_type") 2402 override = " OVERRIDE" if expression.args.get("override") else "" 2403 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2404 2405 def lockingstatement_sql(self, expression: exp.LockingStatement) -> str: 2406 self.unsupported("LOCKING request modifier is not supported") 2407 return self.sql(expression, "expression") 2408 2409 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2410 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2411 statistics = expression.args.get("statistics") 2412 statistics_sql = "" 2413 if statistics is not None: 2414 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2415 return f"{data_sql}{statistics_sql}" 2416 2417 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2418 this = self.sql(expression, "this") 2419 this = f"HISTORY_TABLE={this}" if this else "" 2420 data_consistency: str | None = self.sql(expression, "data_consistency") 2421 data_consistency = ( 2422 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2423 ) 2424 retention_period: str | None = self.sql(expression, "retention_period") 2425 retention_period = ( 2426 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2427 ) 2428 2429 if this: 2430 on_sql = self.func("ON", this, data_consistency, retention_period) 2431 else: 2432 on_sql = "ON" if expression.args.get("on") else "OFF" 2433 2434 sql = f"SYSTEM_VERSIONING={on_sql}" 2435 2436 return f"WITH({sql})" if expression.args.get("with_") else sql 2437 2438 def insert_sql(self, expression: exp.Insert) -> str: 2439 hint = self.sql(expression, "hint") 2440 overwrite = expression.args.get("overwrite") 2441 2442 if isinstance(expression.this, exp.Directory): 2443 this = " OVERWRITE" if overwrite else " INTO" 2444 else: 2445 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2446 2447 stored = self.sql(expression, "stored") 2448 stored = f" {stored}" if stored else "" 2449 alternative = expression.args.get("alternative") 2450 alternative = f" OR {alternative}" if alternative else "" 2451 ignore = " IGNORE" if expression.args.get("ignore") else "" 2452 is_function = expression.args.get("is_function") 2453 if is_function: 2454 this = f"{this} FUNCTION" 2455 this = f"{this} {self.sql(expression, 'this')}" 2456 2457 exists = " IF EXISTS" if expression.args.get("exists") else "" 2458 where = self.sql(expression, "where") 2459 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2460 using = self.expressions(expression, key="using", flat=True) 2461 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2462 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2463 on_conflict = self.sql(expression, "conflict") 2464 on_conflict = f" {on_conflict}" if on_conflict else "" 2465 by_name = " BY NAME" if expression.args.get("by_name") else "" 2466 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2467 returning = self.sql(expression, "returning") 2468 2469 if self.RETURNING_END: 2470 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2471 else: 2472 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2473 2474 partition_by = self.sql(expression, "partition") 2475 partition_by = f" {partition_by}" if partition_by else "" 2476 settings = self.sql(expression, "settings") 2477 settings = f" {settings}" if settings else "" 2478 2479 source = self.sql(expression, "source") 2480 source = f"TABLE {source}" if source else "" 2481 2482 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2483 return self.prepend_ctes(expression, sql) 2484 2485 def introducer_sql(self, expression: exp.Introducer) -> str: 2486 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2487 2488 def kill_sql(self, expression: exp.Kill) -> str: 2489 kind = self.sql(expression, "kind") 2490 kind = f" {kind}" if kind else "" 2491 this = self.sql(expression, "this") 2492 this = f" {this}" if this else "" 2493 return f"KILL{kind}{this}" 2494 2495 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2496 return expression.name 2497 2498 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2499 return expression.name 2500 2501 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2502 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2503 2504 constraint = self.sql(expression, "constraint") 2505 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2506 2507 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2508 if conflict_keys: 2509 conflict_keys = f"({conflict_keys})" 2510 2511 index_predicate = self.sql(expression, "index_predicate") 2512 conflict_keys = f"{conflict_keys}{index_predicate} " 2513 2514 action = self.sql(expression, "action") 2515 2516 expressions = self.expressions(expression, flat=True) 2517 if expressions: 2518 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2519 expressions = f" {set_keyword}{expressions}" 2520 2521 where = self.sql(expression, "where") 2522 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2523 2524 def returning_sql(self, expression: exp.Returning) -> str: 2525 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2526 2527 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2528 fields = self.sql(expression, "fields") 2529 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2530 escaped = self.sql(expression, "escaped") 2531 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2532 items = self.sql(expression, "collection_items") 2533 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2534 keys = self.sql(expression, "map_keys") 2535 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2536 lines = self.sql(expression, "lines") 2537 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2538 null = self.sql(expression, "null") 2539 null = f" NULL DEFINED AS {null}" if null else "" 2540 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2541 2542 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2543 return f"WITH ({self.expressions(expression, flat=True)})" 2544 2545 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2546 this = f"{self.sql(expression, 'this')} INDEX" 2547 target = self.sql(expression, "target") 2548 target = f" FOR {target}" if target else "" 2549 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2550 2551 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2552 this = self.sql(expression, "this") 2553 kind = self.sql(expression, "kind") 2554 expr = self.sql(expression, "expression") 2555 return f"{this} ({kind} => {expr})" 2556 2557 def table_parts(self, expression: exp.Table) -> str: 2558 return ".".join( 2559 self.sql(part) 2560 for part in ( 2561 expression.args.get("catalog"), 2562 expression.args.get("db"), 2563 expression.args.get("this"), 2564 ) 2565 if part is not None 2566 ) 2567 2568 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2569 table = self.table_parts(expression) 2570 only = "ONLY " if expression.args.get("only") else "" 2571 partition = self.sql(expression, "partition") 2572 partition = f" {partition}" if partition else "" 2573 version = self.sql(expression, "version") 2574 version = f" {version}" if version else "" 2575 alias = self.sql(expression, "alias") 2576 alias = f"{sep}{alias}" if alias else "" 2577 2578 sample = self.sql(expression, "sample") 2579 post_alias = "" 2580 pre_alias = "" 2581 2582 if self.dialect.ALIAS_POST_TABLESAMPLE: 2583 pre_alias = sample 2584 else: 2585 post_alias = sample 2586 2587 if self.dialect.ALIAS_POST_VERSION: 2588 pre_alias = f"{pre_alias}{version}" 2589 else: 2590 post_alias = f"{post_alias}{version}" 2591 2592 hints = self.expressions(expression, key="hints", sep=" ") 2593 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2594 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2595 joins = self.indent( 2596 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2597 ) 2598 laterals = self.expressions(expression, key="laterals", sep="") 2599 2600 file_format = self.sql(expression, "format") 2601 pattern = self.sql(expression, "pattern") 2602 if file_format: 2603 pattern = f", PATTERN => {pattern}" if pattern else "" 2604 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2605 elif pattern: 2606 file_format = f" (PATTERN => {pattern})" 2607 2608 ordinality = expression.args.get("ordinality") or "" 2609 if ordinality: 2610 ordinality = f" WITH ORDINALITY{alias}" 2611 alias = "" 2612 2613 when = self.sql(expression, "when") 2614 if when: 2615 if self.HISTORICAL_DATA_POST_ALIAS: 2616 alias = f"{alias} {when}" 2617 else: 2618 table = f"{table} {when}" 2619 2620 changes = self.sql(expression, "changes") 2621 changes = f" {changes}" if changes else "" 2622 2623 rows_from = self.expressions(expression, key="rows_from") 2624 if rows_from: 2625 table = f"ROWS FROM {self.wrap(rows_from)}" 2626 2627 indexed = expression.args.get("indexed") 2628 if indexed is not None: 2629 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2630 else: 2631 indexed = "" 2632 2633 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2634 2635 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2636 table = self.func("TABLE", expression.this) 2637 alias = self.sql(expression, "alias") 2638 alias = f" AS {alias}" if alias else "" 2639 sample = self.sql(expression, "sample") 2640 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2641 joins = self.indent( 2642 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2643 ) 2644 return f"{table}{alias}{pivots}{sample}{joins}" 2645 2646 def tablesample_sql( 2647 self, 2648 expression: exp.TableSample, 2649 tablesample_keyword: str | None = None, 2650 ) -> str: 2651 if expression.expressions: 2652 self.unsupported("Multiple sample sets are not supported") 2653 return "" 2654 2655 method = self.sql(expression, "method") 2656 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2657 numerator = self.sql(expression, "bucket_numerator") 2658 denominator = self.sql(expression, "bucket_denominator") 2659 field = self.sql(expression, "bucket_field") 2660 field = f" ON {field}" if field else "" 2661 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2662 seed = self.sql(expression, "seed") 2663 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2664 2665 size = self.sql(expression, "size") 2666 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2667 size = f"{size} ROWS" 2668 2669 percent = self.sql(expression, "percent") 2670 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2671 percent = f"{percent} PERCENT" 2672 2673 expr = f"{bucket}{percent}{size}" 2674 if self.TABLESAMPLE_REQUIRES_PARENS: 2675 expr = f"({expr})" 2676 2677 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2678 2679 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2680 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2681 # the stored column name differs from the target dialect's natural output. 2682 columns = expression.args.get("columns") 2683 if not columns or len(expression.fields) != 1: 2684 return None 2685 2686 args = expression.args 2687 parser_cls = self.dialect.parser_class 2688 2689 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2690 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2691 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2692 2693 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2694 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2695 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2696 2697 if ( 2698 src_identify_pivot_strings == tgt_identify_pivot_strings 2699 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2700 and src_pivot_column_naming == tgt_pivot_column_naming 2701 ): 2702 return None 2703 2704 in_exprs = expression.fields[0].expressions 2705 step = len(columns) // len(in_exprs) 2706 2707 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2708 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2709 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2710 first_stored = columns[0].name 2711 2712 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2713 if not first_stored.startswith(first_base): 2714 return None 2715 2716 suffix = first_stored[len(first_base) :] 2717 2718 # Whether the target dialect would append an agg-name suffix for this pivot. 2719 # Spark single-agg uniquely drops the agg alias entirely. 2720 target_has_suffix = ( 2721 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2722 ) and any(a.alias for a in expression.expressions) 2723 source_has_suffix = suffix != "" 2724 2725 new_exprs: list[exp.Expression] = [] 2726 modified = False 2727 for val_idx, e in enumerate(in_exprs): 2728 if isinstance(e, exp.PivotAlias): 2729 new_exprs.append(e) 2730 continue 2731 2732 i = val_idx * step 2733 stored_full = columns[i].name 2734 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2735 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2736 2737 # Source had a suffix, but target won't apply one 2738 if source_has_suffix and not target_has_suffix: 2739 new_exprs.append( 2740 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2741 ) 2742 modified = True 2743 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2744 elif stored_value != target_value: 2745 new_exprs.append( 2746 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2747 ) 2748 modified = True 2749 else: 2750 new_exprs.append(e) 2751 2752 return new_exprs if modified else None 2753 2754 def pivot_sql(self, expression: exp.Pivot) -> str: 2755 expressions = self.expressions(expression, flat=True) 2756 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2757 2758 group = self.sql(expression, "group") 2759 2760 if expression.this: 2761 this = self.sql(expression, "this") 2762 if not expressions: 2763 sql = f"UNPIVOT {this}" 2764 else: 2765 on = f"{self.seg('ON')} {expressions}" 2766 into = self.sql(expression, "into") 2767 into = f"{self.seg('INTO')} {into}" if into else "" 2768 using = self.expressions(expression, key="using", flat=True) 2769 using = f"{self.seg('USING')} {using}" if using else "" 2770 sql = f"{direction} {this}{on}{into}{using}{group}" 2771 return self.prepend_ctes(expression, sql) 2772 2773 if not expression.unpivot: 2774 # Wrap IN-list values with explicit aliases where the target dialect would differ 2775 new_field_exprs = self._pivot_in_value_aliases(expression) 2776 if new_field_exprs is not None: 2777 expression.fields[0].set("expressions", new_field_exprs) 2778 2779 alias = self.sql(expression, "alias") 2780 if alias: 2781 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2782 2783 fields = self.expressions( 2784 expression, 2785 "fields", 2786 sep=" ", 2787 dynamic=True, 2788 new_line=True, 2789 skip_first=True, 2790 skip_last=True, 2791 ) 2792 2793 include_nulls = expression.args.get("include_nulls") 2794 if include_nulls is not None: 2795 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2796 else: 2797 nulls = "" 2798 2799 default_on_null = self.sql(expression, "default_on_null") 2800 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2801 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2802 return self.prepend_ctes(expression, sql) 2803 2804 def version_sql(self, expression: exp.Version) -> str: 2805 this = f"FOR {expression.name}" 2806 kind = expression.text("kind") 2807 expr = self.sql(expression, "expression") 2808 return f"{this} {kind} {expr}" 2809 2810 def tuple_sql(self, expression: exp.Tuple) -> str: 2811 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2812 2813 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2814 """ 2815 Returns (join_sql, from_sql) for UPDATE statements. 2816 - join_sql: placed after UPDATE table, before SET 2817 - from_sql: placed after SET clause (standard position) 2818 Dialects like MySQL need to convert FROM to JOIN syntax. 2819 """ 2820 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2821 return ("", self.sql(expression, "from_")) 2822 2823 # Qualify unqualified columns in SET clause with the target table 2824 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2825 target_table = expression.this 2826 if isinstance(target_table, exp.Table): 2827 target_name = exp.to_identifier(target_table.alias_or_name) 2828 for eq in expression.expressions: 2829 col = eq.this 2830 if isinstance(col, exp.Column) and not col.table: 2831 col.set("table", target_name) 2832 2833 table = from_expr.this 2834 if nested_joins := table.args.get("joins", []): 2835 table.set("joins", None) 2836 2837 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2838 for nested in nested_joins: 2839 if not nested.args.get("on") and not nested.args.get("using"): 2840 nested.set("on", exp.true()) 2841 join_sql += self.sql(nested) 2842 2843 return (join_sql, "") 2844 2845 def update_sql(self, expression: exp.Update) -> str: 2846 hint = self.sql(expression, "hint") 2847 this = self.sql(expression, "this") 2848 join_sql, from_sql = self._update_from_joins_sql(expression) 2849 set_sql = self.expressions(expression, flat=True) 2850 where_sql = self.sql(expression, "where") 2851 returning = self.sql(expression, "returning") 2852 order = self.sql(expression, "order") 2853 limit = self.sql(expression, "limit") 2854 if self.RETURNING_END: 2855 expression_sql = f"{from_sql}{where_sql}{returning}" 2856 else: 2857 expression_sql = f"{returning}{from_sql}{where_sql}" 2858 options = self.expressions(expression, key="options") 2859 options = f" OPTION({options})" if options else "" 2860 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2861 return self.prepend_ctes(expression, sql) 2862 2863 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2864 values_as_table = values_as_table and self.VALUES_AS_TABLE 2865 2866 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2867 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2868 args = self.expressions(expression) 2869 alias = self.sql(expression, "alias") 2870 values = f"VALUES{self.seg('')}{args}" 2871 values = ( 2872 f"({values})" 2873 if self.WRAP_DERIVED_VALUES 2874 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2875 else values 2876 ) 2877 values = self.query_modifiers(expression, values) 2878 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2879 return f"{values} AS {alias}{pivots}" if alias else f"{values}{pivots}" 2880 2881 # Converts `VALUES...` expression into a series of select unions. 2882 alias_node = expression.args.get("alias") 2883 column_names = alias_node and alias_node.columns 2884 2885 selects: list[exp.Query] = [] 2886 2887 for i, tup in enumerate(expression.expressions): 2888 row = tup.expressions 2889 2890 if i == 0 and column_names: 2891 row = [ 2892 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2893 ] 2894 2895 selects.append(exp.Select(expressions=row)) 2896 2897 if self.pretty: 2898 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2899 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2900 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2901 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2902 for key in ("order", "limit", "offset"): 2903 if value := expression.args.get(key): 2904 query.set(key, value.pop()) 2905 subquery = query.subquery(alias_node and alias_node.this, copy=False) 2906 subquery.set("pivots", expression.args.pop("pivots", None)) 2907 return self.subquery_sql(subquery) 2908 2909 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2910 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2911 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2912 return f"({self.query_modifiers(expression, unions)}){alias}{pivots}" 2913 2914 def var_sql(self, expression: exp.Var) -> str: 2915 return self.sql(expression, "this") 2916 2917 @unsupported_args("expressions") 2918 def into_sql(self, expression: exp.Into) -> str: 2919 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2920 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2921 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2922 2923 def from_sql(self, expression: exp.From) -> str: 2924 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2925 2926 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2927 grouping_sets = self.expressions(expression, indent=False) 2928 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2929 2930 def rollup_sql(self, expression: exp.Rollup) -> str: 2931 expressions = self.expressions(expression, indent=False) 2932 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2933 2934 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2935 this = self.sql(expression, "this") 2936 2937 columns = self.expressions(expression, flat=True) 2938 2939 from_sql = self.sql(expression, "from_index") 2940 from_sql = f" FROM {from_sql}" if from_sql else "" 2941 2942 properties = expression.args.get("properties") 2943 properties_sql = ( 2944 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2945 ) 2946 2947 return f"{this}({columns}){from_sql}{properties_sql}" 2948 2949 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2950 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2951 2952 def cube_sql(self, expression: exp.Cube) -> str: 2953 expressions = self.expressions(expression, indent=False) 2954 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2955 2956 def group_sql(self, expression: exp.Group) -> str: 2957 group_by_all = expression.args.get("all") 2958 if group_by_all is True: 2959 modifier = " ALL" 2960 elif group_by_all is False: 2961 modifier = " DISTINCT" 2962 else: 2963 modifier = "" 2964 2965 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2966 2967 grouping_sets = self.expressions(expression, key="grouping_sets") 2968 cube = self.expressions(expression, key="cube") 2969 rollup = self.expressions(expression, key="rollup") 2970 2971 groupings = csv( 2972 self.seg(grouping_sets) if grouping_sets else "", 2973 self.seg(cube) if cube else "", 2974 self.seg(rollup) if rollup else "", 2975 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2976 sep=self.GROUPINGS_SEP, 2977 ) 2978 2979 if ( 2980 expression.expressions 2981 and groupings 2982 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2983 ): 2984 add_separator = True 2985 2986 if grouping_sets: 2987 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2988 add_separator = False 2989 else: 2990 self.unsupported( 2991 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2992 ) 2993 2994 if add_separator: 2995 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2996 2997 return f"{group_by}{groupings}" 2998 2999 def having_sql(self, expression: exp.Having) -> str: 3000 this = self.indent(self.sql(expression, "this")) 3001 return f"{self.seg('HAVING')}{self.sep()}{this}" 3002 3003 def connectto_sql(self, expression: exp.ConnectTo) -> str: 3004 return f"CONNECT {self.sql(expression, 'this')}" 3005 3006 def disconnect_sql(self, expression: exp.Disconnect) -> str: 3007 return "DISCONNECT" 3008 3009 def connect_sql(self, expression: exp.Connect) -> str: 3010 start = self.sql(expression, "start") 3011 start = self.seg(f"START WITH {start}") if start else "" 3012 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 3013 connect = self.sql(expression, "connect") 3014 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 3015 return start + connect 3016 3017 def prior_sql(self, expression: exp.Prior) -> str: 3018 return f"PRIOR {self.sql(expression, 'this')}" 3019 3020 def join_sql(self, expression: exp.Join, join_op: str | None = None) -> str: 3021 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 3022 side = None 3023 else: 3024 side = expression.side 3025 3026 op_sql = " ".join( 3027 op 3028 for op in ( 3029 expression.method, 3030 "GLOBAL" if expression.args.get("global_") else None, 3031 side, 3032 expression.kind, 3033 expression.hint if self.JOIN_HINTS else None, 3034 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 3035 ) 3036 if op 3037 ) 3038 match_cond = self.sql(expression, "match_condition") 3039 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 3040 nearest = self.sql(expression, "nearest") 3041 nearest = f" {nearest}" if nearest else "" 3042 on_sql = self.sql(expression, "on") 3043 using = expression.args.get("using") 3044 3045 if not on_sql and using: 3046 on_sql = csv(*(self.sql(column) for column in using)) 3047 3048 this = expression.this 3049 this_sql = self.sql(this) 3050 3051 exprs = self.expressions(expression) 3052 if exprs: 3053 this_sql = f"{this_sql},{self.seg(exprs)}" 3054 3055 if on_sql: 3056 on_sql = self.indent(on_sql, skip_first=True) 3057 space = self.seg(" " * self.pad) if self.pretty else " " 3058 if using: 3059 on_sql = f"{space}USING ({on_sql})" 3060 else: 3061 on_sql = f"{space}ON {on_sql}" 3062 elif not op_sql and not nearest: 3063 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 3064 return f" {this_sql}" 3065 3066 return f", {this_sql}" 3067 3068 if join_op: 3069 op_sql = join_op 3070 elif op_sql != "STRAIGHT_JOIN": 3071 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 3072 3073 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3074 return f"{self.seg(op_sql)} {this_sql}{match_cond}{nearest}{on_sql}{pivots}" 3075 3076 def nearest_sql(self, expression: exp.Nearest) -> str: 3077 if not self.SUPPORTS_NEAREST_JOIN: 3078 self.unsupported("NEAREST joins are not supported") 3079 3080 approximate = "APPROX " if expression.args.get("approximate") else "" 3081 metric = "SIMILARITY" if expression.args.get("similarity") else "DISTANCE" 3082 return f"{approximate}NEAREST {self.sql(expression, 'count')} BY {metric} {self.sql(expression, 'this')}" 3083 3084 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 3085 args = self.expressions(expression, flat=True) 3086 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 3087 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 3088 3089 def lateral_op(self, expression: exp.Lateral) -> str: 3090 cross_apply = expression.args.get("cross_apply") 3091 3092 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 3093 if cross_apply is True: 3094 op = "INNER JOIN " 3095 elif cross_apply is False: 3096 op = "LEFT JOIN " 3097 else: 3098 op = "" 3099 3100 return f"{op}LATERAL" 3101 3102 def lateral_sql(self, expression: exp.Lateral) -> str: 3103 this = self.sql(expression, "this") 3104 3105 if expression.args.get("view"): 3106 alias = expression.args["alias"] 3107 columns = self.expressions(alias, key="columns", flat=True) 3108 table = f" {alias.name}" if alias.name else "" 3109 columns = f" AS {columns}" if columns else "" 3110 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 3111 return f"{op_sql}{self.sep()}{this}{table}{columns}" 3112 3113 table_alias = expression.args.get("alias") 3114 offset = expression.this.args.get("offset") 3115 3116 if ( 3117 self.UNNEST_WITH_ORDINALITY 3118 and table_alias 3119 and isinstance(expression.this, exp.Unnest) 3120 and isinstance(offset, exp.Identifier) 3121 ): 3122 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3123 table_alias = table_alias.copy() 3124 table_alias.append("columns", offset.copy()) 3125 3126 alias = self.sql(table_alias) 3127 alias = f" AS {alias}" if alias else "" 3128 3129 ordinality = expression.args.get("ordinality") or "" 3130 if ordinality: 3131 ordinality = f" WITH ORDINALITY{alias}" 3132 alias = "" 3133 3134 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 3135 3136 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3137 this = self.sql(expression, "this") 3138 3139 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3140 return this 3141 3142 args = [ 3143 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3144 for e in (expression.args.get(k) for k in ("offset", "expression")) 3145 if e 3146 ] 3147 3148 args_sql = ", ".join(self.sql(e) for e in args) 3149 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3150 expressions = self.expressions(expression, flat=True) 3151 limit_options = self.sql(expression, "limit_options") 3152 expressions = f" BY {expressions}" if expressions else "" 3153 3154 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 3155 3156 def offset_sql(self, expression: exp.Offset) -> str: 3157 this = self.sql(expression, "this") 3158 value = expression.expression 3159 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3160 expressions = self.expressions(expression, flat=True) 3161 expressions = f" BY {expressions}" if expressions else "" 3162 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 3163 3164 def setitem_sql(self, expression: exp.SetItem) -> str: 3165 kind = self.sql(expression, "kind") 3166 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3167 kind = "" 3168 else: 3169 kind = f"{kind} " if kind else "" 3170 this = self.sql(expression, "this") 3171 expressions = self.expressions(expression) 3172 collate = self.sql(expression, "collate") 3173 collate = f" COLLATE {collate}" if collate else "" 3174 global_ = "GLOBAL " if expression.args.get("global_") else "" 3175 return f"{global_}{kind}{this}{expressions}{collate}" 3176 3177 def set_sql(self, expression: exp.Set) -> str: 3178 expressions = f" {self.expressions(expression, flat=True)}" 3179 tag = " TAG" if expression.args.get("tag") else "" 3180 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 3181 3182 def queryband_sql(self, expression: exp.QueryBand) -> str: 3183 this = self.sql(expression, "this") 3184 update = " UPDATE" if expression.args.get("update") else "" 3185 scope = self.sql(expression, "scope") 3186 scope = f" FOR {scope}" if scope else "" 3187 3188 return f"QUERY_BAND = {this}{update}{scope}" 3189 3190 def pragma_sql(self, expression: exp.Pragma) -> str: 3191 return f"PRAGMA {self.sql(expression, 'this')}" 3192 3193 def lock_sql(self, expression: exp.Lock) -> str: 3194 if not self.LOCKING_READS_SUPPORTED: 3195 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3196 return "" 3197 3198 update = expression.args["update"] 3199 key = expression.args.get("key") 3200 if update: 3201 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3202 else: 3203 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3204 expressions = self.expressions(expression, flat=True) 3205 expressions = f" OF {expressions}" if expressions else "" 3206 wait = expression.args.get("wait") 3207 3208 if wait is not None: 3209 if isinstance(wait, exp.Literal): 3210 wait = f" WAIT {self.sql(wait)}" 3211 else: 3212 wait = " NOWAIT" if wait else " SKIP LOCKED" 3213 3214 return f"{lock_type}{expressions}{wait or ''}" 3215 3216 def literal_sql(self, expression: exp.Literal) -> str: 3217 text = expression.this or "" 3218 if expression.is_string: 3219 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 3220 return text 3221 3222 def escape_str( 3223 self, 3224 text: str, 3225 escape_backslash: bool = True, 3226 delimiter: str | None = None, 3227 escaped_delimiter: str | None = None, 3228 is_byte_string: bool = False, 3229 is_bytes: bool = False, 3230 ) -> str: 3231 if is_byte_string: 3232 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3233 else: 3234 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3235 3236 if supports_escape_sequences: 3237 text = "".join( 3238 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3239 for ch in text 3240 ) 3241 3242 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3243 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3244 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3245 3246 delimiter = delimiter or self.dialect.QUOTE_END 3247 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3248 3249 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3250 3251 def loaddata_sql(self, expression: exp.LoadData) -> str: 3252 is_overwrite = expression.args.get("overwrite") 3253 overwrite = " OVERWRITE" if is_overwrite else "" 3254 this = self.sql(expression, "this") 3255 3256 files = expression.args.get("files") 3257 if files: 3258 files_sql = self.expressions(files, flat=True) 3259 files_sql = f"FILES{self.wrap(files_sql)}" 3260 if is_overwrite: 3261 this = f" {this}" 3262 elif expression.args.get("temp"): 3263 this = f" INTO TEMP TABLE {this}" 3264 else: 3265 this = f" INTO TABLE {this}" 3266 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3267 3268 local = " LOCAL" if expression.args.get("local") else "" 3269 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3270 this = f" INTO TABLE {this}" 3271 partition = self.sql(expression, "partition") 3272 partition = f" {partition}" if partition else "" 3273 input_format = self.sql(expression, "input_format") 3274 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3275 serde = self.sql(expression, "serde") 3276 serde = f" SERDE {serde}" if serde else "" 3277 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3278 3279 def null_sql(self, *_) -> str: 3280 return "NULL" 3281 3282 def boolean_sql(self, expression: exp.Boolean) -> str: 3283 return "TRUE" if expression.this else "FALSE" 3284 3285 def booland_sql(self, expression: exp.Booland) -> str: 3286 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3287 3288 def boolor_sql(self, expression: exp.Boolor) -> str: 3289 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3290 3291 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3292 this = self.sql(expression, "this") 3293 this = f"{this} " if this else this 3294 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3295 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3296 3297 def withfill_sql(self, expression: exp.WithFill) -> str: 3298 from_sql = self.sql(expression, "from_") 3299 from_sql = f" FROM {from_sql}" if from_sql else "" 3300 to_sql = self.sql(expression, "to") 3301 to_sql = f" TO {to_sql}" if to_sql else "" 3302 step_sql = self.sql(expression, "step") 3303 step_sql = f" STEP {step_sql}" if step_sql else "" 3304 interpolated_values = [ 3305 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3306 if isinstance(e, exp.Alias) 3307 else self.sql(e, "this") 3308 for e in expression.args.get("interpolate") or [] 3309 ] 3310 interpolate = ( 3311 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3312 ) 3313 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3314 3315 def cluster_sql(self, expression: exp.Cluster) -> str: 3316 return self.op_expressions("CLUSTER BY", expression) 3317 3318 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3319 if expression.this: 3320 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3321 return "" 3322 expressions = self.expressions(expression, flat=True) 3323 return f"CLUSTER BY ({expressions})" 3324 3325 def distribute_sql(self, expression: exp.Distribute) -> str: 3326 return self.op_expressions("DISTRIBUTE BY", expression) 3327 3328 def sort_sql(self, expression: exp.Sort) -> str: 3329 return self.op_expressions("SORT BY", expression) 3330 3331 def _resolve_ordered_for_null_ordering_simulation( 3332 self, expression: exp.Ordered 3333 ) -> exp.Expr | None: 3334 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3335 3336 Returns the underlying expression of the uniquely-matching projection 3337 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3338 simulation, since the CASE is evaluated in FROM-clause scope rather 3339 than alias scope (MySQL error 1052). Returns None if no safe 3340 substitution applies, leaving the original behaviour unchanged. 3341 """ 3342 this = expression.this 3343 if not (isinstance(this, exp.Column) and not this.table): 3344 return None 3345 3346 # Only a query's own ORDER BY can reference its projection aliases 3347 order = expression.parent 3348 ancestor = order.parent if isinstance(order, exp.Order) else None 3349 if not isinstance(ancestor, exp.Select): 3350 return None 3351 3352 column_name = this.name 3353 matched: list[exp.Expr] = [ 3354 p.this if isinstance(p, exp.Alias) else p 3355 for p in ancestor.selects 3356 if p.output_name == column_name 3357 ] 3358 match = matched[0] if len(matched) == 1 else None 3359 3360 # Skip the substitution when it would be identical to the existing 3361 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3362 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3363 return None 3364 3365 return match 3366 3367 def ordered_sql(self, expression: exp.Ordered) -> str: 3368 desc = expression.args.get("desc") 3369 asc = not desc 3370 3371 nulls_first = expression.args.get("nulls_first") 3372 nulls_last = not nulls_first 3373 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3374 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3375 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3376 3377 this = self.sql(expression, "this") 3378 3379 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3380 nulls_sort_change = "" 3381 if nulls_first and ( 3382 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3383 ): 3384 nulls_sort_change = " NULLS FIRST" 3385 elif ( 3386 nulls_last 3387 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3388 and not nulls_are_last 3389 ): 3390 nulls_sort_change = " NULLS LAST" 3391 3392 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3393 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3394 window = expression.find_ancestor(exp.Window, exp.Select) 3395 3396 if isinstance(window, exp.Window): 3397 window_this = window.this 3398 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3399 window_this = window_this.this 3400 spec = window.args.get("spec") 3401 else: 3402 window_this = None 3403 spec = None 3404 3405 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3406 # without a spec or with a ROWS spec, but not with RANGE 3407 if not ( 3408 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3409 and (not spec or spec.text("kind").upper() == "ROWS") 3410 ): 3411 if window_this and spec: 3412 self.unsupported( 3413 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3414 ) 3415 nulls_sort_change = "" 3416 elif self.NULL_ORDERING_SUPPORTED is False and ( 3417 (asc and nulls_sort_change == " NULLS LAST") 3418 or (desc and nulls_sort_change == " NULLS FIRST") 3419 ): 3420 # BigQuery does not allow these ordering/nulls combinations when used under 3421 # an aggregation func or under a window containing one 3422 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3423 3424 if isinstance(ancestor, exp.Window): 3425 ancestor = ancestor.this 3426 if isinstance(ancestor, exp.AggFunc): 3427 self.unsupported( 3428 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3429 ) 3430 nulls_sort_change = "" 3431 elif self.NULL_ORDERING_SUPPORTED is None: 3432 if expression.this.is_int: 3433 self.unsupported( 3434 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3435 ) 3436 elif not isinstance(expression.this, exp.Rand): 3437 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3438 target = self.sql(resolved) if resolved is not None else this 3439 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3440 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3441 nulls_sort_change = "" 3442 3443 with_fill = self.sql(expression, "with_fill") 3444 with_fill = f" {with_fill}" if with_fill else "" 3445 3446 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3447 3448 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3449 window_frame = self.sql(expression, "window_frame") 3450 window_frame = f"{window_frame} " if window_frame else "" 3451 3452 this = self.sql(expression, "this") 3453 3454 return f"{window_frame}{this}" 3455 3456 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3457 partition = self.partition_by_sql(expression) 3458 order = self.sql(expression, "order") 3459 measures = self.expressions(expression, key="measures") 3460 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3461 rows = self.sql(expression, "rows") 3462 rows = self.seg(rows) if rows else "" 3463 after = self.sql(expression, "after") 3464 after = self.seg(after) if after else "" 3465 pattern = self.sql(expression, "pattern") 3466 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3467 definition_sqls = [ 3468 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3469 for definition in expression.args.get("define", []) 3470 ] 3471 definitions = self.expressions(sqls=definition_sqls) 3472 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3473 body = "".join( 3474 ( 3475 partition, 3476 order, 3477 measures, 3478 rows, 3479 after, 3480 pattern, 3481 define, 3482 ) 3483 ) 3484 alias = self.sql(expression, "alias") 3485 alias = f" {alias}" if alias else "" 3486 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3487 3488 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3489 limit = expression.args.get("limit") 3490 3491 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3492 count = limit.args.get("count") 3493 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3494 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3495 limit = exp.Limit( 3496 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3497 ) 3498 elif ( 3499 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3500 ): 3501 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3502 3503 return csv( 3504 *sqls, 3505 *[self.sql(join) for join in expression.args.get("joins") or []], 3506 self.sql(expression, "match"), 3507 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3508 self.sql(expression, "prewhere"), 3509 self.sql(expression, "where"), 3510 self.sql(expression, "connect"), 3511 self.sql(expression, "group"), 3512 self.sql(expression, "having"), 3513 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3514 self.sql(expression, "order"), 3515 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3516 *self.after_limit_modifiers(expression), 3517 self.sql(expression, "for_"), 3518 self.options_modifier(expression), 3519 sep="", 3520 ) 3521 3522 def options_modifier(self, expression: exp.Expr) -> str: 3523 options = self.expressions(expression, key="options") 3524 return f" {options}" if options else "" 3525 3526 def forclause_sql(self, expression: exp.ForClause) -> str: 3527 kind = expression.args["kind"] 3528 if kind == "BROWSE": 3529 return f"{self.sep()}FOR BROWSE" 3530 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3531 # the target dialect doesn't support QueryOption, so we drop the clause. 3532 options = self.expressions(expression, key="expressions") 3533 if not options: 3534 return "" 3535 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3536 3537 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3538 self.unsupported("Unsupported query option.") 3539 return "" 3540 3541 def offset_limit_modifiers( 3542 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3543 ) -> list[str]: 3544 return [ 3545 self.sql(expression, "offset") if fetch else self.sql(limit), 3546 self.sql(limit) if fetch else self.sql(expression, "offset"), 3547 ] 3548 3549 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3550 locks = self.expressions(expression, key="locks", sep=" ") 3551 locks = f" {locks}" if locks else "" 3552 return [locks, self.sql(expression, "sample")] 3553 3554 def select_sql(self, expression: exp.Select) -> str: 3555 into = expression.args.get("into") 3556 if not self.SUPPORTS_SELECT_INTO and into: 3557 into.pop() 3558 3559 hint = self.sql(expression, "hint") 3560 distinct = self.sql(expression, "distinct") 3561 distinct = f" {distinct}" if distinct else "" 3562 kind = self.sql(expression, "kind") 3563 3564 limit = expression.args.get("limit") 3565 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3566 top = self.limit_sql(limit, top=True) 3567 limit.pop() 3568 else: 3569 top = "" 3570 3571 expressions = self.expressions(expression) 3572 3573 if kind: 3574 if kind in self.SELECT_KINDS: 3575 kind = f" AS {kind}" 3576 else: 3577 if kind == "STRUCT": 3578 expressions = self.expressions( 3579 sqls=[ 3580 self.sql( 3581 exp.Struct( 3582 expressions=[ 3583 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3584 if isinstance(e, exp.Alias) 3585 else e 3586 for e in expression.expressions 3587 ] 3588 ) 3589 ) 3590 ] 3591 ) 3592 kind = "" 3593 3594 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3595 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3596 3597 exclude = expression.args.get("exclude") 3598 3599 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3600 exclude_sql = self.expressions(sqls=exclude, flat=True) 3601 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3602 3603 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3604 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3605 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3606 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3607 sql = self.query_modifiers( 3608 expression, 3609 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3610 self.sql(expression, "into", comment=False), 3611 self.sql(expression, "from_", comment=False), 3612 ) 3613 3614 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3615 if expression.args.get("with_"): 3616 sql = self.maybe_comment(sql, expression) 3617 expression.pop_comments() 3618 3619 sql = self.prepend_ctes(expression, sql) 3620 3621 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3622 expression.set("exclude", None) 3623 subquery = expression.subquery(copy=False) 3624 star = exp.Star(except_=exclude) 3625 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3626 3627 if not self.SUPPORTS_SELECT_INTO and into: 3628 if into.args.get("temporary"): 3629 table_kind = " TEMPORARY" 3630 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3631 table_kind = " UNLOGGED" 3632 else: 3633 table_kind = "" 3634 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3635 3636 return sql 3637 3638 def schema_sql(self, expression: exp.Schema) -> str: 3639 this = self.sql(expression, "this") 3640 sql = self.schema_columns_sql(expression) 3641 return f"{this} {sql}" if this and sql else this or sql 3642 3643 def schema_columns_sql(self, expression: exp.Expr) -> str: 3644 if expression.expressions: 3645 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3646 return "" 3647 3648 def star_sql(self, expression: exp.Star) -> str: 3649 except_ = self.expressions(expression, key="except_", flat=True) 3650 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3651 replace = self.expressions(expression, key="replace", flat=True) 3652 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3653 rename = self.expressions(expression, key="rename", flat=True) 3654 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3655 ilike = self.sql(expression, "ilike") 3656 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3657 return f"*{ilike}{except_}{replace}{rename}" 3658 3659 def parameter_sql(self, expression: exp.Parameter) -> str: 3660 this = self.sql(expression, "this") 3661 return f"{self.PARAMETER_TOKEN}{this}" 3662 3663 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3664 this = self.sql(expression, "this") 3665 kind = expression.text("kind") 3666 if kind: 3667 kind = f"{kind}." 3668 return f"@@{kind}{this}" 3669 3670 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3671 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3672 3673 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3674 alias = self.sql(expression, "alias") 3675 alias = f"{sep}{alias}" if alias else "" 3676 sample = self.sql(expression, "sample") 3677 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3678 alias = f"{sample}{alias}" 3679 3680 # Set to None so it's not generated again by self.query_modifiers() 3681 expression.set("sample", None) 3682 3683 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3684 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3685 return self.prepend_ctes(expression, sql) 3686 3687 def qualify_sql(self, expression: exp.Qualify) -> str: 3688 this = self.indent(self.sql(expression, "this")) 3689 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3690 3691 def unnest_sql(self, expression: exp.Unnest) -> str: 3692 args = self.expressions(expression, flat=True) 3693 3694 alias = expression.args.get("alias") 3695 offset = expression.args.get("offset") 3696 3697 if self.UNNEST_WITH_ORDINALITY: 3698 if alias and isinstance(offset, exp.Expr): 3699 alias.append("columns", offset) 3700 expression.set("offset", None) 3701 3702 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3703 columns = alias.columns 3704 alias = self.sql(columns[0]) if columns else "" 3705 else: 3706 alias = self.sql(alias) 3707 3708 alias = f" AS {alias}" if alias else alias 3709 if self.UNNEST_WITH_ORDINALITY: 3710 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3711 else: 3712 if isinstance(offset, exp.Expr): 3713 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3714 elif offset: 3715 suffix = f"{alias} WITH OFFSET" 3716 else: 3717 suffix = alias 3718 3719 return f"UNNEST({args}){suffix}" 3720 3721 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3722 return "" 3723 3724 def where_sql(self, expression: exp.Where) -> str: 3725 this = self.indent(self.sql(expression, "this")) 3726 return f"{self.seg('WHERE')}{self.sep()}{this}" 3727 3728 def window_sql(self, expression: exp.Window) -> str: 3729 this = self.sql(expression, "this") 3730 partition = self.partition_by_sql(expression) 3731 order = expression.args.get("order") 3732 order = self.order_sql(order, flat=True) if order else "" 3733 spec = self.sql(expression, "spec") 3734 alias = self.sql(expression, "alias") 3735 over = self.sql(expression, "over") or "OVER" 3736 3737 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3738 3739 first = expression.args.get("first") 3740 if first is None: 3741 first = "" 3742 else: 3743 first = "FIRST" if first else "LAST" 3744 3745 if not partition and not order and not spec and alias: 3746 # WINDOW w2 AS w1 is unparseable in DuckDB/Postgres; parens are required. 3747 if expression.arg_key == "windows": 3748 return f"{this} ({alias})" 3749 return f"{this} {alias}" 3750 3751 args = self.format_args( 3752 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3753 ) 3754 return f"{this} ({args})" 3755 3756 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3757 partition = self.expressions(expression, key="partition_by", flat=True) 3758 return f"PARTITION BY {partition}" if partition else "" 3759 3760 def partitionbyexcluding_sql(self, expression: exp.PartitionByExcluding) -> str: 3761 return f"EXCLUDING {self.expressions(expression, flat=True)}" 3762 3763 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3764 kind = self.sql(expression, "kind") 3765 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3766 end = ( 3767 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3768 or "CURRENT ROW" 3769 ) 3770 3771 window_spec = f"{kind} BETWEEN {start} AND {end}" 3772 3773 exclude = self.sql(expression, "exclude") 3774 if exclude: 3775 if self.SUPPORTS_WINDOW_EXCLUDE: 3776 window_spec += f" EXCLUDE {exclude}" 3777 else: 3778 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3779 3780 return window_spec 3781 3782 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3783 this = self.sql(expression, "this") 3784 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3785 return f"{this} WITHIN GROUP ({expression_sql})" 3786 3787 def between_sql(self, expression: exp.Between) -> str: 3788 this = self.sql(expression, "this") 3789 low = self.sql(expression, "low") 3790 high = self.sql(expression, "high") 3791 symmetric = expression.args.get("symmetric") 3792 3793 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3794 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3795 3796 flag = ( 3797 " SYMMETRIC" 3798 if symmetric 3799 else " ASYMMETRIC" 3800 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3801 else "" # silently drop ASYMMETRIC – semantics identical 3802 ) 3803 return f"{this} BETWEEN{flag} {low} AND {high}" 3804 3805 def bracket_offset_expressions( 3806 self, expression: exp.Bracket, index_offset: int | None = None 3807 ) -> list[exp.Expr]: 3808 if expression.args.get("json_access"): 3809 return expression.expressions 3810 3811 return apply_index_offset( 3812 expression.this, 3813 expression.expressions, 3814 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3815 dialect=self.dialect, 3816 ) 3817 3818 def bracket_sql(self, expression: exp.Bracket) -> str: 3819 expressions = self.bracket_offset_expressions(expression) 3820 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3821 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3822 3823 def all_sql(self, expression: exp.All) -> str: 3824 this = self.sql(expression, "this") 3825 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3826 this = self.wrap(this) 3827 return f"ALL {this}" 3828 3829 def any_sql(self, expression: exp.Any) -> str: 3830 this = self.sql(expression, "this") 3831 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3832 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3833 this = self.wrap(this) 3834 return f"ANY{this}" 3835 return f"ANY {this}" 3836 3837 def exists_sql(self, expression: exp.Exists) -> str: 3838 return f"EXISTS{self.wrap(expression)}" 3839 3840 def case_sql(self, expression: exp.Case) -> str: 3841 this = self.sql(expression, "this") 3842 statements = [f"CASE {this}" if this else "CASE"] 3843 3844 for e in expression.args["ifs"]: 3845 statements.append(f"WHEN {self.sql(e, 'this')}") 3846 statements.append(f"THEN {self.sql(e, 'true')}") 3847 3848 default = self.sql(expression, "default") 3849 3850 if default: 3851 statements.append(f"ELSE {default}") 3852 3853 statements.append("END") 3854 3855 if self.pretty and self.too_wide(statements): 3856 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3857 3858 return " ".join(statements) 3859 3860 def constraint_sql(self, expression: exp.Constraint) -> str: 3861 this = self.sql(expression, "this") 3862 expressions = self.expressions(expression, flat=True) 3863 return f"CONSTRAINT {this} {expressions}" 3864 3865 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3866 order = expression.args.get("order") 3867 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3868 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3869 3870 def extract_sql(self, expression: exp.Extract) -> str: 3871 import sqlglot.dialects.dialect 3872 3873 this = ( 3874 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3875 if self.NORMALIZE_EXTRACT_DATE_PARTS 3876 else expression.this 3877 ) 3878 if self.EXTRACT_ALLOWS_QUOTES: 3879 this_sql = self.sql(this) 3880 elif isinstance(this, exp.WeekStart): 3881 this_sql = self.weekstart_name(this) 3882 else: 3883 this_sql = this.name 3884 expression_sql = self.sql(expression, "expression") 3885 3886 return f"EXTRACT({this_sql} FROM {expression_sql})" 3887 3888 def trim_sql(self, expression: exp.Trim) -> str: 3889 trim_type = self.sql(expression, "position") 3890 3891 if trim_type == "LEADING": 3892 func_name = "LTRIM" 3893 elif trim_type == "TRAILING": 3894 func_name = "RTRIM" 3895 else: 3896 func_name = "TRIM" 3897 3898 return self.func(func_name, expression.this, expression.expression) 3899 3900 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3901 args = expression.expressions 3902 if isinstance(expression, exp.ConcatWs): 3903 args = args[1:] # Skip the delimiter 3904 3905 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3906 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3907 3908 concat_coalesce = ( 3909 self.dialect.CONCAT_WS_COALESCE 3910 if isinstance(expression, exp.ConcatWs) 3911 else self.dialect.CONCAT_COALESCE 3912 ) 3913 3914 if not concat_coalesce and expression.args.get("coalesce"): 3915 3916 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3917 if not e.type: 3918 import sqlglot.optimizer.annotate_types 3919 3920 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3921 3922 if e.is_string or e.is_type(exp.DType.ARRAY): 3923 return e 3924 3925 return exp.func("coalesce", e, exp.Literal.string("")) 3926 3927 args = [_wrap_with_coalesce(e) for e in args] 3928 3929 return args 3930 3931 def concat_sql(self, expression: exp.Concat) -> str: 3932 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3933 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3934 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3935 # instead of coalescing them to empty string. 3936 import sqlglot.dialects.dialect 3937 3938 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3939 3940 expressions = self.convert_concat_args(expression) 3941 3942 # Some dialects don't allow a single-argument CONCAT call 3943 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3944 return self.sql(expressions[0]) 3945 3946 return self.func("CONCAT", *expressions) 3947 3948 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3949 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3950 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3951 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3952 all_args = expression.expressions 3953 expression.set("coalesce", True) 3954 return self.sql( 3955 exp.case() 3956 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3957 .else_(expression) 3958 ) 3959 3960 return self.func( 3961 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3962 ) 3963 3964 def check_sql(self, expression: exp.Check) -> str: 3965 this = self.sql(expression, key="this") 3966 return f"CHECK ({this})" 3967 3968 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3969 this = self.sql(expression, "this") 3970 this = f" {this}" if this else "" 3971 expressions = self.expressions(expression, flat=True) 3972 expressions = f" ({expressions})" if expressions else "" 3973 reference = self.sql(expression, "reference") 3974 reference = f" {reference}" if reference else "" 3975 delete = self.sql(expression, "delete") 3976 delete = f" ON DELETE {delete}" if delete else "" 3977 update = self.sql(expression, "update") 3978 update = f" ON UPDATE {update}" if update else "" 3979 options = self.expressions(expression, key="options", flat=True, sep=" ") 3980 options = f" {options}" if options else "" 3981 return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}" 3982 3983 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3984 this = self.sql(expression, "this") 3985 this = f" {this}" if this else "" 3986 expressions = self.expressions(expression, flat=True) 3987 include = self.sql(expression, "include") 3988 options = self.expressions(expression, key="options", flat=True, sep=" ") 3989 options = f" {options}" if options else "" 3990 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3991 3992 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3993 self.unsupported("TIMESERIES primary key columns are not supported") 3994 return self.sql(expression, "this") 3995 3996 def if_sql(self, expression: exp.If) -> str: 3997 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3998 3999 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 4000 if self.MATCH_AGAINST_TABLE_PREFIX: 4001 expressions = [] 4002 for expr in expression.expressions: 4003 if isinstance(expr, exp.Table): 4004 expressions.append(f"TABLE {self.sql(expr)}") 4005 else: 4006 expressions.append(expr) 4007 else: 4008 expressions = expression.expressions 4009 4010 modifier = expression.args.get("modifier") 4011 modifier = f" {modifier}" if modifier else "" 4012 return ( 4013 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 4014 ) 4015 4016 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 4017 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 4018 4019 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 4020 path = self.expressions(expression, sep="", flat=True).lstrip(".") 4021 4022 if self.QUOTE_JSON_PATH: 4023 path = self.escape_str(path) 4024 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 4025 4026 return path 4027 4028 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 4029 if isinstance(expression, exp.JSONPathPart): 4030 transform = self.TRANSFORMS.get(expression.__class__) 4031 if not callable(transform): 4032 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 4033 return "" 4034 4035 return transform(self, expression) 4036 4037 if isinstance(expression, int): 4038 return str(expression) 4039 4040 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 4041 escaped = expression.replace("'", "\\'") 4042 escaped = f"'{escaped}'" 4043 else: 4044 escaped = expression.replace('"', '\\"') 4045 escaped = f'"{escaped}"' 4046 4047 return escaped 4048 4049 def formatjson_sql(self, expression: exp.FormatJson) -> str: 4050 return f"{self.sql(expression, 'this')} FORMAT JSON" 4051 4052 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 4053 # Output the Teradata column FORMAT override. 4054 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 4055 this = self.sql(expression, "this") 4056 fmt = self.sql(expression, "format") 4057 return f"{this} (FORMAT {fmt})" 4058 4059 def teradatatitle_sql(self, expression: exp.TeradataTitle) -> str: 4060 self.unsupported("Teradata TITLE display headings are not supported") 4061 return self.sql(expression, "this") 4062 4063 def _jsonobject_sql( 4064 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 4065 ) -> str: 4066 null_handling = expression.args.get("null_handling") 4067 null_handling = f" {null_handling}" if null_handling else "" 4068 4069 unique_keys = expression.args.get("unique_keys") 4070 if unique_keys is not None: 4071 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 4072 else: 4073 unique_keys = "" 4074 4075 return_type = self.sql(expression, "return_type") 4076 return_type = f" RETURNING {return_type}" if return_type else "" 4077 encoding = self.sql(expression, "encoding") 4078 encoding = f" ENCODING {encoding}" if encoding else "" 4079 4080 if not name: 4081 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 4082 4083 return self.func( 4084 name, 4085 *expression.expressions, 4086 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 4087 ) 4088 4089 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 4090 null_handling = expression.args.get("null_handling") 4091 null_handling = f" {null_handling}" if null_handling else "" 4092 return_type = self.sql(expression, "return_type") 4093 return_type = f" RETURNING {return_type}" if return_type else "" 4094 strict = " STRICT" if expression.args.get("strict") else "" 4095 return self.func( 4096 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 4097 ) 4098 4099 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 4100 this = self.sql(expression, "this") 4101 order = self.sql(expression, "order") 4102 null_handling = expression.args.get("null_handling") 4103 null_handling = f" {null_handling}" if null_handling else "" 4104 return_type = self.sql(expression, "return_type") 4105 return_type = f" RETURNING {return_type}" if return_type else "" 4106 strict = " STRICT" if expression.args.get("strict") else "" 4107 return self.func( 4108 "JSON_ARRAYAGG", 4109 this, 4110 suffix=f"{order}{null_handling}{return_type}{strict})", 4111 ) 4112 4113 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 4114 path = self.sql(expression, "path") 4115 path = f" PATH {path}" if path else "" 4116 nested_schema = self.sql(expression, "nested_schema") 4117 4118 if nested_schema: 4119 return f"NESTED{path} {nested_schema}" 4120 4121 this = self.sql(expression, "this") 4122 kind = self.sql(expression, "kind") 4123 kind = f" {kind}" if kind else "" 4124 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 4125 4126 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 4127 return f"{this}{kind}{format_json}{path}{ordinality}" 4128 4129 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 4130 return self.func("COLUMNS", *expression.expressions) 4131 4132 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4133 this = self.sql(expression, "this") 4134 path = self.sql(expression, "path") 4135 path = f", {path}" if path else "" 4136 error_handling = expression.args.get("error_handling") 4137 error_handling = f" {error_handling}" if error_handling else "" 4138 empty_handling = expression.args.get("empty_handling") 4139 empty_handling = f" {empty_handling}" if empty_handling else "" 4140 schema = self.sql(expression, "schema") 4141 return self.func( 4142 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4143 ) 4144 4145 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4146 this = self.sql(expression, "this") 4147 kind = self.sql(expression, "kind") 4148 path = self.sql(expression, "path") 4149 path = f" {path}" if path else "" 4150 as_json = " AS JSON" if expression.args.get("as_json") else "" 4151 return f"{this} {kind}{path}{as_json}" 4152 4153 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4154 this = self.sql(expression, "this") 4155 path = self.sql(expression, "path") 4156 path = f", {path}" if path else "" 4157 expressions = self.expressions(expression) 4158 with_ = ( 4159 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4160 if expressions 4161 else "" 4162 ) 4163 return f"OPENJSON({this}{path}){with_}" 4164 4165 def in_sql(self, expression: exp.In) -> str: 4166 query = expression.args.get("query") 4167 unnest = expression.args.get("unnest") 4168 field = expression.args.get("field") 4169 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4170 4171 if query: 4172 in_sql = self.sql(query) 4173 elif unnest: 4174 in_sql = self.in_unnest_op(unnest) 4175 elif field: 4176 in_sql = self.sql(field) 4177 else: 4178 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4179 4180 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 4181 4182 def in_unnest_op(self, unnest: exp.Unnest) -> str: 4183 return f"(SELECT {self.sql(unnest)})" 4184 4185 def interval_sql(self, expression: exp.Interval) -> str: 4186 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4187 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4188 exp.AutoRefreshProperty, 4189 ) 4190 interval_keyword = "INTERVAL" if include_keyword else "" 4191 unit_expression = expression.args.get("unit") 4192 unit = self.sql(unit_expression) if unit_expression else "" 4193 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4194 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4195 unit = f" {unit}" if unit else "" 4196 4197 if self.SINGLE_STRING_INTERVAL: 4198 this = expression.this.name if expression.this else "" 4199 if this: 4200 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4201 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4202 return f"{interval_keyword}{self.sql(exp.Literal.string(this))}{unit}" 4203 return f"{interval_keyword}{self.sql(exp.Literal.string(f'{this}{unit}'))}" 4204 return f"{interval_keyword}{unit}" 4205 4206 this = self.sql(expression, "this") 4207 if this: 4208 if not include_keyword and expression.this.is_string: 4209 this = expression.this.name 4210 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4211 this = f"({this})" 4212 if include_keyword: 4213 this = f" {this}" 4214 4215 return f"{interval_keyword}{this}{unit}" 4216 4217 def return_sql(self, expression: exp.Return) -> str: 4218 return f"RETURN {self.sql(expression, 'this')}" 4219 4220 def reference_sql(self, expression: exp.Reference) -> str: 4221 this = self.sql(expression, "this") 4222 expressions = self.expressions(expression, flat=True) 4223 expressions = f"({expressions})" if expressions else "" 4224 options = self.expressions(expression, key="options", flat=True, sep=" ") 4225 options = f" {options}" if options else "" 4226 return f"REFERENCES {this}{expressions}{options}" 4227 4228 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4229 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4230 parent = expression.parent 4231 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4232 4233 return self.func( 4234 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4235 ) 4236 4237 def paren_sql(self, expression: exp.Paren) -> str: 4238 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 4239 return f"({sql}{self.seg(')', sep='')}" 4240 4241 def neg_sql(self, expression: exp.Neg) -> str: 4242 # This makes sure we don't convert "- - 5" to "--5", which is a comment 4243 this_sql = self.sql(expression, "this") 4244 sep = " " if this_sql[0] == "-" else "" 4245 return f"-{sep}{this_sql}" 4246 4247 def not_sql(self, expression: exp.Not) -> str: 4248 return f"NOT {self.sql(expression, 'this')}" 4249 4250 def alias_sql(self, expression: exp.Alias) -> str: 4251 alias = self.sql(expression, "alias") 4252 alias = f" AS {alias}" if alias else "" 4253 return f"{self.sql(expression, 'this')}{alias}" 4254 4255 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4256 alias = expression.args["alias"] 4257 4258 parent = expression.parent 4259 pivot = parent and parent.parent 4260 4261 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4262 identifier_alias = isinstance(alias, exp.Identifier) 4263 literal_alias = isinstance(alias, exp.Literal) 4264 4265 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4266 alias.replace(exp.Literal.string(alias.output_name)) 4267 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4268 alias.replace(exp.to_identifier(alias.output_name)) 4269 4270 return self.alias_sql(expression) 4271 4272 def aliases_sql(self, expression: exp.Aliases) -> str: 4273 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4274 4275 def atindex_sql(self, expression: exp.AtIndex) -> str: 4276 this = self.sql(expression, "this") 4277 index = self.sql(expression, "expression") 4278 return f"{this} AT {index}" 4279 4280 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4281 this = self.sql(expression, "this") 4282 zone = self.sql(expression, "zone") 4283 return f"{this} AT TIME ZONE {zone}" 4284 4285 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4286 this = self.sql(expression, "this") 4287 zone = self.sql(expression, "zone") 4288 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4289 4290 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4291 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4292 4293 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4294 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4295 4296 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4297 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4298 4299 def add_sql(self, expression: exp.Add) -> str: 4300 return self.binary(expression, "+") 4301 4302 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4303 return self.connector_sql(expression, "AND", stack) 4304 4305 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4306 return self.connector_sql(expression, "OR", stack) 4307 4308 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4309 return self.connector_sql(expression, "XOR", stack) 4310 4311 def connector_sql( 4312 self, 4313 expression: exp.Connector, 4314 op: str, 4315 stack: list[str | exp.Expr] | None = None, 4316 ) -> str: 4317 if stack is not None: 4318 stack.append(expression.right) 4319 if expression.comments and self.comments: 4320 op = self.maybe_comment(op, comments=expression.comments) 4321 4322 stack.extend((op, expression.left)) 4323 return op 4324 4325 stack = [expression] 4326 sqls: list[str] = [] 4327 ops = set() 4328 4329 while stack: 4330 node = stack.pop() 4331 if isinstance(node, exp.Connector): 4332 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4333 else: 4334 sql = self.sql(node) 4335 if sqls and sqls[-1] in ops: 4336 sqls[-1] += f" {sql}" 4337 else: 4338 sqls.append(sql) 4339 4340 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4341 return sep.join(sqls) 4342 4343 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4344 return self.binary(expression, "&") 4345 4346 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4347 return self.binary(expression, "<<") 4348 4349 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4350 return f"~{self.sql(expression, 'this')}" 4351 4352 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4353 return self.binary(expression, "|") 4354 4355 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4356 return self.binary(expression, ">>") 4357 4358 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4359 return self.binary(expression, "^") 4360 4361 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4362 format_sql = self.sql(expression, "format") 4363 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4364 to_sql = self.sql(expression, "to") 4365 to_sql = f" {to_sql}" if to_sql else "" 4366 action = self.sql(expression, "action") 4367 action = f" {action}" if action else "" 4368 default = self.sql(expression, "default") 4369 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4370 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4371 4372 # Base implementation that excludes safe, zone, and target_type metadata args 4373 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4374 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4375 4376 # Base implementation that excludes the safe and default_year metadata args 4377 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4378 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4379 4380 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4381 return self.func( 4382 "PARSE_DATETIME", 4383 expression.this, 4384 expression.args.get("format"), 4385 expression.args.get("zone"), 4386 ) 4387 4388 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4389 zone = self.sql(expression, "this") 4390 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4391 4392 def collate_sql(self, expression: exp.Collate) -> str: 4393 if self.COLLATE_IS_FUNC: 4394 return self.function_fallback_sql(expression) 4395 return self.binary(expression, "COLLATE") 4396 4397 def command_sql(self, expression: exp.Command) -> str: 4398 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4399 4400 def comment_sql(self, expression: exp.Comment) -> str: 4401 this = self.sql(expression, "this") 4402 kind = expression.args["kind"] 4403 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4404 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4405 expression_sql = self.sql(expression, "expression") 4406 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4407 4408 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4409 this = self.sql(expression, "this") 4410 delete = " DELETE" if expression.args.get("delete") else "" 4411 recompress = self.sql(expression, "recompress") 4412 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4413 to_disk = self.sql(expression, "to_disk") 4414 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4415 to_volume = self.sql(expression, "to_volume") 4416 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4417 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4418 4419 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4420 where = self.sql(expression, "where") 4421 group = self.sql(expression, "group") 4422 aggregates = self.expressions(expression, key="aggregates") 4423 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4424 4425 if not (where or group or aggregates) and len(expression.expressions) == 1: 4426 return f"TTL {self.expressions(expression, flat=True)}" 4427 4428 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4429 4430 def transaction_sql(self, expression: exp.Transaction) -> str: 4431 modes = self.expressions(expression, key="modes") 4432 modes = f" {modes}" if modes else "" 4433 return f"BEGIN{modes}" 4434 4435 def commit_sql(self, expression: exp.Commit) -> str: 4436 chain = expression.args.get("chain") 4437 if chain is not None: 4438 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4439 4440 return f"COMMIT{chain or ''}" 4441 4442 def rollback_sql(self, expression: exp.Rollback) -> str: 4443 savepoint = expression.args.get("savepoint") 4444 savepoint = f" TO {savepoint}" if savepoint else "" 4445 return f"ROLLBACK{savepoint}" 4446 4447 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4448 this = self.sql(expression, "this") 4449 4450 exists = "" 4451 if expression.args.get("exists"): 4452 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4453 exists = " IF EXISTS" 4454 else: 4455 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4456 4457 dtype = self.sql(expression, "dtype") 4458 if dtype: 4459 collate = self.sql(expression, "collate") 4460 collate = f" COLLATE {collate}" if collate else "" 4461 using = self.sql(expression, "using") 4462 using = f" USING {using}" if using else "" 4463 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4464 null_constraint = self._alter_column_null_constraint_sql(expression) 4465 4466 return ( 4467 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4468 f"{collate}{using}{null_constraint}" 4469 ) 4470 4471 default = self.sql(expression, "default") 4472 if default: 4473 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4474 4475 comment = self.sql(expression, "comment") 4476 if comment: 4477 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4478 4479 visible = expression.args.get("visible") 4480 if visible: 4481 return f"ALTER COLUMN{exists} {this} SET {visible}" 4482 4483 allow_null = expression.args.get("allow_null") 4484 drop = expression.args.get("drop") 4485 4486 if not drop and not allow_null: 4487 self.unsupported("Unsupported ALTER COLUMN syntax") 4488 4489 if allow_null is not None: 4490 keyword = "DROP" if drop else "SET" 4491 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4492 4493 return f"ALTER COLUMN{exists} {this} DROP DEFAULT" 4494 4495 def _alter_column_null_constraint_sql(self, expression: exp.AlterColumn) -> str: 4496 allow_null = expression.args.get("allow_null") 4497 if allow_null is None: 4498 return "" 4499 4500 if not self.SUPPORTS_ALTER_COLUMN_NULLABILITY: 4501 self.unsupported("ALTER COLUMN cannot set nullability along with a type") 4502 return "" 4503 4504 return " NULL" if allow_null else " NOT NULL" 4505 4506 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4507 this = self.sql(expression, "this") 4508 rename_from = self.sql(expression, "rename_from") 4509 if rename_from: 4510 if not self.SUPPORTS_CHANGE_COLUMN: 4511 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4512 return f"CHANGE COLUMN {rename_from} {this}" 4513 if not self.SUPPORTS_MODIFY_COLUMN: 4514 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4515 return f"MODIFY COLUMN {this}" 4516 4517 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4518 this = self.sql(expression, "this") 4519 4520 visible = expression.args.get("visible") 4521 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4522 4523 return f"ALTER INDEX {this} {visible_sql}" 4524 4525 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4526 this = self.sql(expression, "this") 4527 if not isinstance(expression.this, exp.Var): 4528 this = f"KEY DISTKEY {this}" 4529 return f"ALTER DISTSTYLE {this}" 4530 4531 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4532 compound = " COMPOUND" if expression.args.get("compound") else "" 4533 this = self.sql(expression, "this") 4534 expressions = self.expressions(expression, flat=True) 4535 expressions = f"({expressions})" if expressions else "" 4536 return f"ALTER{compound} SORTKEY {this or expressions}" 4537 4538 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4539 if not self.RENAME_TABLE_WITH_DB: 4540 # Remove db from tables 4541 expression = expression.transform( 4542 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4543 ).assert_is(exp.AlterRename) 4544 this = self.sql(expression, "this") 4545 to_kw = " TO" if include_to else "" 4546 return f"RENAME{to_kw} {this}" 4547 4548 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4549 exists = " IF EXISTS" if expression.args.get("exists") else "" 4550 old_column = self.sql(expression, "this") 4551 new_column = self.sql(expression, "to") 4552 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4553 4554 def alterset_sql(self, expression: exp.AlterSet) -> str: 4555 exprs = self.expressions(expression, flat=True) 4556 if self.ALTER_SET_WRAPPED: 4557 exprs = f"({exprs})" 4558 4559 return f"SET {exprs}" 4560 4561 def alter_sql(self, expression: exp.Alter) -> str: 4562 actions = expression.actions 4563 4564 if ( 4565 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4566 and actions 4567 and isinstance(actions[0], exp.ColumnDef) 4568 ): 4569 actions_sql = self.expressions(expression, key="actions", flat=True) 4570 actions_sql = f"ADD {actions_sql}" 4571 else: 4572 actions_list = [] 4573 for action in actions: 4574 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4575 action_sql = self.add_column_sql(action) 4576 else: 4577 action_sql = self.sql(action) 4578 if isinstance(action, exp.Query): 4579 action_sql = f"AS {action_sql}" 4580 4581 actions_list.append(action_sql) 4582 4583 actions_sql = self.format_args(*actions_list).lstrip("\n") 4584 4585 iceberg = ( 4586 "ICEBERG " 4587 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4588 else "" 4589 ) 4590 exists = " IF EXISTS" if expression.args.get("exists") else "" 4591 on_cluster = self.sql(expression, "cluster") 4592 on_cluster = f" {on_cluster}" if on_cluster else "" 4593 only = " ONLY" if expression.args.get("only") else "" 4594 options = expression.args.get("options") or [] 4595 options_sql = self.expressions( 4596 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4597 ) 4598 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4599 partitions_sql = self.expressions( 4600 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4601 sep=" ", 4602 flat=True, 4603 ) 4604 if partitions_sql and (actions_sql or options_sql): 4605 partitions_sql = f" {partitions_sql}" 4606 kind = self.sql(expression, "kind") 4607 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4608 check = " WITH CHECK" if expression.args.get("check") else "" 4609 cascade = ( 4610 " CASCADE" 4611 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4612 else "" 4613 ) 4614 this = self.sql(expression, "this") 4615 this = f" {this}" if this else "" 4616 4617 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}" 4618 4619 def altersession_sql(self, expression: exp.AlterSession) -> str: 4620 items_sql = self.expressions(expression, flat=True) 4621 keyword = "UNSET" if expression.args.get("unset") else "SET" 4622 return f"{keyword} {items_sql}" 4623 4624 def add_column_sql(self, expression: exp.Expr) -> str: 4625 column_text = "" 4626 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4627 if isinstance(expression, exp.ColumnDef): 4628 column_text = " COLUMN" 4629 elif isinstance(expression, exp.Schema): 4630 column_text = " COLUMNS" 4631 4632 if isinstance(expression, exp.Schema) and not self.ALTER_TABLE_ADD_COLUMNS_WRAPPED: 4633 sql = self.expressions(expression, flat=True) 4634 else: 4635 sql = self.sql(expression) 4636 4637 return f"ADD{column_text} {sql}" 4638 4639 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4640 expressions = self.expressions(expression) 4641 exists = " IF EXISTS " if expression.args.get("exists") else " " 4642 return f"DROP{exists}{expressions}" 4643 4644 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4645 return "DROP PRIMARY KEY" 4646 4647 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4648 return f"ADD {self.expressions(expression, indent=False)}" 4649 4650 def replacecolumns_sql(self, expression: exp.ReplaceColumns) -> str: 4651 return f"REPLACE COLUMNS ({self.expressions(expression, flat=True)})" 4652 4653 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4654 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4655 location = self.sql(expression, "location") 4656 location = f" {location}" if location else "" 4657 return f"ADD {exists}{self.sql(expression.this)}{location}" 4658 4659 def distinct_sql(self, expression: exp.Distinct) -> str: 4660 this = self.expressions(expression, flat=True) 4661 4662 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4663 case = exp.case() 4664 for arg in expression.expressions: 4665 case = case.when(arg.is_(exp.null()), exp.null()) 4666 this = self.sql(case.else_(f"({this})")) 4667 4668 this = f" {this}" if this else "" 4669 4670 on = self.sql(expression, "on") 4671 on = f" ON {on}" if on else "" 4672 return f"DISTINCT{this}{on}" 4673 4674 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4675 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4676 4677 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4678 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4679 4680 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4681 this_sql = self.sql(expression, "this") 4682 expression_sql = self.sql(expression, "expression") 4683 kind = "MAX" if expression.args.get("max") else "MIN" 4684 return f"{this_sql} HAVING {kind} {expression_sql}" 4685 4686 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4687 return self._truncated_div_sql(expression.this, expression.expression) 4688 4689 def _truncated_div_sql(self, this: exp.Expr, divisor: exp.Expr) -> str: 4690 div: exp.Expr = exp.Div(this=this, expression=divisor) 4691 if self.CAST_TO_INT_ROUNDS: 4692 div = exp.Trunc(this=div) 4693 4694 return self.sql(exp.Cast(this=div, to=exp.DataType(this=exp.DType.BIGINT))) 4695 4696 def dpipe_sql(self, expression: exp.DPipe) -> str: 4697 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4698 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4699 return self.binary(expression, "||") 4700 4701 def nullif_zero_divisor(self, expression: exp.Div | exp.Mod) -> None: 4702 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4703 divisor = expression.right 4704 divisor.replace(exp.Nullif(this=divisor.copy(), expression=exp.Literal.number(0))) 4705 4706 def div_sql(self, expression: exp.Div) -> str: 4707 l, r = expression.left, expression.right 4708 self.nullif_zero_divisor(expression) 4709 4710 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4711 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4712 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4713 4714 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4715 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4716 # Native integer division depends on the target's operand types, which may differ 4717 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4718 # integer literals or casts 4719 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4720 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4721 ): 4722 return self.sql(exp.IntDiv(this=l, expression=expression.right)) 4723 return self._truncated_div_sql(l, expression.right) 4724 4725 return self.binary(expression, "/") 4726 4727 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4728 n = exp._wrap(expression.this, exp.Binary) 4729 d = exp._wrap(expression.expression, exp.Binary) 4730 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4731 4732 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4733 return self.binary(expression, "OVERLAPS") 4734 4735 def distance_sql(self, expression: exp.Distance) -> str: 4736 return self.binary(expression, "<->") 4737 4738 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4739 return self.binary(expression, "<<->>") 4740 4741 def dot_sql(self, expression: exp.Dot) -> str: 4742 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4743 4744 def eq_sql(self, expression: exp.EQ) -> str: 4745 return self.binary(expression, "=") 4746 4747 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4748 return self.binary(expression, ":=") 4749 4750 def escape_sql(self, expression: exp.Escape) -> str: 4751 this = expression.this 4752 if ( 4753 isinstance(this, (exp.Like, exp.ILike)) 4754 and isinstance(this.expression, (exp.All, exp.Any)) 4755 and not self.SUPPORTS_LIKE_QUANTIFIERS 4756 ): 4757 return self._like_sql(this, escape=expression) 4758 return self.binary(expression, "ESCAPE") 4759 4760 def glob_sql(self, expression: exp.Glob) -> str: 4761 return self.binary(expression, "GLOB") 4762 4763 def gt_sql(self, expression: exp.GT) -> str: 4764 return self.binary(expression, ">") 4765 4766 def gte_sql(self, expression: exp.GTE) -> str: 4767 return self.binary(expression, ">=") 4768 4769 def is_sql(self, expression: exp.Is) -> str: 4770 negate = expression.args.get("negate") 4771 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4772 positive = bool(expression.expression.this) != bool(negate) 4773 return self.sql(expression.this if positive else exp.not_(expression.this)) 4774 return self.binary(expression, "IS NOT" if negate else "IS") 4775 4776 def _like_sql( 4777 self, 4778 expression: exp.Like | exp.ILike, 4779 escape: exp.Escape | None = None, 4780 ) -> str: 4781 this = expression.this 4782 rhs = expression.expression 4783 4784 if isinstance(expression, exp.Like): 4785 exp_class: type[exp.Like | exp.ILike] = exp.Like 4786 op = "LIKE" 4787 else: 4788 exp_class = exp.ILike 4789 op = "ILIKE" 4790 4791 if expression.args.get("negate"): 4792 op = f"NOT {op}" 4793 4794 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4795 exprs = rhs.this.unnest() 4796 4797 if isinstance(exprs, exp.Tuple): 4798 exprs = exprs.expressions 4799 else: 4800 exprs = [exprs] 4801 4802 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4803 4804 def _make_like(expr: exp.Expression) -> exp.Expression: 4805 like: exp.Expression = exp_class( 4806 this=this, expression=expr, negate=expression.args.get("negate") 4807 ) 4808 if escape: 4809 like = exp.Escape(this=like, expression=escape.expression.copy()) 4810 return like 4811 4812 like_expr: exp.Expr = _make_like(exprs[0]) 4813 for expr in exprs[1:]: 4814 like_expr = connective(like_expr, _make_like(expr), copy=False) 4815 4816 parent = escape.parent if escape else expression.parent 4817 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4818 parent, exp.Condition 4819 ): 4820 like_expr = exp.paren(like_expr, copy=False) 4821 4822 return self.sql(like_expr) 4823 4824 return self.binary(expression, op) 4825 4826 def like_sql(self, expression: exp.Like) -> str: 4827 return self._like_sql(expression) 4828 4829 def ilike_sql(self, expression: exp.ILike) -> str: 4830 return self._like_sql(expression) 4831 4832 def match_sql(self, expression: exp.Match) -> str: 4833 if expression.args.get("kind") or expression.args.get("analyzer"): 4834 self.unsupported("MATCH variants and USING ANALYZER are not supported in this dialect") 4835 return self.binary(expression, "MATCH") 4836 4837 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4838 return self.binary(expression, "SIMILAR TO") 4839 4840 def lt_sql(self, expression: exp.LT) -> str: 4841 return self.binary(expression, "<") 4842 4843 def lte_sql(self, expression: exp.LTE) -> str: 4844 return self.binary(expression, "<=") 4845 4846 def mod_sql(self, expression: exp.Mod) -> str: 4847 self.nullif_zero_divisor(expression) 4848 this = self.sql(expression, "this") 4849 expr = self.sql(expression, "expression") 4850 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4851 4852 parent = expression.parent 4853 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4854 return f"({sql})" 4855 4856 return sql 4857 4858 def mul_sql(self, expression: exp.Mul) -> str: 4859 return self.binary(expression, "*") 4860 4861 def neq_sql(self, expression: exp.NEQ) -> str: 4862 return self.binary(expression, "<>") 4863 4864 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4865 return self.binary(expression, "IS NOT DISTINCT FROM") 4866 4867 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4868 return self.binary(expression, "IS DISTINCT FROM") 4869 4870 def sub_sql(self, expression: exp.Sub) -> str: 4871 return self.binary(expression, "-") 4872 4873 def trycast_sql(self, expression: exp.TryCast) -> str: 4874 return self.cast_sql(expression, safe_prefix="TRY_") 4875 4876 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4877 return self.cast_sql(expression) 4878 4879 def try_sql(self, expression: exp.Try) -> str: 4880 if not self.TRY_SUPPORTED: 4881 self.unsupported("Unsupported TRY function") 4882 return self.sql(expression, "this") 4883 4884 return self.func("TRY", expression.this) 4885 4886 def log_sql(self, expression: exp.Log) -> str: 4887 this = expression.this 4888 expr = expression.expression 4889 4890 if self.dialect.LOG_BASE_FIRST is False: 4891 this, expr = expr, this 4892 elif self.dialect.LOG_BASE_FIRST is None and expr: 4893 if this.name in ("2", "10"): 4894 return self.func(f"LOG{this.name}", expr) 4895 4896 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4897 4898 return self.func("LOG", this, expr) 4899 4900 def use_sql(self, expression: exp.Use) -> str: 4901 kind = self.sql(expression, "kind") 4902 kind = f" {kind}" if kind else "" 4903 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4904 this = f" {this}" if this else "" 4905 return f"USE{kind}{this}" 4906 4907 def binary(self, expression: exp.Binary, op: str) -> str: 4908 sqls: list[str] = [] 4909 stack: list[None | str | exp.Expr] = [expression] 4910 binary_type = type(expression) 4911 is_is = binary_type is exp.Is and op in ("IS", "IS NOT") 4912 negate = expression.args.get("negate") 4913 4914 while stack: 4915 node = stack.pop() 4916 4917 if type(node) is binary_type and ( 4918 node is expression 4919 or (is_is and not isinstance(node.expression, exp.Boolean)) 4920 or node.args.get("negate") == negate 4921 ): 4922 op_func = node.args.get("operator") 4923 if op_func: 4924 op = f"OPERATOR({self.sql(op_func)})" 4925 elif is_is: 4926 op = "IS NOT" if node.args.get("negate") else "IS" 4927 4928 stack.append(node.args.get("expression")) 4929 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4930 stack.append(node.args.get("this")) 4931 else: 4932 sqls.append(self.sql(node)) 4933 4934 return "".join(sqls) 4935 4936 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4937 to_clause = self.sql(expression, "to") 4938 if to_clause: 4939 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4940 4941 return self.function_fallback_sql(expression) 4942 4943 def function_fallback_sql(self, expression: exp.Func) -> str: 4944 args = [] 4945 4946 for key in expression.arg_types: 4947 arg_value = expression.args.get(key) 4948 4949 if isinstance(arg_value, list): 4950 for value in arg_value: 4951 args.append(value) 4952 elif arg_value is not None: 4953 args.append(arg_value) 4954 4955 return self.func(self.function_name(expression), *args) 4956 4957 def function_name(self, expression: exp.Func) -> str: 4958 name_key = self.dialect.ORIGINAL_NAME_META_KEY 4959 return (expression.meta_get(name_key) if name_key else None) or expression.sql_name() 4960 4961 def func( 4962 self, 4963 name: str, 4964 *args: t.Any, 4965 prefix: str = "(", 4966 suffix: str = ")", 4967 normalize: bool = True, 4968 ) -> str: 4969 name = self.normalize_func(name) if normalize else name 4970 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4971 4972 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4973 arg_sqls = tuple( 4974 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4975 ) 4976 if self.pretty and self.too_wide(arg_sqls): 4977 return self.indent( 4978 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4979 ) 4980 return sep.join(arg_sqls) 4981 4982 def too_wide(self, args: t.Iterable) -> bool: 4983 return sum(len(arg) for arg in args) > self.max_text_width 4984 4985 def format_time( 4986 self, 4987 expression: exp.Expr, 4988 inverse_time_mapping: dict[str, str] | None = None, 4989 inverse_time_trie: dict | None = None, 4990 ) -> str | None: 4991 return format_time( 4992 self.sql(expression, "format"), 4993 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4994 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4995 ) 4996 4997 def expressions( 4998 self, 4999 expression: exp.Expr | None = None, 5000 key: str | None = None, 5001 sqls: t.Collection[str | exp.Expr] | None = None, 5002 flat: bool = False, 5003 indent: bool = True, 5004 skip_first: bool = False, 5005 skip_last: bool = False, 5006 sep: str = ", ", 5007 prefix: str = "", 5008 dynamic: bool = False, 5009 new_line: bool = False, 5010 ) -> str: 5011 expressions = expression.args.get(key or "expressions") if expression else sqls 5012 5013 if not expressions: 5014 return "" 5015 5016 if flat: 5017 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 5018 5019 num_sqls = len(expressions) 5020 result_sqls = [] 5021 5022 for i, e in enumerate(expressions): 5023 sql = self.sql(e, comment=False) 5024 if not sql: 5025 continue 5026 5027 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 5028 5029 if self.pretty: 5030 if self.leading_comma: 5031 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 5032 else: 5033 result_sqls.append( 5034 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 5035 ) 5036 else: 5037 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 5038 5039 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 5040 if new_line: 5041 result_sqls.insert(0, "") 5042 result_sqls.append("") 5043 result_sql = "\n".join(s.rstrip() for s in result_sqls) 5044 else: 5045 result_sql = "".join(result_sqls) 5046 5047 return ( 5048 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 5049 if indent 5050 else result_sql 5051 ) 5052 5053 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 5054 flat = flat or isinstance(expression.parent, exp.Properties) 5055 expressions_sql = self.expressions(expression, flat=flat) 5056 if flat: 5057 return f"{op} {expressions_sql}" 5058 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 5059 5060 def naked_property(self, expression: exp.Property) -> str: 5061 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 5062 if not property_name: 5063 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 5064 return f"{property_name} {self.sql(expression, 'this')}" 5065 5066 def tag_sql(self, expression: exp.Tag) -> str: 5067 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 5068 5069 def token_sql(self, token_type: TokenType) -> str: 5070 return self.TOKEN_MAPPING.get(token_type, token_type.name) 5071 5072 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 5073 this = self.sql(expression, "this") 5074 expressions = self.no_identify(self.expressions, expression) 5075 expressions = ( 5076 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 5077 ) 5078 return f"{this}{expressions}" if expressions.strip() != "" else this 5079 5080 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 5081 return self.expressions(expression, flat=True) 5082 5083 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 5084 params = self.no_identify(self.expressions, expression, flat=True) 5085 body = self.sql(expression, "this") 5086 prefix = "TABLE " if expression.args.get("is_table") else "" 5087 return f"({params}) AS {prefix}{body}" 5088 5089 def joinhint_sql(self, expression: exp.JoinHint) -> str: 5090 this = self.sql(expression, "this") 5091 expressions = self.expressions(expression, flat=True) 5092 return f"{this}({expressions})" 5093 5094 def kwarg_sql(self, expression: exp.Kwarg) -> str: 5095 return self.binary(expression, "=>") 5096 5097 def when_sql(self, expression: exp.When) -> str: 5098 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 5099 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 5100 condition = self.sql(expression, "condition") 5101 condition = f" AND {condition}" if condition else "" 5102 5103 then_expression = expression.args.get("then") 5104 if isinstance(then_expression, exp.Insert): 5105 this = ( 5106 "ALL BY NAME" 5107 if self.MERGE_ALL_BY_NAME and isinstance(then_expression.this, exp.Star) 5108 else self.sql(then_expression, "this") 5109 ) 5110 this = f"INSERT {this}" if this else "INSERT" 5111 then = self.sql(then_expression, "expression") 5112 then = f"{this} VALUES {then}" if then else this 5113 elif isinstance(then_expression, exp.Update): 5114 if isinstance(then_expression.args.get("expressions"), exp.Star): 5115 then = ( 5116 "UPDATE ALL BY NAME" 5117 if self.MERGE_ALL_BY_NAME 5118 else f"UPDATE {self.sql(then_expression, 'expressions')}" 5119 ) 5120 elif ( 5121 self.MERGE_ALL_BY_NAME 5122 and len(then_expression.expressions) == 1 5123 and isinstance(then_expression.expressions[0], exp.Star) 5124 ): 5125 then = "UPDATE ALL BY NAME" 5126 else: 5127 expressions_sql = self.expressions(then_expression) 5128 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 5129 else: 5130 then = self.sql(then_expression) 5131 5132 if isinstance(then_expression, (exp.Insert, exp.Update)): 5133 where = self.sql(then_expression, "where") 5134 if where and not self.SUPPORTS_MERGE_WHERE: 5135 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 5136 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 5137 where = "" 5138 then = f"{then}{where}" 5139 return f"WHEN {matched}{source}{condition} THEN {then}" 5140 5141 def whens_sql(self, expression: exp.Whens) -> str: 5142 return self.expressions(expression, sep=" ", indent=False) 5143 5144 def merge_sql(self, expression: exp.Merge) -> str: 5145 table = expression.this 5146 table_alias = "" 5147 5148 hints = table.args.get("hints") 5149 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 5150 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 5151 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 5152 5153 this = self.sql(table) 5154 using = f"USING {self.sql(expression, 'using')}" 5155 whens = self.sql(expression, "whens") 5156 5157 on = self.sql(expression, "on") 5158 on = f"ON {on}" if on else "" 5159 5160 if not on: 5161 on = self.expressions(expression, key="using_cond") 5162 on = f"USING ({on})" if on else "" 5163 5164 returning = self.sql(expression, "returning") 5165 if returning: 5166 whens = f"{whens}{returning}" 5167 5168 sep = self.sep() 5169 5170 return self.prepend_ctes( 5171 expression, 5172 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5173 ) 5174 5175 @unsupported_args("format") 5176 def tochar_sql(self, expression: exp.ToChar) -> str: 5177 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 5178 5179 @unsupported_args("default") 5180 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5181 if not self.SUPPORTS_TO_NUMBER: 5182 self.unsupported("Unsupported TO_NUMBER function") 5183 return self._tonumber_cast_sql(expression) 5184 5185 fmt = expression.args.get("format") 5186 if not fmt: 5187 self.unsupported("Conversion format is required for TO_NUMBER") 5188 return self._tonumber_cast_sql(expression) 5189 5190 return self.func("TO_NUMBER", expression.this, fmt) 5191 5192 def _tonumber_cast_sql(self, expression: exp.ToNumber) -> str: 5193 if expression.args.get("safe"): 5194 return self.sql(exp.TryCast(this=expression.this, to=exp.DataType.build("DOUBLE"))) 5195 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 5196 5197 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5198 this = self.sql(expression, "this") 5199 kind = self.sql(expression, "kind") 5200 settings_sql = self.expressions(expression, key="settings", sep=" ") 5201 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5202 return f"{this}({kind}{args})" 5203 5204 def dictrange_sql(self, expression: exp.DictRange) -> str: 5205 this = self.sql(expression, "this") 5206 max = self.sql(expression, "max") 5207 min = self.sql(expression, "min") 5208 return f"{this}(MIN {min} MAX {max})" 5209 5210 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 5211 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 5212 5213 def aggregatekeyproperty_sql(self, expression: exp.AggregateKeyProperty) -> str: 5214 return f"AGGREGATE KEY ({self.expressions(expression, flat=True)})" 5215 5216 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 5217 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 5218 5219 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 5220 def uniquekeyproperty_sql( 5221 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 5222 ) -> str: 5223 return f"{prefix} ({self.expressions(expression, flat=True)})" 5224 5225 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 5226 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5227 expressions = self.expressions(expression, flat=True) 5228 expressions = f" {self.wrap(expressions)}" if expressions else "" 5229 buckets = self.sql(expression, "buckets") 5230 kind = self.sql(expression, "kind") 5231 buckets = f" BUCKETS {buckets}" if buckets else "" 5232 order = self.sql(expression, "order") 5233 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 5234 5235 def oncluster_sql(self, expression: exp.OnCluster) -> str: 5236 return "" 5237 5238 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5239 expressions = self.expressions(expression, key="expressions", flat=True) 5240 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5241 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5242 buckets = self.sql(expression, "buckets") 5243 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 5244 5245 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5246 this = self.sql(expression, "this") 5247 having = self.sql(expression, "having") 5248 5249 if having: 5250 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5251 5252 return self.func("ANY_VALUE", this) 5253 5254 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5255 transform = self.func("TRANSFORM", *expression.expressions) 5256 row_format_before = self.sql(expression, "row_format_before") 5257 row_format_before = f" {row_format_before}" if row_format_before else "" 5258 record_writer = self.sql(expression, "record_writer") 5259 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5260 using = f" USING {self.sql(expression, 'command_script')}" 5261 schema = self.sql(expression, "schema") 5262 schema = f" AS {schema}" if schema else "" 5263 row_format_after = self.sql(expression, "row_format_after") 5264 row_format_after = f" {row_format_after}" if row_format_after else "" 5265 record_reader = self.sql(expression, "record_reader") 5266 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5267 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 5268 5269 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5270 key_block_size = self.sql(expression, "key_block_size") 5271 if key_block_size: 5272 return f"KEY_BLOCK_SIZE = {key_block_size}" 5273 5274 using = self.sql(expression, "using") 5275 if using: 5276 return f"USING {using}" 5277 5278 parser = self.sql(expression, "parser") 5279 if parser: 5280 return f"WITH PARSER {parser}" 5281 5282 comment = self.sql(expression, "comment") 5283 if comment: 5284 return f"COMMENT {comment}" 5285 5286 visible = expression.args.get("visible") 5287 if visible is not None: 5288 return "VISIBLE" if visible else "INVISIBLE" 5289 5290 engine_attr = self.sql(expression, "engine_attr") 5291 if engine_attr: 5292 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5293 5294 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5295 if secondary_engine_attr: 5296 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5297 5298 self.unsupported("Unsupported index constraint option.") 5299 return "" 5300 5301 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 5302 enforced = " ENFORCED" if expression.args.get("enforced") else "" 5303 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 5304 5305 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5306 kind = self.sql(expression, "kind") 5307 kind = f"{kind} INDEX" if kind else "INDEX" 5308 this = self.sql(expression, "this") 5309 this = f" {this}" if this else "" 5310 index_type = self.sql(expression, "index_type") 5311 index_type = f" USING {index_type}" if index_type else "" 5312 expressions = self.expressions(expression, flat=True) 5313 expressions = f" ({expressions})" if expressions else "" 5314 options = self.expressions(expression, key="options", sep=" ") 5315 options = f" {options}" if options else "" 5316 return f"{kind}{this}{index_type}{expressions}{options}" 5317 5318 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5319 if self.NVL2_SUPPORTED: 5320 return self.function_fallback_sql(expression) 5321 5322 case = exp.Case().when( 5323 expression.this.is_(exp.null()).not_(copy=False), 5324 expression.args["true"], 5325 copy=False, 5326 ) 5327 else_cond = expression.args.get("false") 5328 if else_cond: 5329 case.else_(else_cond, copy=False) 5330 5331 return self.sql(case) 5332 5333 def nthvalue_sql(self, expression: exp.NthValue) -> str: 5334 if expression.args.get("from_first") is False: 5335 self.unsupported("NTH_VALUE FROM LAST is not supported") 5336 5337 return self.function_fallback_sql(expression) 5338 5339 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5340 this = self.sql(expression, "this") 5341 expr = self.sql(expression, "expression") 5342 position = self.sql(expression, "position") 5343 position = f", {position}" if position else "" 5344 iterator = self.sql(expression, "iterator") 5345 condition = self.sql(expression, "condition") 5346 condition = f" IF {condition}" if condition else "" 5347 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5348 5349 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5350 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5351 5352 def opclass_sql(self, expression: exp.Opclass) -> str: 5353 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5354 5355 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5356 model = self.sql(expression, "this") 5357 model = f"MODEL {model}" 5358 expr = expression.expression 5359 if expr: 5360 expr_sql = self.sql(expression, "expression") 5361 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5362 else: 5363 expr_sql = None 5364 5365 parameters = self.sql(expression, "params_struct") or None 5366 5367 return self.func(name, model, expr_sql, parameters) 5368 5369 def predict_sql(self, expression: exp.Predict) -> str: 5370 return self._ml_sql(expression, "PREDICT") 5371 5372 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5373 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5374 return self._ml_sql(expression, name) 5375 5376 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5377 return self._ml_sql(expression, "GENERATE_TEXT") 5378 5379 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5380 return self._ml_sql(expression, "GENERATE_TABLE") 5381 5382 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5383 return self._ml_sql(expression, "GENERATE_BOOL") 5384 5385 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5386 return self._ml_sql(expression, "GENERATE_INT") 5387 5388 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5389 return self._ml_sql(expression, "GENERATE_DOUBLE") 5390 5391 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5392 return self._ml_sql(expression, "TRANSLATE") 5393 5394 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5395 return self._ml_sql(expression, "FORECAST") 5396 5397 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5398 this_sql = self.sql(expression, "this") 5399 if isinstance(expression.this, exp.Table): 5400 this_sql = f"TABLE {this_sql}" 5401 5402 return self.func( 5403 "FORECAST", 5404 this_sql, 5405 expression.args.get("data_col"), 5406 expression.args.get("timestamp_col"), 5407 expression.args.get("model"), 5408 expression.args.get("id_cols"), 5409 expression.args.get("horizon"), 5410 expression.args.get("forecast_end_timestamp"), 5411 expression.args.get("confidence_level"), 5412 expression.args.get("output_historical_time_series"), 5413 expression.args.get("context_window"), 5414 ) 5415 5416 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5417 this_sql = self.sql(expression, "this") 5418 if isinstance(expression.this, exp.Table): 5419 this_sql = f"TABLE {this_sql}" 5420 5421 return self.func( 5422 "FEATURES_AT_TIME", 5423 this_sql, 5424 expression.args.get("time"), 5425 expression.args.get("num_rows"), 5426 expression.args.get("ignore_feature_nulls"), 5427 ) 5428 5429 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5430 this_sql = self.sql(expression, "this") 5431 if isinstance(expression.this, exp.Table): 5432 this_sql = f"TABLE {this_sql}" 5433 5434 query_table = self.sql(expression, "query_table") 5435 if isinstance(expression.args["query_table"], exp.Table): 5436 query_table = f"TABLE {query_table}" 5437 5438 return self.func( 5439 "VECTOR_SEARCH", 5440 this_sql, 5441 expression.args.get("column_to_search"), 5442 query_table, 5443 expression.args.get("query_column_to_search"), 5444 expression.args.get("top_k"), 5445 expression.args.get("distance_type"), 5446 expression.args.get("options"), 5447 ) 5448 5449 def forin_sql(self, expression: exp.ForIn) -> str: 5450 this = self.sql(expression, "this") 5451 expression_sql = self.sql(expression, "expression") 5452 return f"FOR {this} DO {expression_sql}" 5453 5454 @unsupported_args("partition_start", "partition_end", "force", "mode") 5455 def refresh_sql(self, expression: exp.Refresh) -> str: 5456 this = self.sql(expression, "this") 5457 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5458 return f"REFRESH {kind}{this}" 5459 5460 def toarray_sql(self, expression: exp.ToArray) -> str: 5461 arg = expression.this 5462 if not arg.type: 5463 import sqlglot.optimizer.annotate_types 5464 5465 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5466 5467 if arg.is_type(exp.DType.ARRAY): 5468 return self.sql(arg) 5469 5470 cond_for_null = arg.is_(exp.null()) 5471 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5472 5473 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5474 this = expression.this 5475 time_format = self.format_time(expression) 5476 5477 if time_format: 5478 return self.sql( 5479 exp.cast( 5480 exp.StrToTime(this=this, format=expression.args["format"]), 5481 exp.DType.TIME, 5482 ) 5483 ) 5484 5485 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5486 return self.sql(this) 5487 5488 return self.sql(exp.cast(this, exp.DType.TIME)) 5489 5490 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5491 this = expression.this 5492 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5493 return self.sql(this) 5494 5495 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5496 5497 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5498 this = expression.this 5499 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5500 return self.sql(this) 5501 5502 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5503 5504 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5505 this = expression.this 5506 time_format = self.format_time(expression) 5507 safe = expression.args.get("safe") 5508 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5509 return self.sql( 5510 exp.cast( 5511 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5512 exp.DType.DATE, 5513 ) 5514 ) 5515 5516 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5517 return self.sql(this) 5518 5519 if safe: 5520 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5521 5522 return self.sql(exp.cast(this, exp.DType.DATE)) 5523 5524 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5525 return self.sql( 5526 exp.func( 5527 "DATEDIFF", 5528 expression.this, 5529 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5530 "day", 5531 ) 5532 ) 5533 5534 def lastday_sql(self, expression: exp.LastDay) -> str: 5535 if self.LAST_DAY_SUPPORTS_DATE_PART: 5536 return self.function_fallback_sql(expression) 5537 5538 unit = expression.args.get("unit") 5539 if unit and unit.name.upper() != "MONTH": 5540 self.unsupported("Date parts are not supported in LAST_DAY.") 5541 5542 return self.func("LAST_DAY", expression.this) 5543 5544 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5545 import sqlglot.dialects.dialect 5546 5547 return self.func( 5548 "DATE_ADD", 5549 expression.this, 5550 expression.expression, 5551 sqlglot.dialects.dialect.unit_to_str(expression), 5552 ) 5553 5554 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5555 this = expression.this 5556 position = expression.args["position"] 5557 offset = index_offset - (expression.args.get("offset") or 0) 5558 5559 if offset: 5560 if position.is_int: 5561 value = position.to_py() 5562 if value >= 0: 5563 position = exp.Literal.number(value + offset) 5564 elif offset < 0 and value == -1: 5565 # 1-based -1 appends, which a 0-based position can only express as the size 5566 position = exp.ArraySize(this=this.copy()) 5567 else: 5568 # Negative positions count from the end, so they shift in the opposite 5569 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5570 position = exp.Literal.number(value - offset) 5571 else: 5572 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5573 5574 return self.func("ARRAY_INSERT", this, position, expression.expression) 5575 5576 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5577 if self.CAN_IMPLEMENT_ARRAY_ANY: 5578 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5579 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5580 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5581 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5582 5583 import sqlglot.dialects.dialect 5584 5585 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5586 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5587 self.unsupported("ARRAY_ANY is unsupported") 5588 5589 return self.function_fallback_sql(expression) 5590 5591 def struct_sql(self, expression: exp.Struct) -> str: 5592 expression.set( 5593 "expressions", 5594 [ 5595 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5596 if isinstance(e, exp.PropertyEQ) 5597 else e 5598 for e in expression.expressions 5599 ], 5600 ) 5601 5602 return self.function_fallback_sql(expression) 5603 5604 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5605 low = self.sql(expression, "this") 5606 high = self.sql(expression, "expression") 5607 5608 return f"{low} TO {high}" 5609 5610 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5611 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5612 tables = f" {self.expressions(expression)}" 5613 5614 exists = " IF EXISTS" if expression.args.get("exists") else "" 5615 5616 on_cluster = self.sql(expression, "cluster") 5617 on_cluster = f" {on_cluster}" if on_cluster else "" 5618 5619 identity = self.sql(expression, "identity") 5620 identity = f" {identity} IDENTITY" if identity else "" 5621 5622 option = self.sql(expression, "option") 5623 option = f" {option}" if option else "" 5624 5625 partition = self.sql(expression, "partition") 5626 partition = f" {partition}" if partition else "" 5627 5628 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5629 5630 # This transpiles T-SQL's CONVERT function 5631 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5632 def convert_sql(self, expression: exp.Convert) -> str: 5633 to = expression.this 5634 value = expression.expression 5635 style = expression.args.get("style") 5636 safe = expression.args.get("safe") 5637 strict = expression.args.get("strict") 5638 5639 if not to or not value: 5640 return "" 5641 5642 # Retrieve length of datatype and override to default if not specified 5643 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5644 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5645 5646 transformed: exp.Expr | None = None 5647 cast = exp.Cast if strict else exp.TryCast 5648 5649 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5650 if isinstance(style, exp.Literal) and style.is_int: 5651 import sqlglot.dialects.tsql 5652 5653 style_value = style.name 5654 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5655 if not converted_style: 5656 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5657 5658 fmt = exp.Literal.string(converted_style) 5659 5660 if to.this == exp.DType.DATE: 5661 transformed = exp.StrToDate(this=value, format=fmt) 5662 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5663 transformed = exp.StrToTime(this=value, format=fmt) 5664 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5665 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5666 elif to.this == exp.DType.TEXT: 5667 transformed = exp.TimeToStr(this=value, format=fmt) 5668 5669 if not transformed: 5670 transformed = cast(this=value, to=to, safe=safe) 5671 5672 return self.sql(transformed) 5673 5674 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5675 this = expression.this 5676 if isinstance(this, exp.JSONPathWildcard): 5677 this = self.json_path_part(this) 5678 return f".{this}" if this else "" 5679 5680 quoted = expression.args.get("quoted") 5681 if not ( 5682 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5683 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5684 return f".{this}" 5685 5686 this = self.json_path_part(this) 5687 5688 return ( 5689 f"[{this}]" 5690 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5691 else f".{this}" 5692 ) 5693 5694 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5695 this = self.json_path_part(expression.this) 5696 return f"[{this}]" if this else "" 5697 5698 def _simplify_unless_literal(self, expression: E) -> E: 5699 if not isinstance(expression, exp.Literal): 5700 import sqlglot.optimizer.simplify 5701 5702 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5703 5704 return expression 5705 5706 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5707 this = expression.this 5708 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5709 self.unsupported( 5710 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5711 ) 5712 return self.sql(this) 5713 5714 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5715 if self.IGNORE_NULLS_BEFORE_ORDER: 5716 from sqlglot.optimizer.scope import find_all_in_scope 5717 5718 # The first modifier here will be the one closest to the AggFunc's arg 5719 mods = sorted( 5720 find_all_in_scope(expression, exp.HavingMax, exp.Order, exp.Limit), 5721 key=lambda x: ( 5722 0 5723 if isinstance(x, exp.HavingMax) 5724 else (1 if isinstance(x, exp.Order) else 2) 5725 ), 5726 ) 5727 5728 if mods: 5729 mod = mods[0] 5730 this = expression.__class__(this=mod.this.copy()) 5731 this.meta["inline"] = True 5732 mod.this.replace(this) 5733 return self.sql(expression.this) 5734 5735 agg_func = expression.find(exp.AggFunc) 5736 5737 if agg_func: 5738 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5739 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5740 5741 return f"{self.sql(expression, 'this')} {text}" 5742 5743 def _replace_line_breaks(self, string: str) -> str: 5744 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5745 if self.pretty: 5746 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5747 return string 5748 5749 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5750 option = self.sql(expression, "this") 5751 5752 if expression.expressions: 5753 upper = option.upper() 5754 5755 # Snowflake FILE_FORMAT options are separated by whitespace 5756 sep = " " if upper == "FILE_FORMAT" else ", " 5757 5758 # Databricks copy/format options do not set their list of values with EQ 5759 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5760 values = self.expressions(expression, flat=True, sep=sep) 5761 return f"{option}{op}({values})" 5762 5763 value = self.sql(expression, "expression") 5764 5765 if not value: 5766 return option 5767 5768 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5769 5770 return f"{option}{op}{value}" 5771 5772 def credentials_sql(self, expression: exp.Credentials) -> str: 5773 cred_expr = expression.args.get("credentials") 5774 if isinstance(cred_expr, exp.Literal): 5775 # Redshift case: CREDENTIALS <string> 5776 credentials = self.sql(expression, "credentials") 5777 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5778 else: 5779 # Snowflake case: CREDENTIALS = (...) 5780 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5781 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5782 5783 storage = self.sql(expression, "storage") 5784 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5785 5786 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5787 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5788 5789 iam_role = self.sql(expression, "iam_role") 5790 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5791 5792 region = self.sql(expression, "region") 5793 region = f" REGION {region}" if region else "" 5794 5795 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5796 5797 def copy_sql(self, expression: exp.Copy) -> str: 5798 this = self.sql(expression, "this") 5799 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5800 5801 credentials = self.sql(expression, "credentials") 5802 credentials = self.seg(credentials) if credentials else "" 5803 files = self.expressions(expression, key="files", flat=True) 5804 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5805 5806 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5807 params = self.expressions( 5808 expression, 5809 key="params", 5810 sep=sep, 5811 new_line=True, 5812 skip_last=True, 5813 skip_first=True, 5814 indent=self.COPY_PARAMS_ARE_WRAPPED, 5815 ) 5816 5817 if params: 5818 if self.COPY_PARAMS_ARE_WRAPPED: 5819 params = f" WITH ({params})" 5820 elif not self.pretty and (files or credentials): 5821 params = f" {params}" 5822 5823 return f"COPY{this}{kind} {files}{credentials}{params}" 5824 5825 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5826 return "" 5827 5828 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5829 on_sql = "ON" if expression.args.get("on") else "OFF" 5830 filter_col: str | None = self.sql(expression, "filter_column") 5831 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5832 retention_period: str | None = self.sql(expression, "retention_period") 5833 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5834 5835 if filter_col or retention_period: 5836 on_sql = self.func("ON", filter_col, retention_period) 5837 5838 return f"DATA_DELETION={on_sql}" 5839 5840 def maskingpolicycolumnconstraint_sql( 5841 self, expression: exp.MaskingPolicyColumnConstraint 5842 ) -> str: 5843 this = self.sql(expression, "this") 5844 expressions = self.expressions(expression, flat=True) 5845 expressions = f" USING ({expressions})" if expressions else "" 5846 return f"MASKING POLICY {this}{expressions}" 5847 5848 def gapfill_sql(self, expression: exp.GapFill) -> str: 5849 this = self.sql(expression, "this") 5850 this = f"TABLE {this}" 5851 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5852 5853 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5854 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5855 5856 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5857 this = self.sql(expression, "this") 5858 expr = expression.expression 5859 5860 if isinstance(expr, exp.Func): 5861 # T-SQL's CLR functions are case sensitive 5862 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5863 else: 5864 expr = self.sql(expression, "expression") 5865 5866 return self.scope_resolution(expr, this) 5867 5868 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5869 if self.PARSE_JSON_NAME is None: 5870 return self.sql(expression.this) 5871 5872 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5873 5874 def rand_sql(self, expression: exp.Rand) -> str: 5875 lower = self.sql(expression, "lower") 5876 upper = self.sql(expression, "upper") 5877 5878 if lower and upper: 5879 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5880 return self.func("RAND", expression.this) 5881 5882 def changes_sql(self, expression: exp.Changes) -> str: 5883 information = self.sql(expression, "information") 5884 information = f"INFORMATION => {information}" 5885 at_before = self.sql(expression, "at_before") 5886 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5887 end = self.sql(expression, "end") 5888 end = f"{self.seg('')}{end}" if end else "" 5889 5890 return f"CHANGES ({information}){at_before}{end}" 5891 5892 def pad_sql(self, expression: exp.Pad) -> str: 5893 prefix = "L" if expression.args.get("is_left") else "R" 5894 5895 fill_pattern = self.sql(expression, "fill_pattern") or None 5896 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5897 fill_pattern = "' '" 5898 5899 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5900 5901 def summarize_sql(self, expression: exp.Summarize) -> str: 5902 table = " TABLE" if expression.args.get("table") else "" 5903 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5904 5905 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5906 generate_series = exp.GenerateSeries(**expression.args) 5907 5908 parent = expression.parent 5909 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5910 parent = parent.parent 5911 5912 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5913 return self.sql(exp.Unnest(expressions=[generate_series])) 5914 5915 if isinstance(parent, exp.Select): 5916 self.unsupported("GenerateSeries projection unnesting is not supported.") 5917 5918 return self.sql(generate_series) 5919 5920 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5921 if self.SUPPORTS_CONVERT_TIMEZONE: 5922 return self.function_fallback_sql(expression) 5923 5924 source_tz = expression.args.get("source_tz") 5925 target_tz = expression.args.get("target_tz") 5926 timestamp = expression.args.get("timestamp") 5927 5928 if source_tz and timestamp: 5929 timestamp = exp.AtTimeZone( 5930 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5931 ) 5932 5933 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5934 5935 return self.sql(expr) 5936 5937 def json_sql(self, expression: exp.JSON) -> str: 5938 this = self.sql(expression, "this") 5939 this = f" {this}" if this else "" 5940 5941 _with = expression.args.get("with_") 5942 5943 if _with is None: 5944 with_sql = "" 5945 elif not _with: 5946 with_sql = " WITHOUT" 5947 else: 5948 with_sql = " WITH" 5949 5950 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5951 5952 return f"JSON{this}{with_sql}{unique_sql}" 5953 5954 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5955 path = self.sql(expression, "path") 5956 returning = self.sql(expression, "returning") 5957 returning = f" RETURNING {returning}" if returning else "" 5958 5959 on_condition = self.sql(expression, "on_condition") 5960 on_condition = f" {on_condition}" if on_condition else "" 5961 5962 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5963 5964 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5965 regexp = " REGEXP" if expression.args.get("regexp") else "" 5966 return f"SKIP{regexp} {self.sql(expression.expression)}" 5967 5968 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5969 else_ = "ELSE " if expression.args.get("else_") else "" 5970 condition = self.sql(expression, "expression") 5971 condition = f"WHEN {condition} THEN " if condition else else_ 5972 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5973 return f"{condition}{insert}" 5974 5975 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5976 kind = self.sql(expression, "kind") 5977 source = self.sql(expression, "source") 5978 5979 if kind: 5980 expressions = self.seg(self.expressions(expression, sep=" ")) 5981 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5982 else: 5983 # If kind isn't present, we have Hive's FROM-first form: 5984 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5985 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5986 sql = f"FROM {source}{self.seg(inserts)}" 5987 5988 return self.prepend_ctes(expression, sql) 5989 5990 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5991 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5992 empty = expression.args.get("empty") 5993 empty = ( 5994 f"DEFAULT {empty} ON EMPTY" 5995 if isinstance(empty, exp.Expr) 5996 else self.sql(expression, "empty") 5997 ) 5998 5999 error = expression.args.get("error") 6000 error = ( 6001 f"DEFAULT {error} ON ERROR" 6002 if isinstance(error, exp.Expr) 6003 else self.sql(expression, "error") 6004 ) 6005 6006 if error and empty: 6007 error = ( 6008 f"{empty} {error}" 6009 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 6010 else f"{error} {empty}" 6011 ) 6012 empty = "" 6013 6014 null = self.sql(expression, "null") 6015 6016 return f"{empty}{error}{null}" 6017 6018 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 6019 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 6020 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 6021 6022 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 6023 this = self.sql(expression, "this") 6024 path = self.sql(expression, "path") 6025 6026 passing = self.expressions(expression, "passing") 6027 passing = f" PASSING {passing}" if passing else "" 6028 6029 on_condition = self.sql(expression, "on_condition") 6030 on_condition = f" {on_condition}" if on_condition else "" 6031 6032 path = f"{path}{passing}{on_condition}" 6033 6034 return self.func("JSON_EXISTS", this, path) 6035 6036 def _add_arrayagg_null_filter( 6037 self, 6038 array_agg_sql: str, 6039 array_agg_expr: exp.ArrayAgg, 6040 column_expr: exp.Expr, 6041 ) -> str: 6042 """ 6043 Add NULL filter to ARRAY_AGG if dialect requires it. 6044 6045 Args: 6046 array_agg_sql: The generated ARRAY_AGG SQL string 6047 array_agg_expr: The ArrayAgg expression node 6048 column_expr: The column/expression to filter (before ORDER BY wrapping) 6049 6050 Returns: 6051 SQL string with FILTER clause added if needed 6052 """ 6053 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 6054 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 6055 if not ( 6056 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 6057 ): 6058 return array_agg_sql 6059 6060 parent = array_agg_expr.parent 6061 if isinstance(parent, exp.Filter): 6062 parent_cond = parent.expression.this 6063 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 6064 elif column_expr.find(exp.Column): 6065 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 6066 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 6067 this_sql = ( 6068 self.expressions(column_expr) 6069 if isinstance(column_expr, exp.Distinct) 6070 else self.sql(column_expr) 6071 ) 6072 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 6073 6074 return array_agg_sql 6075 6076 @unsupported_args("count") 6077 def min_sql(self, expression: exp.Min) -> str: 6078 return self.function_fallback_sql(expression) 6079 6080 @unsupported_args("count") 6081 def max_sql(self, expression: exp.Max) -> str: 6082 return self.function_fallback_sql(expression) 6083 6084 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 6085 array_agg = self.function_fallback_sql(expression) 6086 column_expr = expression.this 6087 if isinstance(column_expr, exp.Order): 6088 column_expr = column_expr.this 6089 6090 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 6091 6092 def timeslice_sql(self, expression: exp.TimeSlice) -> str: 6093 # Only the SQLGlot dialect serializes the internal origin for round-tripping. 6094 origin = expression.args.get("origin") if self.dialect == "" else None 6095 kind = expression.args.get("kind") 6096 if origin is not None and kind is None: 6097 kind = exp.Literal.string("START") 6098 6099 return self.func( 6100 "TIME_SLICE", expression.this, expression.expression, expression.unit, kind, origin 6101 ) 6102 6103 def slice_sql(self, expression: exp.Slice) -> str: 6104 if "[" in self.dialect.tokenizer_class._IDENTIFIERS: 6105 self.unsupported("Slices are not supported") 6106 6107 step = self.sql(expression, "step") 6108 # An omitted end is written as `-` before a step, since `::` is the cast operator 6109 end = self.sql(expression.expression) or ("-" if step else "") 6110 begin = self.sql(expression.this) 6111 6112 sql = f"{end}:{step}" if step else end 6113 return f"{begin}:{sql}" if sql else f"{begin}:" 6114 6115 def apply_sql(self, expression: exp.Apply) -> str: 6116 this = self.sql(expression, "this") 6117 expr = self.sql(expression, "expression") 6118 6119 return f"{this} APPLY({expr})" 6120 6121 def _grant_or_revoke_sql( 6122 self, 6123 expression: exp.Grant | exp.Revoke, 6124 keyword: str, 6125 preposition: str, 6126 grant_option_prefix: str = "", 6127 grant_option_suffix: str = "", 6128 ) -> str: 6129 privileges_sql = self.expressions(expression, key="privileges", flat=True) 6130 6131 kind = self.sql(expression, "kind") 6132 kind = f" {kind}" if kind else "" 6133 6134 securable = self.sql(expression, "securable") 6135 securable = f" {securable}" if securable else "" 6136 6137 principals = self.expressions(expression, key="principals", flat=True) 6138 6139 if not expression.args.get("grant_option"): 6140 grant_option_prefix = grant_option_suffix = "" 6141 6142 # cascade for revoke only 6143 cascade = self.sql(expression, "cascade") 6144 cascade = f" {cascade}" if cascade else "" 6145 6146 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 6147 6148 def grant_sql(self, expression: exp.Grant) -> str: 6149 return self._grant_or_revoke_sql( 6150 expression, 6151 keyword="GRANT", 6152 preposition="TO", 6153 grant_option_suffix=" WITH GRANT OPTION", 6154 ) 6155 6156 def revoke_sql(self, expression: exp.Revoke) -> str: 6157 return self._grant_or_revoke_sql( 6158 expression, 6159 keyword="REVOKE", 6160 preposition="FROM", 6161 grant_option_prefix="GRANT OPTION FOR ", 6162 ) 6163 6164 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 6165 this = self.sql(expression, "this") 6166 columns = self.expressions(expression, flat=True) 6167 columns = f"({columns})" if columns else "" 6168 6169 return f"{this}{columns}" 6170 6171 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 6172 this = self.sql(expression, "this") 6173 6174 kind = self.sql(expression, "kind") 6175 kind = f"{kind} " if kind else "" 6176 6177 return f"{kind}{this}" 6178 6179 def columns_sql(self, expression: exp.Columns) -> str: 6180 func = self.function_fallback_sql(expression) 6181 if expression.args.get("unpack"): 6182 func = f"*{func}" 6183 6184 return func 6185 6186 def overlay_sql(self, expression: exp.Overlay) -> str: 6187 this = self.sql(expression, "this") 6188 expr = self.sql(expression, "expression") 6189 from_sql = self.sql(expression, "from_") 6190 for_sql = self.sql(expression, "for_") 6191 for_sql = f" FOR {for_sql}" if for_sql else "" 6192 6193 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 6194 6195 @unsupported_args("format") 6196 def todouble_sql(self, expression: exp.ToDouble) -> str: 6197 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 6198 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 6199 6200 def string_sql(self, expression: exp.String) -> str: 6201 this = expression.this 6202 zone = expression.args.get("zone") 6203 6204 if zone: 6205 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6206 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6207 # set for source_tz to transpile the time conversion before the STRING cast 6208 this = exp.ConvertTimezone( 6209 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6210 ) 6211 6212 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 6213 6214 def median_sql(self, expression: exp.Median) -> str: 6215 if not self.SUPPORTS_MEDIAN: 6216 return self.sql( 6217 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 6218 ) 6219 6220 return self.function_fallback_sql(expression) 6221 6222 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6223 filler = self.sql(expression, "this") 6224 filler = f" {filler}" if filler else "" 6225 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6226 return f"TRUNCATE{filler} {with_count}" 6227 6228 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6229 if self.SUPPORTS_UNIX_SECONDS: 6230 return self.function_fallback_sql(expression) 6231 6232 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6233 6234 return self.sql( 6235 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6236 ) 6237 6238 def arraysort_sql(self, expression: exp.ArraySort) -> str: 6239 asc = expression.args.get("asc") 6240 nulls_first = expression.args.get("nulls_first") 6241 if (asc and asc != exp.true()) or (nulls_first and nulls_first != exp.false()): 6242 self.unsupported("ARRAY_SORT only sorts ascending with NULLs last") 6243 return self.func("ARRAY_SORT", expression.this, expression.expression) 6244 6245 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6246 dim = expression.expression 6247 6248 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6249 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6250 if not (dim.is_int and dim.name == "1"): 6251 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6252 dim = None 6253 6254 # If dimension is required but not specified, default initialize it 6255 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6256 dim = exp.Literal.number(1) 6257 6258 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 6259 6260 def attach_sql(self, expression: exp.Attach) -> str: 6261 this = self.sql(expression, "this") 6262 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6263 expressions = self.expressions(expression) 6264 expressions = f" ({expressions})" if expressions else "" 6265 6266 return f"ATTACH{exists_sql} {this}{expressions}" 6267 6268 def detach_sql(self, expression: exp.Detach) -> str: 6269 kind = self.sql(expression, "kind") 6270 kind = f" {kind}" if kind else "" 6271 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6272 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6273 exists = " IF EXISTS" if expression.args.get("exists") else "" 6274 if exists: 6275 kind = kind or " DATABASE" 6276 6277 this = self.sql(expression, "this") 6278 this = f" {this}" if this else "" 6279 cluster = self.sql(expression, "cluster") 6280 cluster = f" {cluster}" if cluster else "" 6281 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6282 sync = " SYNC" if expression.args.get("sync") else "" 6283 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 6284 6285 def attachoption_sql(self, expression: exp.AttachOption) -> str: 6286 this = self.sql(expression, "this") 6287 value = self.sql(expression, "expression") 6288 value = f" {value}" if value else "" 6289 return f"{this}{value}" 6290 6291 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 6292 return ( 6293 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 6294 ) 6295 6296 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6297 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6298 encode = f"{encode} {self.sql(expression, 'this')}" 6299 6300 properties = expression.args.get("properties") 6301 if properties: 6302 encode = f"{encode} {self.properties(properties)}" 6303 6304 return encode 6305 6306 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6307 this = self.sql(expression, "this") 6308 include = f"INCLUDE {this}" 6309 6310 column_def = self.sql(expression, "column_def") 6311 if column_def: 6312 include = f"{include} {column_def}" 6313 6314 alias = self.sql(expression, "alias") 6315 if alias: 6316 include = f"{include} AS {alias}" 6317 6318 return include 6319 6320 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 6321 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 6322 name = f"{prefix} {self.sql(expression, 'this')}" 6323 return self.func("XMLELEMENT", name, *expression.expressions) 6324 6325 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 6326 this = self.sql(expression, "this") 6327 expr = self.sql(expression, "expression") 6328 expr = f"({expr})" if expr else "" 6329 return f"{this}{expr}" 6330 6331 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6332 partitions = self.expressions(expression, "partition_expressions") 6333 create = self.expressions(expression, "create_expressions") 6334 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 6335 6336 def partitionbyrangepropertydynamic_sql( 6337 self, expression: exp.PartitionByRangePropertyDynamic 6338 ) -> str: 6339 start = self.sql(expression, "start") 6340 end = self.sql(expression, "end") 6341 6342 every = expression.args["every"] 6343 if isinstance(every, exp.Interval) and every.this.is_string: 6344 every.this.replace(exp.Literal.number(every.name)) 6345 6346 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 6347 6348 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 6349 name = self.sql(expression, "this") 6350 values = self.expressions(expression, flat=True) 6351 6352 return f"NAME {name} VALUE {values}" 6353 6354 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 6355 kind = self.sql(expression, "kind") 6356 sample = self.sql(expression, "sample") 6357 return f"SAMPLE {sample} {kind}" 6358 6359 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6360 kind = self.sql(expression, "kind") 6361 option = self.sql(expression, "option") 6362 option = f" {option}" if option else "" 6363 this = self.sql(expression, "this") 6364 this = f" {this}" if this else "" 6365 columns = self.expressions(expression) 6366 columns = f" {columns}" if columns else "" 6367 return f"{kind}{option} STATISTICS{this}{columns}" 6368 6369 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6370 this = self.sql(expression, "this") 6371 columns = self.expressions(expression) 6372 inner_expression = self.sql(expression, "expression") 6373 inner_expression = f" {inner_expression}" if inner_expression else "" 6374 update_options = self.sql(expression, "update_options") 6375 update_options = f" {update_options} UPDATE" if update_options else "" 6376 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 6377 6378 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 6379 kind = self.sql(expression, "kind") 6380 kind = f" {kind}" if kind else "" 6381 return f"DELETE{kind} STATISTICS" 6382 6383 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 6384 inner_expression = self.sql(expression, "expression") 6385 return f"LIST CHAINED ROWS{inner_expression}" 6386 6387 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6388 kind = self.sql(expression, "kind") 6389 this = self.sql(expression, "this") 6390 this = f" {this}" if this else "" 6391 inner_expression = self.sql(expression, "expression") 6392 return f"VALIDATE {kind}{this}{inner_expression}" 6393 6394 def analyze_sql(self, expression: exp.Analyze) -> str: 6395 options = self.expressions(expression, key="options", sep=" ") 6396 options = f" {options}" if options else "" 6397 kind = self.sql(expression, "kind") 6398 kind = f" {kind}" if kind else "" 6399 tables = self.expressions(expression, key="tables", flat=True) 6400 tables = f" {tables}" if tables else "" 6401 mode = self.sql(expression, "mode") 6402 mode = f" {mode}" if mode else "" 6403 properties = self.sql(expression, "properties") 6404 properties = f" {properties}" if properties else "" 6405 partition = self.sql(expression, "partition") 6406 partition = f" {partition}" if partition else "" 6407 inner_expression = self.sql(expression, "expression") 6408 inner_expression = f" {inner_expression}" if inner_expression else "" 6409 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}" 6410 6411 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6412 this = self.sql(expression, "this") 6413 namespaces = self.expressions(expression, key="namespaces") 6414 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6415 passing = self.expressions(expression, key="passing") 6416 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6417 columns = self.expressions(expression, key="columns") 6418 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6419 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6420 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6421 6422 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6423 this = self.sql(expression, "this") 6424 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6425 6426 def export_sql(self, expression: exp.Export) -> str: 6427 this = self.sql(expression, "this") 6428 connection = self.sql(expression, "connection") 6429 connection = f"WITH CONNECTION {connection} " if connection else "" 6430 options = self.sql(expression, "options") 6431 return f"EXPORT DATA {connection}{options} AS {this}" 6432 6433 def declare_sql(self, expression: exp.Declare) -> str: 6434 replace = "OR REPLACE " if expression.args.get("replace") else "" 6435 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6436 6437 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6438 variables = self.expressions(expression, "this") 6439 default = self.sql(expression, "default") 6440 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6441 6442 kind = self.sql(expression, "kind") 6443 if isinstance(expression.args.get("kind"), exp.Schema): 6444 kind = f"TABLE {kind}" 6445 6446 kind = f" {kind}" if kind else "" 6447 6448 return f"{variables}{kind}{default}" 6449 6450 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6451 kind = self.sql(expression, "kind") 6452 this = self.sql(expression, "this") 6453 set = self.sql(expression, "expression") 6454 to = self.sql(expression, "to") 6455 to = f" TO {to}" if to else "" 6456 default = self.sql(expression, "default") 6457 default = f" DEFAULT {default}" if default else "" 6458 using = self.sql(expression, "using") 6459 using = f" USING {using}" if using else "" 6460 6461 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6462 6463 return f"{kind_sql} {this} SET {set}{to}{default}{using}" 6464 6465 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6466 params = self.expressions(expression, key="params", flat=True) 6467 return self.func(expression.name, *expression.expressions) + f"({params})" 6468 6469 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6470 return self.func(expression.name, *expression.expressions) 6471 6472 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6473 return self.anonymousaggfunc_sql(expression) 6474 6475 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6476 return self.parameterizedagg_sql(expression) 6477 6478 def show_sql(self, expression: exp.Show) -> str: 6479 self.unsupported("Unsupported SHOW statement") 6480 return "" 6481 6482 def install_sql(self, expression: exp.Install) -> str: 6483 self.unsupported("Unsupported INSTALL statement") 6484 return "" 6485 6486 def liststage_sql(self, expression: exp.ListStage) -> str: 6487 pattern = self.sql(expression, "pattern") 6488 pattern = f" PATTERN = {pattern}" if pattern else "" 6489 return f"LIST {self.sql(expression, 'this')}{pattern}" 6490 6491 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6492 # Snowflake GET/PUT statements: 6493 # PUT <file> <internalStage> <properties> 6494 # GET <internalStage> <file> <properties> 6495 props = expression.args.get("properties") 6496 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6497 this = self.sql(expression, "this") 6498 target = self.sql(expression, "target") 6499 6500 if isinstance(expression, exp.Put): 6501 return f"PUT {this} {target}{props_sql}" 6502 else: 6503 return f"GET {target} {this}{props_sql}" 6504 6505 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6506 this = self.sql(expression, "this") 6507 expr = self.sql(expression, "expression") 6508 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6509 return f"TRANSLATE({this} USING {expr}{with_error})" 6510 6511 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6512 if self.SUPPORTS_DECODE_CASE: 6513 return self.func("DECODE", *expression.expressions) 6514 6515 decode_expr, *expressions = expression.expressions 6516 6517 ifs = [] 6518 for search, result in zip(expressions[::2], expressions[1::2]): 6519 if isinstance(search, exp.Literal): 6520 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6521 elif isinstance(search, exp.Null): 6522 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6523 else: 6524 if isinstance(search, exp.Binary): 6525 search = exp.paren(search) 6526 6527 cond = exp.or_( 6528 decode_expr.eq(search), 6529 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6530 copy=False, 6531 ) 6532 ifs.append(exp.If(this=cond, true=result)) 6533 6534 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6535 return self.sql(case) 6536 6537 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6538 this = self.sql(expression, "this") 6539 this = self.seg(this, sep="") 6540 dimensions = self.expressions( 6541 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6542 ) 6543 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6544 metrics = self.expressions( 6545 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6546 ) 6547 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6548 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6549 facts = self.seg(f"FACTS {facts}") if facts else "" 6550 where = self.sql(expression, "where") 6551 where = self.seg(f"WHERE {where}") if where else "" 6552 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6553 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6554 6555 def getextract_sql(self, expression: exp.GetExtract) -> str: 6556 this = expression.this 6557 expr = expression.expression 6558 6559 if not this.type or not expression.type: 6560 import sqlglot.optimizer.annotate_types 6561 6562 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6563 6564 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6565 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6566 6567 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6568 6569 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6570 return self.sql( 6571 exp.DateAdd( 6572 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6573 expression=expression.this, 6574 unit=exp.var("DAY"), 6575 ) 6576 ) 6577 6578 def space_sql(self: Generator, expression: exp.Space) -> str: 6579 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6580 6581 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6582 return f"BUILD {self.sql(expression, 'this')}" 6583 6584 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6585 method = self.sql(expression, "method") 6586 kind = expression.args.get("kind") 6587 if not kind: 6588 return f"REFRESH {method}" 6589 6590 every = self.sql(expression, "every") 6591 unit = self.sql(expression, "unit") 6592 every = f" EVERY {every} {unit}" if every else "" 6593 starts = self.sql(expression, "starts") 6594 starts = f" STARTS {starts}" if starts else "" 6595 6596 return f"REFRESH {method} ON {kind}{every}{starts}" 6597 6598 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6599 self.unsupported("The model!attribute syntax is not supported") 6600 return "" 6601 6602 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6603 return self.func("DIRECTORY", expression.this) 6604 6605 def uuid_sql(self, expression: exp.Uuid) -> str: 6606 is_string = expression.args.get("is_string", False) 6607 uuid_func_sql = self.func("UUID") 6608 6609 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6610 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6611 6612 return uuid_func_sql 6613 6614 def initcap_sql(self, expression: exp.Initcap) -> str: 6615 delimiters = expression.expression 6616 6617 if delimiters: 6618 # do not generate delimiters arg if we are round-tripping from default delimiters 6619 if ( 6620 delimiters.is_string 6621 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6622 ): 6623 delimiters = None 6624 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6625 self.unsupported("INITCAP does not support custom delimiters") 6626 delimiters = None 6627 6628 return self.func("INITCAP", expression.this, delimiters) 6629 6630 def localtime_sql(self, expression: exp.Localtime) -> str: 6631 this = expression.this 6632 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6633 6634 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6635 this = expression.this 6636 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6637 6638 def weekstart_name(self, expression: exp.WeekStart) -> str: 6639 import sqlglot.dialects.dialect 6640 6641 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6642 this = expression.this.name.upper() 6643 6644 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6645 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6646 6647 if dow_from_week_start_day != dow_from_week_offset: 6648 self.unsupported( 6649 f"WEEK({this}) is not supported; falling back to the default week start day" 6650 ) 6651 6652 return "WEEK" 6653 6654 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6655 name = self.weekstart_name(expression) 6656 6657 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6658 if isinstance(expression.parent, exp.DateTrunc): 6659 return self.sql(exp.Literal.string(name)) 6660 6661 return name 6662 6663 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6664 this = self.expressions(expression) 6665 charset = self.sql(expression, "charset") 6666 using = f" USING {charset}" if charset else "" 6667 return self.func(name, this + using) 6668 6669 def weightstring_sql(self, expression: exp.WeightString) -> str: 6670 to = self.sql(expression, "to") 6671 to = f" AS {to}" if to else "" 6672 return self.func("WEIGHT_STRING", f"{self.sql(expression, 'this')}{to}") 6673 6674 def block_sql(self, expression: exp.Block) -> str: 6675 expressions = self.expressions(expression, sep="; ", flat=True) 6676 begin = "BEGIN " if expression.args.get("begin") else "" 6677 return f"{begin}{expressions}" if expressions else "" 6678 6679 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6680 self.unsupported("Unsupported Inline UDFs syntax") 6681 return "" 6682 6683 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6684 self.unsupported("Unsupported Stored Procedure syntax") 6685 return "" 6686 6687 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6688 self.unsupported("Unsupported If block syntax") 6689 return "" 6690 6691 def casestatement_sql(self, expression: exp.CaseStatement) -> str: 6692 self.unsupported("Unsupported Case statement syntax") 6693 return "" 6694 6695 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6696 self.unsupported("Unsupported While block syntax") 6697 return "" 6698 6699 def loopblock_sql(self, expression: exp.LoopBlock) -> str: 6700 self.unsupported("Unsupported Loop block syntax") 6701 return "" 6702 6703 def repeatblock_sql(self, expression: exp.RepeatBlock) -> str: 6704 self.unsupported("Unsupported Repeat block syntax") 6705 return "" 6706 6707 def leave_sql(self, expression: exp.Leave) -> str: 6708 self.unsupported("Unsupported Leave syntax") 6709 return "" 6710 6711 def iterate_sql(self, expression: exp.Iterate) -> str: 6712 self.unsupported("Unsupported Iterate syntax") 6713 return "" 6714 6715 def execute_sql(self, expression: exp.Execute) -> str: 6716 self.unsupported("Unsupported Execute syntax") 6717 return "" 6718 6719 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6720 self.unsupported("Unsupported Execute syntax") 6721 return "" 6722 6723 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6724 props = self.expressions(expression, sep=" ") 6725 return f"MODIFY {props}" 6726 6727 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6728 kind = expression.args.get("kind") 6729 return f"USING {kind} {self.sql(expression, 'this')}" 6730 6731 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6732 this = self.sql(expression, "this") 6733 to = self.sql(expression, "to") 6734 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)
940 def __init__( 941 self, 942 pretty: bool | int | None = None, 943 identify: str | bool = False, 944 normalize: bool = False, 945 pad: int = 2, 946 indent: int = 2, 947 normalize_functions: str | bool | None = None, 948 unsupported_level: ErrorLevel = ErrorLevel.WARN, 949 max_unsupported: int = 3, 950 leading_comma: bool = False, 951 max_text_width: int = 80, 952 comments: bool = True, 953 dialect: DialectType = None, 954 ): 955 import sqlglot 956 import sqlglot.dialects.dialect 957 958 self.pretty = pretty if pretty is not None else sqlglot.pretty 959 self.identify = identify 960 self.normalize = normalize 961 self.pad = pad 962 self._indent = indent 963 self.unsupported_level = unsupported_level 964 self.max_unsupported = max_unsupported 965 self.leading_comma = leading_comma 966 self.max_text_width = max_text_width 967 self.comments = comments 968 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 969 970 # This is both a Dialect property and a Generator argument, so we prioritize the latter 971 self.normalize_functions = ( 972 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 973 ) 974 975 self.unsupported_messages: list[str] = [] 976 self._escaped_quote_end: str = ( 977 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 978 ) 979 self._escaped_byte_quote_end: str = ( 980 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 981 if self.dialect.BYTE_END 982 else "" 983 ) 984 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 985 self._identifier_escaped_sequences = self.dialect.IDENTIFIER_ESCAPED_SEQUENCES 986 987 self._next_name = name_sequence("_t") 988 989 self._identifier_start = self.dialect.IDENTIFIER_START 990 self._identifier_end = self.dialect.IDENTIFIER_END 991 992 self._quote_json_path_key_using_brackets = True 993 994 cls = type(self) 995 dispatch = _DISPATCH_CACHE.get(cls) 996 if dispatch is None: 997 dispatch = _build_dispatch(cls) 998 _DISPATCH_CACHE[cls] = dispatch 999 self._dispatch = dispatch
TRANSFORMS: ClassVar[dict[type[sqlglot.expressions.core.Expr], Callable[..., str]]] =
{<class 'sqlglot.expressions.query.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.core.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainedBy'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.BinaryColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.string.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyTagsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IgnoreGrantsProperty'>: <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.WithoutRowidProperty'>: <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.JSONPathScript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>, <class 'sqlglot.expressions.query.JSONPathWildcard'>, <class 'sqlglot.expressions.query.JSONPathRecursive'>, <class 'sqlglot.expressions.query.JSONPathUnion'>, <class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathFilter'>}
TYPE_MAPPING: ClassVar =
{<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TYPE_PARAM_SETTINGS: ClassVar[dict[sqlglot.expressions.datatypes.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]] =
{}
TIME_PART_SINGULARS: ClassVar =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS: ClassVar =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function <lambda>>, 'qualify': <function <lambda>>}
PROPERTIES_LOCATION: ClassVar =
{<class 'sqlglot.expressions.properties.AggregateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CopyTagsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusterProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <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.IgnoreGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InteractiveProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <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.properties.OptionsProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <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.UNSUPPORTED: 'UNSUPPORTED'>, <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.WithoutRowidProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.ddl.Command'>, <class 'sqlglot.expressions.ddl.Create'>, <class 'sqlglot.expressions.ddl.Describe'>, <class 'sqlglot.expressions.dml.Delete'>, <class 'sqlglot.expressions.ddl.Drop'>, <class 'sqlglot.expressions.query.From'>, <class 'sqlglot.expressions.dml.Insert'>, <class 'sqlglot.expressions.query.Join'>, <class 'sqlglot.expressions.query.MultitableInserts'>, <class 'sqlglot.expressions.query.Order'>, <class 'sqlglot.expressions.query.Group'>, <class 'sqlglot.expressions.query.Having'>, <class 'sqlglot.expressions.query.Select'>, <class 'sqlglot.expressions.query.SetOperation'>, <class 'sqlglot.expressions.dml.Update'>, <class 'sqlglot.expressions.query.Where'>, <class 'sqlglot.expressions.query.With'>)
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar =
{<DType.CHAR: 'CHAR'>, <DType.VARCHAR: 'VARCHAR'>, <DType.NCHAR: 'NCHAR'>, <DType.NVARCHAR: 'NVARCHAR'>}
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'>)
1001 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 1002 """ 1003 Generates the SQL string corresponding to the given syntax tree. 1004 1005 Args: 1006 expression: The syntax tree. 1007 copy: Whether to copy the expression. The generator performs mutations so 1008 it is safer to copy. 1009 1010 Returns: 1011 The SQL string corresponding to `expression`. 1012 """ 1013 if copy: 1014 expression = expression.copy() 1015 1016 expression = self.preprocess(expression) 1017 1018 self.unsupported_messages = [] 1019 sql = self.sql(expression).strip() 1020 1021 if self.pretty: 1022 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 1023 1024 if self.unsupported_level == ErrorLevel.IGNORE: 1025 return sql 1026 1027 if self.unsupported_level == ErrorLevel.WARN: 1028 for msg in self.unsupported_messages: 1029 logger.warning(msg) 1030 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 1031 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 1032 1033 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.
1035 def preprocess(self, expression: exp.Expr) -> exp.Expr: 1036 """Apply generic preprocessing transformations to a given expression.""" 1037 expression = self._move_ctes_to_top_level(expression) 1038 1039 if self.ENSURE_BOOLS: 1040 import sqlglot.transforms 1041 1042 expression = sqlglot.transforms.ensure_bools(expression) 1043 1044 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
1068 def sanitize_comment(self, comment: str) -> str: 1069 comment = " " + comment if comment[0].strip() else comment 1070 comment = comment + " " if comment[-1].strip() else comment 1071 1072 # Escape block comment markers to prevent premature closure or unintended nesting. 1073 # This is necessary because single-line comments (--) are converted to block comments 1074 # (/* */) on output, and any */ in the original text would close the comment early. 1075 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1076 1077 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
1079 def maybe_comment( 1080 self, 1081 sql: str, 1082 expression: exp.Expr | None = None, 1083 comments: list[str] | None = None, 1084 separated: bool = False, 1085 ) -> str: 1086 comments = ( 1087 ((expression and expression.comments) if comments is None else comments) # type: ignore 1088 if self.comments 1089 else None 1090 ) 1091 1092 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1093 return sql 1094 1095 comments_list = [ 1096 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1097 for comment in comments 1098 if comment 1099 ] 1100 1101 if not comments_list: 1102 return sql 1103 1104 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1105 comments_sql = self.sep().join(comments_list) 1106 return ( 1107 f"{self.sep()}{comments_sql}{sql}" 1108 if not sql or sql[0].isspace() 1109 else f"{comments_sql}{self.sep()}{sql}" 1110 ) 1111 1112 return f"{sql} {' '.join(comments_list)}"
1114 def wrap(self, expression: exp.Expr | str) -> str: 1115 this_sql = ( 1116 self.sql(expression) 1117 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1118 else self.sql(expression, "this") 1119 ) 1120 if not this_sql: 1121 return "()" 1122 1123 this_sql = self.indent(this_sql, level=1, pad=0) 1124 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:
1140 def indent( 1141 self, 1142 sql: str, 1143 level: int = 0, 1144 pad: int | None = None, 1145 skip_first: bool = False, 1146 skip_last: bool = False, 1147 ) -> str: 1148 if not self.pretty or not sql: 1149 return sql 1150 1151 pad = self.pad if pad is None else pad 1152 lines = sql.split("\n") 1153 1154 return "\n".join( 1155 ( 1156 line 1157 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1158 else f"{' ' * (level * self._indent + pad)}{line}" 1159 ) 1160 for i, line in enumerate(lines) 1161 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1163 def sql( 1164 self, 1165 expression: str | exp.Expr | None, 1166 key: str | None = None, 1167 comment: bool = True, 1168 ) -> str: 1169 if not expression: 1170 return "" 1171 1172 if isinstance(expression, str): 1173 return expression 1174 1175 if key: 1176 value = expression.args.get(key) 1177 if value: 1178 return self.sql(value) 1179 return "" 1180 1181 handler = self._dispatch.get(expression.__class__) 1182 1183 if handler: 1184 sql = handler(self, expression) 1185 elif isinstance(expression, exp.Func): 1186 sql = self.function_fallback_sql(expression) 1187 elif isinstance(expression, exp.Property): 1188 sql = self.property_sql(expression) 1189 else: 1190 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1191 1192 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1199 def cache_sql(self, expression: exp.Cache) -> str: 1200 lazy = " LAZY" if expression.args.get("lazy") else "" 1201 table = self.sql(expression, "this") 1202 options = expression.args.get("options") 1203 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1204 sql = self.sql(expression, "expression") 1205 sql = f" AS{self.sep()}{sql}" if sql else "" 1206 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1207 return self.prepend_ctes(expression, sql)
1213 def column_parts(self, expression: exp.Column) -> str: 1214 if expression.args.get("shadow") and self.dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES: 1215 # The qualifier would be captured by a colliding projection alias (see qualify_columns) 1216 return self.sql(expression, "this") 1217 1218 return ".".join( 1219 self.sql(part) 1220 for part in ( 1221 expression.args.get("catalog"), 1222 expression.args.get("db"), 1223 expression.args.get("table"), 1224 expression.args.get("this"), 1225 ) 1226 if part 1227 )
1229 def column_sql(self, expression: exp.Column) -> str: 1230 join_mark = " (+)" if expression.args.get("join_mark") else "" 1231 1232 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1233 join_mark = "" 1234 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1235 1236 return f"{self.column_parts(expression)}{join_mark}"
1247 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1248 column = self.sql(expression, "this") 1249 kind = self.sql(expression, "kind") 1250 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1251 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1252 kind = f"{sep}{kind}" if kind else "" 1253 constraints = f" {constraints}" if constraints else "" 1254 position = self.sql(expression, "position") 1255 position = f" {position}" if position else "" 1256 1257 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1258 kind = "" 1259 1260 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:
1267 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1268 this = self.sql(expression, "this") 1269 if expression.args.get("not_null"): 1270 persisted = " PERSISTED NOT NULL" 1271 elif expression.args.get("persisted"): 1272 persisted = " PERSISTED" 1273 else: 1274 persisted = "" 1275 1276 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:
1289 def generatedasidentitycolumnconstraint_sql( 1290 self, expression: exp.GeneratedAsIdentityColumnConstraint 1291 ) -> str: 1292 this = "" 1293 if expression.this is not None: 1294 on_null = " ON NULL" if expression.args.get("on_null") else "" 1295 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1296 1297 start = expression.args.get("start") 1298 start = f"START WITH {start}" if start else "" 1299 increment = expression.args.get("increment") 1300 increment = f" INCREMENT BY {increment}" if increment else "" 1301 minvalue = expression.args.get("minvalue") 1302 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1303 maxvalue = expression.args.get("maxvalue") 1304 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1305 cycle = expression.args.get("cycle") 1306 cycle_sql = "" 1307 1308 if cycle is not None: 1309 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1310 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1311 1312 sequence_opts = "" 1313 if start or increment or cycle_sql: 1314 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1315 sequence_opts = f" ({sequence_opts.strip()})" 1316 1317 expr = self.sql(expression, "expression") 1318 expr = f"({expr})" if expr else "IDENTITY" 1319 1320 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1322 def generatedasrowcolumnconstraint_sql( 1323 self, expression: exp.GeneratedAsRowColumnConstraint 1324 ) -> str: 1325 start = "START" if expression.args.get("start") else "END" 1326 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1327 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:
1337 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1338 desc = expression.args.get("desc") 1339 if desc is not None: 1340 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1341 options = self.expressions(expression, key="options", flat=True, sep=" ") 1342 options = f" {options}" if options else "" 1343 include = self.sql(expression, "include") 1344 return f"PRIMARY KEY{include}{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1346 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1347 this = self.sql(expression, "this") 1348 this = f" {this}" if this else "" 1349 index_type = expression.args.get("index_type") 1350 index_type = f" USING {index_type}" if index_type else "" 1351 on_conflict = self.sql(expression, "on_conflict") 1352 on_conflict = f" {on_conflict}" if on_conflict else "" 1353 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1354 options = self.expressions(expression, key="options", flat=True, sep=" ") 1355 options = f" {options}" if options else "" 1356 include = self.sql(expression, "include") 1357 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{include}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1359 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1360 input_ = expression.args.get("input_") 1361 output = expression.args.get("output") 1362 variadic = expression.args.get("variadic") 1363 1364 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1365 if variadic: 1366 return "VARIADIC" 1367 1368 if input_ and output: 1369 return f"IN{self.INOUT_SEPARATOR}OUT" 1370 if input_: 1371 return "IN" 1372 if output: 1373 return "OUT" 1374 1375 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1380 def create_sql(self, expression: exp.Create) -> str: 1381 kind = self.sql(expression, "kind") 1382 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1383 1384 properties = expression.args.get("properties") 1385 1386 if ( 1387 kind == "TRIGGER" 1388 and properties 1389 and properties.expressions 1390 and isinstance(properties.expressions[0], exp.TriggerProperties) 1391 and properties.expressions[0].args.get("constraint") 1392 ): 1393 kind = f"CONSTRAINT {kind}" 1394 1395 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1396 1397 this = self.createable_sql(expression, properties_locs) 1398 1399 properties_sql = "" 1400 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1401 exp.Properties.Location.POST_WITH 1402 ): 1403 props_ast = exp.Properties( 1404 expressions=[ 1405 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1406 *properties_locs[exp.Properties.Location.POST_WITH], 1407 ] 1408 ) 1409 props_ast.parent = expression 1410 properties_sql = self.sql(props_ast) 1411 1412 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1413 properties_sql = self.sep() + properties_sql 1414 elif not self.pretty: 1415 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1416 properties_sql = f" {properties_sql}" 1417 1418 begin = " BEGIN" if expression.args.get("begin") else "" 1419 1420 expression_sql = self.sql(expression, "expression") 1421 if expression_sql: 1422 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1423 1424 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1425 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1426 ): 1427 postalias_props_sql = "" 1428 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1429 postalias_props_sql = self.properties( 1430 exp.Properties( 1431 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1432 ), 1433 wrapped=False, 1434 ) 1435 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1436 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1437 1438 postindex_props_sql = "" 1439 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1440 postindex_props_sql = self.properties( 1441 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1442 wrapped=False, 1443 prefix=" ", 1444 ) 1445 1446 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1447 indexes = f" {indexes}" if indexes else "" 1448 index_sql = indexes + postindex_props_sql 1449 1450 replace = " OR REPLACE" if expression.args.get("replace") else "" 1451 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1452 unique = " UNIQUE" if expression.args.get("unique") else "" 1453 1454 clustered = expression.args.get("clustered") 1455 if clustered is None: 1456 clustered_sql = "" 1457 elif clustered: 1458 clustered_sql = " CLUSTERED COLUMNSTORE" 1459 else: 1460 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1461 1462 postcreate_props_sql = "" 1463 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1464 postcreate_props_sql = self.properties( 1465 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1466 sep=" ", 1467 prefix=" ", 1468 wrapped=False, 1469 ) 1470 1471 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1472 1473 postexpression_props_sql = "" 1474 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1475 postexpression_props_sql = self.properties( 1476 exp.Properties( 1477 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1478 ), 1479 sep=" ", 1480 prefix=" ", 1481 wrapped=False, 1482 ) 1483 1484 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1485 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1486 no_schema_binding = ( 1487 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1488 ) 1489 1490 clone = self.sql(expression, "clone") 1491 clone = f" {clone}" if clone else "" 1492 1493 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1494 properties_expression = f"{expression_sql}{properties_sql}" 1495 else: 1496 properties_expression = f"{properties_sql}{expression_sql}" 1497 1498 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1499 return self.prepend_ctes(expression, expression_sql)
1501 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1502 start = self.sql(expression, "start") 1503 start = f"START WITH {start}" if start else "" 1504 increment = self.sql(expression, "increment") 1505 increment = f" INCREMENT BY {increment}" if increment else "" 1506 minvalue = self.sql(expression, "minvalue") 1507 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1508 maxvalue = self.sql(expression, "maxvalue") 1509 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1510 owned = self.sql(expression, "owned") 1511 owned = f" OWNED BY {owned}" if owned else "" 1512 1513 cache = expression.args.get("cache") 1514 if cache is None: 1515 cache_str = "" 1516 elif cache is True: 1517 cache_str = " CACHE" 1518 else: 1519 cache_str = f" CACHE {cache}" 1520 1521 options = self.expressions(expression, key="options", flat=True, sep=" ") 1522 options = f" {options}" if options else "" 1523 1524 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1526 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1527 timing = expression.args.get("timing", "") 1528 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1529 timing_events = f"{timing} {events}".strip() if timing or events else "" 1530 1531 parts = [timing_events, "ON", self.sql(expression, "table")] 1532 1533 if referenced_table := expression.args.get("referenced_table"): 1534 parts.extend(["FROM", self.sql(referenced_table)]) 1535 1536 if deferrable := expression.args.get("deferrable"): 1537 parts.append(deferrable) 1538 1539 if initially := expression.args.get("initially"): 1540 parts.append(f"INITIALLY {initially}") 1541 1542 if referencing := expression.args.get("referencing"): 1543 parts.append(self.sql(referencing)) 1544 1545 if for_each := expression.args.get("for_each"): 1546 parts.append(f"FOR EACH {for_each}") 1547 1548 if when := expression.args.get("when"): 1549 parts.append(f"WHEN ({self.sql(when)})") 1550 1551 parts.append(self.sql(expression, "execute")) 1552 1553 return self.sep().join(parts)
1555 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1556 parts = [] 1557 1558 if old_alias := expression.args.get("old"): 1559 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1560 1561 if new_alias := expression.args.get("new"): 1562 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1563 1564 return f"REFERENCING {' '.join(parts)}"
1573 def clone_sql(self, expression: exp.Clone) -> str: 1574 this = self.sql(expression, "this") 1575 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1576 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1577 return f"{shallow}{keyword} {this}"
1579 def describe_sql(self, expression: exp.Describe) -> str: 1580 style = expression.args.get("style") 1581 style = f" {style}" if style else "" 1582 partition = self.sql(expression, "partition") 1583 partition = f" {partition}" if partition else "" 1584 format = self.sql(expression, "format") 1585 format = f" {format}" if format else "" 1586 as_json = " AS JSON" if expression.args.get("as_json") else "" 1587 1588 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1600 def with_sql(self, expression: exp.With) -> str: 1601 udfs = self.expressions(expression, key="udfs", flat=True) 1602 udfs = f"WITH {udfs}" if udfs else "" 1603 1604 sql = self.expressions(expression, flat=True) 1605 1606 recursive = ( 1607 "RECURSIVE " 1608 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1609 else "" 1610 ) 1611 search = self.sql(expression, "search") 1612 search = f" {search}" if search else "" 1613 cycle = self.sql(expression, "cycle") 1614 cycle = f" {cycle}" if cycle else "" 1615 1616 sql = f"WITH {recursive}{sql}{search}{cycle}" if sql else "" 1617 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1619 def cte_sql(self, expression: exp.CTE) -> str: 1620 alias = expression.args.get("alias") 1621 if alias: 1622 alias.add_comments(expression.pop_comments()) 1623 1624 alias_sql = self.sql(expression, "alias") 1625 1626 materialized = expression.args.get("materialized") 1627 if materialized is False: 1628 materialized = "NOT MATERIALIZED " 1629 elif materialized: 1630 materialized = "MATERIALIZED " 1631 1632 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1633 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1634 1635 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1637 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1638 alias = self.sql(expression, "this") 1639 columns = self.expressions(expression, key="columns", flat=True) 1640 columns = f"({columns})" if columns else "" 1641 1642 if ( 1643 columns 1644 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1645 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1646 ): 1647 columns = "" 1648 self.unsupported("Named columns are not supported in table alias.") 1649 1650 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1651 alias = self._next_name() 1652 1653 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1661 def hexstring_sql( 1662 self, expression: exp.HexString, binary_function_repr: str | None = None 1663 ) -> str: 1664 this = self.sql(expression, "this") 1665 is_integer_type = expression.args.get("is_integer") 1666 1667 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1668 not self.dialect.HEX_START and not binary_function_repr 1669 ): 1670 # Integer representation will be returned if: 1671 # - The read dialect treats the hex value as integer literal but not the write 1672 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1673 return f"{int(this, 16)}" 1674 1675 if not is_integer_type: 1676 # Read dialect treats the hex value as BINARY/BLOB 1677 if binary_function_repr: 1678 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1679 return self.func(binary_function_repr, exp.Literal.string(this)) 1680 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1681 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1682 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1683 1684 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1686 def bytestring_sql(self, expression: exp.ByteString) -> str: 1687 this = self.sql(expression, "this") 1688 is_bytes = bool(expression.args.get("is_bytes")) 1689 if is_bytes and self.BYTE_STRING_FUNCTION: 1690 # Hex digits can be quoted directly without escaping. 1691 return self.func(self.BYTE_STRING_FUNCTION, f"'{this.encode('latin-1').hex()}'") 1692 1693 if self.dialect.BYTE_START: 1694 escaped_byte_string = self.escape_str( 1695 this, 1696 escape_backslash=bool( 1697 self.dialect.tokenizer_class.NUMERIC_ESCAPES 1698 or self.dialect.tokenizer_class.DROP_UNKNOWN_ESCAPES 1699 ), 1700 delimiter=self.dialect.BYTE_END, 1701 escaped_delimiter=self._escaped_byte_quote_end, 1702 is_byte_string=True, 1703 is_bytes=is_bytes, 1704 ) 1705 delimited_byte_string = ( 1706 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1707 ) 1708 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1709 return self.sql( 1710 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1711 ) 1712 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1713 return self.sql( 1714 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1715 ) 1716 1717 return delimited_byte_string 1718 1719 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1720 return self.sql(exp.Literal.string(this)) 1721 1722 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1723 return ""
1725 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1726 this = self.sql(expression, "this") 1727 escape = expression.args.get("escape") 1728 unicode_start = self.dialect.UNICODE_START 1729 1730 if unicode_start: 1731 escape_substitute = r"\\\1" 1732 left_quote, right_quote = unicode_start, self.dialect.UNICODE_END or "" 1733 else: 1734 escape_substitute = r"\\u\1" 1735 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1736 1737 if escape: 1738 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1739 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1740 else: 1741 escape_pattern = ESCAPED_UNICODE_RE 1742 escape_sql = "" 1743 1744 if not unicode_start or (escape and not self.SUPPORTS_UESCAPE): 1745 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1746 1747 if unicode_start: 1748 # A Unicode literal only escapes its delimiter by doubling it; the escape character 1749 # introduces a code point, so the dialect's ordinary string escapes don't apply here 1750 this = self._replace_line_breaks(this).replace(right_quote, right_quote * 2) 1751 else: 1752 this = self.escape_str(this, escape_backslash=False) 1753 1754 return f"{left_quote}{this}{right_quote}{escape_sql}"
1756 def rawstring_sql(self, expression: exp.RawString) -> str: 1757 string = expression.this 1758 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1759 string = string.replace("\\", "\\\\") 1760 1761 string = self.escape_str(string, escape_backslash=False) 1762 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:
1770 def datatype_param_bound_limiter( 1771 self, 1772 expression: exp.DataType, 1773 type_value: exp.DType, 1774 defaults: tuple[int, ...], 1775 bounds: tuple[int | None, ...], 1776 ) -> exp.DataType: 1777 params = expression.expressions 1778 1779 if not params: 1780 if defaults: 1781 expression.set( 1782 "expressions", 1783 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1784 ) 1785 return expression 1786 1787 if not bounds: 1788 return expression 1789 1790 for i, param in enumerate(params): 1791 bound = bounds[i] if i < len(bounds) else None 1792 if bound is None: 1793 continue 1794 1795 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1796 value = ( 1797 param_value.to_py() 1798 if isinstance(param_value, exp.Literal) and param_value.is_number 1799 else None 1800 ) 1801 if isinstance(value, (int, Decimal)) and value > bound: 1802 self.unsupported( 1803 f"{type_value.value} parameter {param_value.name} exceeds " 1804 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1805 ) 1806 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1807 1808 return expression
1810 def datatype_sql(self, expression: exp.DataType) -> str: 1811 nested = "" 1812 values = "" 1813 1814 expr_nested = expression.args.get("nested") 1815 type_value = expression.this 1816 1817 if ( 1818 not expr_nested 1819 and isinstance(type_value, exp.DType) 1820 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1821 ): 1822 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1823 1824 interior = ( 1825 self.expressions( 1826 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1827 ) 1828 if expr_nested and self.pretty 1829 else self.expressions(expression, flat=True) 1830 ) 1831 1832 if type_value in self.UNSUPPORTED_TYPES: 1833 self.unsupported( 1834 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1835 ) 1836 1837 type_sql: t.Any = "" 1838 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1839 type_sql = self.sql(expression, "kind") 1840 elif type_value == exp.DType.CHARACTER_SET: 1841 char = f"CHAR({interior})" if interior else "CHAR" 1842 return f"{char} CHARACTER SET {self.sql(expression, 'kind')}" 1843 else: 1844 type_sql = ( 1845 self.TYPE_MAPPING.get(type_value, type_value.value) 1846 if isinstance(type_value, exp.DType) 1847 else type_value 1848 ) 1849 1850 if interior: 1851 if expr_nested: 1852 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1853 if expression.args.get("values") is not None: 1854 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1855 values = self.expressions(expression, key="values", flat=True) 1856 values = f"{delimiters[0]}{values}{delimiters[1]}" 1857 elif type_value == exp.DType.INTERVAL: 1858 nested = f" {interior}" 1859 else: 1860 nested = f"({interior})" 1861 1862 type_sql = f"{type_sql}{nested}{values}" 1863 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1864 exp.DType.TIMETZ, 1865 exp.DType.TIMESTAMPTZ, 1866 ): 1867 type_sql = f"{type_sql} WITH TIME ZONE" 1868 1869 collate = self.sql(expression, "collate") 1870 if collate: 1871 type_sql = f"{type_sql} COLLATE {collate}" 1872 1873 return type_sql
1875 def directory_sql(self, expression: exp.Directory) -> str: 1876 local = "LOCAL " if expression.args.get("local") else "" 1877 row_format = self.sql(expression, "row_format") 1878 row_format = f" {row_format}" if row_format else "" 1879 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1881 def delete_sql(self, expression: exp.Delete) -> str: 1882 hint = self.sql(expression, "hint") 1883 this = self.sql(expression, "this") 1884 this = f" FROM {this}" if this else "" 1885 using = self.expressions(expression, key="using") 1886 using = f" USING {using}" if using else "" 1887 cluster = self.sql(expression, "cluster") 1888 cluster = f" {cluster}" if cluster else "" 1889 where = self.sql(expression, "where") 1890 returning = self.sql(expression, "returning") 1891 order = self.sql(expression, "order") 1892 limit = self.sql(expression, "limit") 1893 tables = self.expressions(expression, key="tables") 1894 tables = f" {tables}" if tables else "" 1895 if self.RETURNING_END: 1896 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1897 else: 1898 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1899 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1901 def drop_sql(self, expression: exp.Drop) -> str: 1902 tables = self.expressions(expression, key="tables", flat=True) 1903 expressions = self.expressions(expression, flat=True) 1904 expressions = f" ({expressions})" if expressions else "" 1905 kind = expression.args["kind"] 1906 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1907 iceberg = ( 1908 " ICEBERG" 1909 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1910 else "" 1911 ) 1912 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1913 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1914 on_cluster = self.sql(expression, "cluster") 1915 on_cluster = f" {on_cluster}" if on_cluster else "" 1916 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1917 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1918 cascade = " CASCADE" if expression.args.get("cascade") else "" 1919 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1920 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1921 purge = " PURGE" if expression.args.get("purge") else "" 1922 sync = " SYNC" if expression.args.get("sync") else "" 1923 force = " FORCE" if expression.args.get("force") else "" 1924 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{tables}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1926 def set_operation(self, expression: exp.SetOperation) -> str: 1927 op_type = type(expression) 1928 op_name = op_type.key.upper() 1929 1930 distinct = expression.args.get("distinct") 1931 if ( 1932 distinct is False 1933 and op_type in (exp.Except, exp.Intersect) 1934 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1935 ): 1936 self.unsupported(f"{op_name} ALL is not supported") 1937 1938 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1939 1940 if distinct is None: 1941 distinct = default_distinct 1942 if distinct is None: 1943 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1944 1945 if distinct is default_distinct: 1946 distinct_or_all = "" 1947 else: 1948 distinct_or_all = " DISTINCT" if distinct else " ALL" 1949 1950 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1951 side_kind = f"{side_kind} " if side_kind else "" 1952 1953 by_name = " BY NAME" if expression.args.get("by_name") else "" 1954 on = self.expressions(expression, key="on", flat=True) 1955 on = f" ON ({on})" if on else "" 1956 1957 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1959 def set_operations(self, expression: exp.SetOperation) -> str: 1960 if not self.SET_OP_MODIFIERS: 1961 limit = expression.args.get("limit") 1962 order = expression.args.get("order") 1963 offset = expression.args.get("offset") 1964 1965 if limit or order or offset: 1966 select = self._move_ctes_to_top_level( 1967 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1968 ) 1969 1970 for arg in ("limit", "order", "offset"): 1971 if value := expression.args.get(arg): 1972 select.set(arg, value.pop()) 1973 return self.sql(select) 1974 1975 sqls: list[str] = [] 1976 stack: list[str | exp.Expr | int | None] = [expression] 1977 depth = 0 1978 open_parens = "" 1979 1980 while stack: 1981 node = stack.pop() 1982 1983 if isinstance(node, int): 1984 # Closes a plain grouping opened below, restoring its depth 1985 depth = node 1986 sqls[-1] += self.sep("") + self.indent(")", level=depth, pad=0) 1987 elif isinstance(node, exp.SetOperation): 1988 stack.append(node.expression) 1989 stack.append( 1990 self.maybe_comment( 1991 self.set_operation(node), comments=node.comments, separated=True 1992 ) 1993 ) 1994 left = node.this 1995 if ( 1996 not self.SET_OP_PARENTHESIZED_OPERANDS 1997 and not self.dialect.INTERSECT_BINDS_TIGHTER_THAN_UNION_AND_EXCEPT 1998 ): 1999 # Left grouping is implicit, so skip the `SELECT * FROM (...)` wrapper: 2000 # it renames duplicate columns and deepens recursion. Inner CTEs and 2001 # modifiers still need the wrapper. 2002 wrappers: list[exp.Subquery] = [] 2003 inner = left 2004 while isinstance(inner, exp.Subquery) and inner.is_wrapper: 2005 wrappers.append(inner) 2006 inner = inner.this 2007 2008 if ( 2009 isinstance(inner, exp.SetOperation) 2010 and not inner.args.get("with_") 2011 and not any(inner.args.get(key) for key in exp.QUERY_MODIFIERS) 2012 ): 2013 for wrapper in wrappers: 2014 inner.add_comments(wrapper.pop_comments()) 2015 left = inner 2016 elif ( 2017 self.SET_OP_PARENTHESIZED_OPERANDS 2018 and isinstance(left, exp.Subquery) 2019 and left.is_wrapper 2020 and not left.comments 2021 and isinstance(left.this, exp.SetOperation) 2022 and not left.this.args.get("with_") 2023 and not any(left.this.args.get(key) for key in exp.QUERY_MODIFIERS) 2024 ): 2025 # Write a plain grouping's parentheses here rather than recursing through 2026 # subquery_sql: left-to-right chains nest one grouping per INTERSECT, and 2027 # long ones would exhaust the stack. The depth pushed here marks the close. 2028 open_parens += self.indent("(", level=depth, pad=0) + self.sep("") 2029 stack.append(depth) 2030 depth += 1 2031 left = left.this 2032 stack.append(left) 2033 else: 2034 if ( 2035 not self.SET_OP_LIMITS 2036 and isinstance(node, exp.Select) 2037 and node.args.get("limit") 2038 ): 2039 node = node.subquery(copy=False) 2040 if not self.SET_OP_PARENTHESIZED_OPERANDS: 2041 node = exp.select("*").from_(node, copy=False) 2042 elif ( 2043 not self.SET_OP_PARENTHESIZED_OPERANDS 2044 and isinstance(node, exp.Subquery) 2045 and isinstance(node.unnest(), exp.SetOperation) 2046 ): 2047 node = exp.select("*").from_(node, copy=False) 2048 sqls.append(open_parens + self.indent(self.sql(node), level=depth, pad=0)) 2049 open_parens = "" 2050 2051 this = self.sep().join(sqls) 2052 this = self.query_modifiers(expression, this) 2053 return self.prepend_ctes(expression, this)
2055 def fetch_sql(self, expression: exp.Fetch) -> str: 2056 direction = expression.args.get("direction") 2057 direction = f" {direction}" if direction else "" 2058 count = self.sql(expression, "count") 2059 count = f" {count}" if count else "" 2060 limit_options = self.sql(expression, "limit_options") 2061 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 2062 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
2064 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 2065 percent = " PERCENT" if expression.args.get("percent") else "" 2066 rows = " ROWS" if expression.args.get("rows") else "" 2067 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 2068 if not with_ties and rows: 2069 with_ties = " ONLY" 2070 return f"{percent}{rows}{with_ties}"
2077 def per_sql(self, expression: exp.Per) -> str: 2078 keys = ", ".join( 2079 f"{self.sql(key, 'this')} {self.sql(key, 'with_fill')}" 2080 if isinstance(key, exp.Ordered) and key.args.get("with_fill") 2081 else self.sql(key) 2082 for key in expression.expressions 2083 ) 2084 return f"{self.sql(expression, 'this')} PER ({keys})"
2093 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 2094 using = self.sql(expression, "using") 2095 using = f" USING {using}" if using else "" 2096 columns = self.expressions(expression, key="columns", flat=True) 2097 columns = f"({columns})" if columns else "" 2098 partition_by = self.expressions(expression, key="partition_by", flat=True) 2099 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 2100 where = self.sql(expression, "where") 2101 include = self.expressions(expression, key="include", flat=True) 2102 if include: 2103 include = f" INCLUDE ({include})" 2104 with_storage = self.expressions(expression, key="with_storage", flat=True) 2105 with_storage = f" WITH ({with_storage})" if with_storage else "" 2106 tablespace = self.sql(expression, "tablespace") 2107 if tablespace: 2108 prefix = "" if isinstance(expression.parent, exp.Index) else " USING INDEX" 2109 tablespace = f"{prefix} TABLESPACE {tablespace}" 2110 on = self.sql(expression, "on") 2111 on = f" ON {on}" if on else "" 2112 2113 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
2115 def index_sql(self, expression: exp.Index) -> str: 2116 unique = "UNIQUE " if expression.args.get("unique") else "" 2117 primary = "PRIMARY " if expression.args.get("primary") else "" 2118 amp = "AMP " if expression.args.get("amp") else "" 2119 name = self.sql(expression, "this") 2120 name = f"{name} " if name else "" 2121 table = self.sql(expression, "table") 2122 table = f"{self.INDEX_ON} {table}" if table else "" 2123 2124 index = "INDEX " if not table else "" 2125 2126 params = self.sql(expression, "params") 2127 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
2129 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 2130 this = expression.this 2131 if this and this.is_string: 2132 resolved = maybe_parse(this.name).sql(self.dialect) 2133 if "expressions" in expression.args: 2134 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 2135 # We can't safely emit the call to other dialects since name/arg semantics may differ 2136 self.unsupported( 2137 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 2138 ) 2139 return resolved 2140 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 2141 return self.func("IDENTIFIER", this)
2143 def identifier_sql(self, expression: exp.Identifier) -> str: 2144 text = expression.name 2145 lower = text.lower() 2146 quoted = expression.quoted 2147 text = lower if self.normalize and not quoted else text 2148 if self._identifier_escaped_sequences: 2149 for char, escaped in self._identifier_escaped_sequences.items(): 2150 text = text.replace(char, escaped) 2151 text = text.replace(self._identifier_end, self._escaped_identifier_end) 2152 if ( 2153 quoted 2154 or self.dialect.can_quote(expression, self.identify) 2155 or lower in self.RESERVED_KEYWORDS 2156 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 2157 ): 2158 text = ( 2159 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 2160 ) 2161 return text
2176 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2177 input_format = self.sql(expression, "input_format") 2178 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2179 output_format = self.sql(expression, "output_format") 2180 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2181 return self.sep().join((input_format, output_format))
2191 def properties_sql(self, expression: exp.Properties) -> str: 2192 root_properties = [] 2193 with_properties = [] 2194 2195 for p in expression.expressions: 2196 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2197 if p_loc == exp.Properties.Location.POST_WITH: 2198 with_properties.append(p) 2199 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2200 root_properties.append(p) 2201 2202 root_props_ast = exp.Properties(expressions=root_properties) 2203 root_props_ast.parent = expression.parent 2204 2205 with_props_ast = exp.Properties(expressions=with_properties) 2206 with_props_ast.parent = expression.parent 2207 2208 root_props = self.root_properties(root_props_ast) 2209 with_props = self.with_properties(with_props_ast) 2210 2211 if root_props and with_props and not self.pretty: 2212 with_props = " " + with_props 2213 2214 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2221 def properties( 2222 self, 2223 properties: exp.Properties, 2224 prefix: str = "", 2225 sep: str = ", ", 2226 suffix: str = "", 2227 wrapped: bool = True, 2228 ) -> str: 2229 if properties.expressions: 2230 expressions = self.expressions(properties, sep=sep, indent=False) 2231 if expressions: 2232 expressions = self.wrap(expressions) if wrapped else expressions 2233 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2234 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2239 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2240 properties_locs = defaultdict(list) 2241 for p in properties.expressions: 2242 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2243 if p_loc != exp.Properties.Location.UNSUPPORTED: 2244 properties_locs[p_loc].append(p) 2245 else: 2246 self.unsupported(f"Unsupported property {p.key}") 2247 2248 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2255 def property_sql(self, expression: exp.Property) -> str: 2256 property_cls = expression.__class__ 2257 if property_cls == exp.Property: 2258 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2259 2260 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2261 if not property_name: 2262 self.unsupported(f"Unsupported property {expression.key}") 2263 return "" 2264 2265 return f"{property_name}={self.sql(expression, 'this')}"
2270 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2271 if self.SUPPORTS_CREATE_TABLE_LIKE: 2272 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2273 options = f" {options}" if options else "" 2274 2275 like = f"LIKE {self.sql(expression, 'this')}{options}" 2276 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2277 like = f"({like})" 2278 2279 return like 2280 2281 if expression.expressions: 2282 self.unsupported("Transpilation of LIKE property options is unsupported") 2283 2284 select = exp.select("*").from_(expression.this).limit(0) 2285 return f"AS {self.sql(select)}"
2292 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2293 no = "NO " if expression.args.get("no") else "" 2294 local = expression.args.get("local") 2295 local = f"{local} " if local else "" 2296 dual = "DUAL " if expression.args.get("dual") else "" 2297 before = "BEFORE " if expression.args.get("before") else "" 2298 after = "AFTER " if expression.args.get("after") else "" 2299 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:
2315 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2316 if expression.args.get("no"): 2317 return "NO MERGEBLOCKRATIO" 2318 if expression.args.get("default"): 2319 return "DEFAULT MERGEBLOCKRATIO" 2320 2321 percent = " PERCENT" if expression.args.get("percent") else "" 2322 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2329 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2330 default = expression.args.get("default") 2331 minimum = expression.args.get("minimum") 2332 maximum = expression.args.get("maximum") 2333 if default or minimum or maximum: 2334 if default: 2335 prop = "DEFAULT" 2336 elif minimum: 2337 prop = "MINIMUM" 2338 else: 2339 prop = "MAXIMUM" 2340 return f"{prop} DATABLOCKSIZE" 2341 units = expression.args.get("units") 2342 units = f" {units}" if units else "" 2343 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2345 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2346 autotemp = expression.args.get("autotemp") 2347 always = expression.args.get("always") 2348 default = expression.args.get("default") 2349 manual = expression.args.get("manual") 2350 never = expression.args.get("never") 2351 2352 if autotemp is not None: 2353 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2354 elif always: 2355 prop = "ALWAYS" 2356 elif default: 2357 prop = "DEFAULT" 2358 elif manual: 2359 prop = "MANUAL" 2360 elif never: 2361 prop = "NEVER" 2362 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2364 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2365 no = expression.args.get("no") 2366 no = " NO" if no else "" 2367 concurrent = expression.args.get("concurrent") 2368 concurrent = " CONCURRENT" if concurrent else "" 2369 target = self.sql(expression, "target") 2370 target = f" {target}" if target else "" 2371 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2373 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2374 if isinstance(expression.this, list): 2375 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2376 if expression.this: 2377 modulus = self.sql(expression, "this") 2378 remainder = self.sql(expression, "expression") 2379 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2380 2381 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2382 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2383 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2385 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2386 this = self.sql(expression, "this") 2387 2388 for_values_or_default = expression.expression 2389 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2390 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2391 else: 2392 for_values_or_default = " DEFAULT" 2393 2394 return f"PARTITION OF {this}{for_values_or_default}"
2396 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2397 kind = expression.args.get("kind") 2398 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2399 for_or_in = expression.args.get("for_or_in") 2400 for_or_in = f" {for_or_in}" if for_or_in else "" 2401 lock_type = expression.args.get("lock_type") 2402 override = " OVERRIDE" if expression.args.get("override") else "" 2403 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2409 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2410 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2411 statistics = expression.args.get("statistics") 2412 statistics_sql = "" 2413 if statistics is not None: 2414 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2415 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2417 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2418 this = self.sql(expression, "this") 2419 this = f"HISTORY_TABLE={this}" if this else "" 2420 data_consistency: str | None = self.sql(expression, "data_consistency") 2421 data_consistency = ( 2422 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2423 ) 2424 retention_period: str | None = self.sql(expression, "retention_period") 2425 retention_period = ( 2426 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2427 ) 2428 2429 if this: 2430 on_sql = self.func("ON", this, data_consistency, retention_period) 2431 else: 2432 on_sql = "ON" if expression.args.get("on") else "OFF" 2433 2434 sql = f"SYSTEM_VERSIONING={on_sql}" 2435 2436 return f"WITH({sql})" if expression.args.get("with_") else sql
2438 def insert_sql(self, expression: exp.Insert) -> str: 2439 hint = self.sql(expression, "hint") 2440 overwrite = expression.args.get("overwrite") 2441 2442 if isinstance(expression.this, exp.Directory): 2443 this = " OVERWRITE" if overwrite else " INTO" 2444 else: 2445 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2446 2447 stored = self.sql(expression, "stored") 2448 stored = f" {stored}" if stored else "" 2449 alternative = expression.args.get("alternative") 2450 alternative = f" OR {alternative}" if alternative else "" 2451 ignore = " IGNORE" if expression.args.get("ignore") else "" 2452 is_function = expression.args.get("is_function") 2453 if is_function: 2454 this = f"{this} FUNCTION" 2455 this = f"{this} {self.sql(expression, 'this')}" 2456 2457 exists = " IF EXISTS" if expression.args.get("exists") else "" 2458 where = self.sql(expression, "where") 2459 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2460 using = self.expressions(expression, key="using", flat=True) 2461 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2462 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2463 on_conflict = self.sql(expression, "conflict") 2464 on_conflict = f" {on_conflict}" if on_conflict else "" 2465 by_name = " BY NAME" if expression.args.get("by_name") else "" 2466 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2467 returning = self.sql(expression, "returning") 2468 2469 if self.RETURNING_END: 2470 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2471 else: 2472 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2473 2474 partition_by = self.sql(expression, "partition") 2475 partition_by = f" {partition_by}" if partition_by else "" 2476 settings = self.sql(expression, "settings") 2477 settings = f" {settings}" if settings else "" 2478 2479 source = self.sql(expression, "source") 2480 source = f"TABLE {source}" if source else "" 2481 2482 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2483 return self.prepend_ctes(expression, sql)
2501 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2502 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2503 2504 constraint = self.sql(expression, "constraint") 2505 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2506 2507 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2508 if conflict_keys: 2509 conflict_keys = f"({conflict_keys})" 2510 2511 index_predicate = self.sql(expression, "index_predicate") 2512 conflict_keys = f"{conflict_keys}{index_predicate} " 2513 2514 action = self.sql(expression, "action") 2515 2516 expressions = self.expressions(expression, flat=True) 2517 if expressions: 2518 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2519 expressions = f" {set_keyword}{expressions}" 2520 2521 where = self.sql(expression, "where") 2522 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2527 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2528 fields = self.sql(expression, "fields") 2529 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2530 escaped = self.sql(expression, "escaped") 2531 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2532 items = self.sql(expression, "collection_items") 2533 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2534 keys = self.sql(expression, "map_keys") 2535 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2536 lines = self.sql(expression, "lines") 2537 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2538 null = self.sql(expression, "null") 2539 null = f" NULL DEFINED AS {null}" if null else "" 2540 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2568 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2569 table = self.table_parts(expression) 2570 only = "ONLY " if expression.args.get("only") else "" 2571 partition = self.sql(expression, "partition") 2572 partition = f" {partition}" if partition else "" 2573 version = self.sql(expression, "version") 2574 version = f" {version}" if version else "" 2575 alias = self.sql(expression, "alias") 2576 alias = f"{sep}{alias}" if alias else "" 2577 2578 sample = self.sql(expression, "sample") 2579 post_alias = "" 2580 pre_alias = "" 2581 2582 if self.dialect.ALIAS_POST_TABLESAMPLE: 2583 pre_alias = sample 2584 else: 2585 post_alias = sample 2586 2587 if self.dialect.ALIAS_POST_VERSION: 2588 pre_alias = f"{pre_alias}{version}" 2589 else: 2590 post_alias = f"{post_alias}{version}" 2591 2592 hints = self.expressions(expression, key="hints", sep=" ") 2593 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2594 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2595 joins = self.indent( 2596 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2597 ) 2598 laterals = self.expressions(expression, key="laterals", sep="") 2599 2600 file_format = self.sql(expression, "format") 2601 pattern = self.sql(expression, "pattern") 2602 if file_format: 2603 pattern = f", PATTERN => {pattern}" if pattern else "" 2604 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2605 elif pattern: 2606 file_format = f" (PATTERN => {pattern})" 2607 2608 ordinality = expression.args.get("ordinality") or "" 2609 if ordinality: 2610 ordinality = f" WITH ORDINALITY{alias}" 2611 alias = "" 2612 2613 when = self.sql(expression, "when") 2614 if when: 2615 if self.HISTORICAL_DATA_POST_ALIAS: 2616 alias = f"{alias} {when}" 2617 else: 2618 table = f"{table} {when}" 2619 2620 changes = self.sql(expression, "changes") 2621 changes = f" {changes}" if changes else "" 2622 2623 rows_from = self.expressions(expression, key="rows_from") 2624 if rows_from: 2625 table = f"ROWS FROM {self.wrap(rows_from)}" 2626 2627 indexed = expression.args.get("indexed") 2628 if indexed is not None: 2629 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2630 else: 2631 indexed = "" 2632 2633 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2635 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2636 table = self.func("TABLE", expression.this) 2637 alias = self.sql(expression, "alias") 2638 alias = f" AS {alias}" if alias else "" 2639 sample = self.sql(expression, "sample") 2640 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2641 joins = self.indent( 2642 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2643 ) 2644 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2646 def tablesample_sql( 2647 self, 2648 expression: exp.TableSample, 2649 tablesample_keyword: str | None = None, 2650 ) -> str: 2651 if expression.expressions: 2652 self.unsupported("Multiple sample sets are not supported") 2653 return "" 2654 2655 method = self.sql(expression, "method") 2656 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2657 numerator = self.sql(expression, "bucket_numerator") 2658 denominator = self.sql(expression, "bucket_denominator") 2659 field = self.sql(expression, "bucket_field") 2660 field = f" ON {field}" if field else "" 2661 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2662 seed = self.sql(expression, "seed") 2663 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2664 2665 size = self.sql(expression, "size") 2666 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2667 size = f"{size} ROWS" 2668 2669 percent = self.sql(expression, "percent") 2670 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2671 percent = f"{percent} PERCENT" 2672 2673 expr = f"{bucket}{percent}{size}" 2674 if self.TABLESAMPLE_REQUIRES_PARENS: 2675 expr = f"({expr})" 2676 2677 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2754 def pivot_sql(self, expression: exp.Pivot) -> str: 2755 expressions = self.expressions(expression, flat=True) 2756 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2757 2758 group = self.sql(expression, "group") 2759 2760 if expression.this: 2761 this = self.sql(expression, "this") 2762 if not expressions: 2763 sql = f"UNPIVOT {this}" 2764 else: 2765 on = f"{self.seg('ON')} {expressions}" 2766 into = self.sql(expression, "into") 2767 into = f"{self.seg('INTO')} {into}" if into else "" 2768 using = self.expressions(expression, key="using", flat=True) 2769 using = f"{self.seg('USING')} {using}" if using else "" 2770 sql = f"{direction} {this}{on}{into}{using}{group}" 2771 return self.prepend_ctes(expression, sql) 2772 2773 if not expression.unpivot: 2774 # Wrap IN-list values with explicit aliases where the target dialect would differ 2775 new_field_exprs = self._pivot_in_value_aliases(expression) 2776 if new_field_exprs is not None: 2777 expression.fields[0].set("expressions", new_field_exprs) 2778 2779 alias = self.sql(expression, "alias") 2780 if alias: 2781 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2782 2783 fields = self.expressions( 2784 expression, 2785 "fields", 2786 sep=" ", 2787 dynamic=True, 2788 new_line=True, 2789 skip_first=True, 2790 skip_last=True, 2791 ) 2792 2793 include_nulls = expression.args.get("include_nulls") 2794 if include_nulls is not None: 2795 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2796 else: 2797 nulls = "" 2798 2799 default_on_null = self.sql(expression, "default_on_null") 2800 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2801 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2802 return self.prepend_ctes(expression, sql)
2845 def update_sql(self, expression: exp.Update) -> str: 2846 hint = self.sql(expression, "hint") 2847 this = self.sql(expression, "this") 2848 join_sql, from_sql = self._update_from_joins_sql(expression) 2849 set_sql = self.expressions(expression, flat=True) 2850 where_sql = self.sql(expression, "where") 2851 returning = self.sql(expression, "returning") 2852 order = self.sql(expression, "order") 2853 limit = self.sql(expression, "limit") 2854 if self.RETURNING_END: 2855 expression_sql = f"{from_sql}{where_sql}{returning}" 2856 else: 2857 expression_sql = f"{returning}{from_sql}{where_sql}" 2858 options = self.expressions(expression, key="options") 2859 options = f" OPTION({options})" if options else "" 2860 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2861 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2863 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2864 values_as_table = values_as_table and self.VALUES_AS_TABLE 2865 2866 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2867 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2868 args = self.expressions(expression) 2869 alias = self.sql(expression, "alias") 2870 values = f"VALUES{self.seg('')}{args}" 2871 values = ( 2872 f"({values})" 2873 if self.WRAP_DERIVED_VALUES 2874 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2875 else values 2876 ) 2877 values = self.query_modifiers(expression, values) 2878 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2879 return f"{values} AS {alias}{pivots}" if alias else f"{values}{pivots}" 2880 2881 # Converts `VALUES...` expression into a series of select unions. 2882 alias_node = expression.args.get("alias") 2883 column_names = alias_node and alias_node.columns 2884 2885 selects: list[exp.Query] = [] 2886 2887 for i, tup in enumerate(expression.expressions): 2888 row = tup.expressions 2889 2890 if i == 0 and column_names: 2891 row = [ 2892 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2893 ] 2894 2895 selects.append(exp.Select(expressions=row)) 2896 2897 if self.pretty: 2898 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2899 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2900 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2901 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2902 for key in ("order", "limit", "offset"): 2903 if value := expression.args.get(key): 2904 query.set(key, value.pop()) 2905 subquery = query.subquery(alias_node and alias_node.this, copy=False) 2906 subquery.set("pivots", expression.args.pop("pivots", None)) 2907 return self.subquery_sql(subquery) 2908 2909 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2910 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2911 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2912 return f"({self.query_modifiers(expression, unions)}){alias}{pivots}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2917 @unsupported_args("expressions") 2918 def into_sql(self, expression: exp.Into) -> str: 2919 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2920 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2921 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2934 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2935 this = self.sql(expression, "this") 2936 2937 columns = self.expressions(expression, flat=True) 2938 2939 from_sql = self.sql(expression, "from_index") 2940 from_sql = f" FROM {from_sql}" if from_sql else "" 2941 2942 properties = expression.args.get("properties") 2943 properties_sql = ( 2944 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2945 ) 2946 2947 return f"{this}({columns}){from_sql}{properties_sql}"
2956 def group_sql(self, expression: exp.Group) -> str: 2957 group_by_all = expression.args.get("all") 2958 if group_by_all is True: 2959 modifier = " ALL" 2960 elif group_by_all is False: 2961 modifier = " DISTINCT" 2962 else: 2963 modifier = "" 2964 2965 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2966 2967 grouping_sets = self.expressions(expression, key="grouping_sets") 2968 cube = self.expressions(expression, key="cube") 2969 rollup = self.expressions(expression, key="rollup") 2970 2971 groupings = csv( 2972 self.seg(grouping_sets) if grouping_sets else "", 2973 self.seg(cube) if cube else "", 2974 self.seg(rollup) if rollup else "", 2975 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2976 sep=self.GROUPINGS_SEP, 2977 ) 2978 2979 if ( 2980 expression.expressions 2981 and groupings 2982 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2983 ): 2984 add_separator = True 2985 2986 if grouping_sets: 2987 if self.SUPPORTS_GROUPING_SETS_AS_SUFFIX: 2988 add_separator = False 2989 else: 2990 self.unsupported( 2991 "GROUPING SETS without a comma after GROUP BY expressions is not supported" 2992 ) 2993 2994 if add_separator: 2995 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2996 2997 return f"{group_by}{groupings}"
3009 def connect_sql(self, expression: exp.Connect) -> str: 3010 start = self.sql(expression, "start") 3011 start = self.seg(f"START WITH {start}") if start else "" 3012 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 3013 connect = self.sql(expression, "connect") 3014 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 3015 return start + connect
3020 def join_sql(self, expression: exp.Join, join_op: str | None = None) -> str: 3021 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 3022 side = None 3023 else: 3024 side = expression.side 3025 3026 op_sql = " ".join( 3027 op 3028 for op in ( 3029 expression.method, 3030 "GLOBAL" if expression.args.get("global_") else None, 3031 side, 3032 expression.kind, 3033 expression.hint if self.JOIN_HINTS else None, 3034 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 3035 ) 3036 if op 3037 ) 3038 match_cond = self.sql(expression, "match_condition") 3039 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 3040 nearest = self.sql(expression, "nearest") 3041 nearest = f" {nearest}" if nearest else "" 3042 on_sql = self.sql(expression, "on") 3043 using = expression.args.get("using") 3044 3045 if not on_sql and using: 3046 on_sql = csv(*(self.sql(column) for column in using)) 3047 3048 this = expression.this 3049 this_sql = self.sql(this) 3050 3051 exprs = self.expressions(expression) 3052 if exprs: 3053 this_sql = f"{this_sql},{self.seg(exprs)}" 3054 3055 if on_sql: 3056 on_sql = self.indent(on_sql, skip_first=True) 3057 space = self.seg(" " * self.pad) if self.pretty else " " 3058 if using: 3059 on_sql = f"{space}USING ({on_sql})" 3060 else: 3061 on_sql = f"{space}ON {on_sql}" 3062 elif not op_sql and not nearest: 3063 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 3064 return f" {this_sql}" 3065 3066 return f", {this_sql}" 3067 3068 if join_op: 3069 op_sql = join_op 3070 elif op_sql != "STRAIGHT_JOIN": 3071 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 3072 3073 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3074 return f"{self.seg(op_sql)} {this_sql}{match_cond}{nearest}{on_sql}{pivots}"
3076 def nearest_sql(self, expression: exp.Nearest) -> str: 3077 if not self.SUPPORTS_NEAREST_JOIN: 3078 self.unsupported("NEAREST joins are not supported") 3079 3080 approximate = "APPROX " if expression.args.get("approximate") else "" 3081 metric = "SIMILARITY" if expression.args.get("similarity") else "DISTANCE" 3082 return f"{approximate}NEAREST {self.sql(expression, 'count')} BY {metric} {self.sql(expression, 'this')}"
def
lambda_sql( self, expression: sqlglot.expressions.query.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
3089 def lateral_op(self, expression: exp.Lateral) -> str: 3090 cross_apply = expression.args.get("cross_apply") 3091 3092 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 3093 if cross_apply is True: 3094 op = "INNER JOIN " 3095 elif cross_apply is False: 3096 op = "LEFT JOIN " 3097 else: 3098 op = "" 3099 3100 return f"{op}LATERAL"
3102 def lateral_sql(self, expression: exp.Lateral) -> str: 3103 this = self.sql(expression, "this") 3104 3105 if expression.args.get("view"): 3106 alias = expression.args["alias"] 3107 columns = self.expressions(alias, key="columns", flat=True) 3108 table = f" {alias.name}" if alias.name else "" 3109 columns = f" AS {columns}" if columns else "" 3110 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 3111 return f"{op_sql}{self.sep()}{this}{table}{columns}" 3112 3113 table_alias = expression.args.get("alias") 3114 offset = expression.this.args.get("offset") 3115 3116 if ( 3117 self.UNNEST_WITH_ORDINALITY 3118 and table_alias 3119 and isinstance(expression.this, exp.Unnest) 3120 and isinstance(offset, exp.Identifier) 3121 ): 3122 # UNNEST ... WITH ORDINALITY stores the ordinality column's name in Unnest.offset 3123 table_alias = table_alias.copy() 3124 table_alias.append("columns", offset.copy()) 3125 3126 alias = self.sql(table_alias) 3127 alias = f" AS {alias}" if alias else "" 3128 3129 ordinality = expression.args.get("ordinality") or "" 3130 if ordinality: 3131 ordinality = f" WITH ORDINALITY{alias}" 3132 alias = "" 3133 3134 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
3136 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 3137 this = self.sql(expression, "this") 3138 3139 if expression.is_limit_all and not self.dialect.SUPPORTS_LIMIT_ALL: 3140 return this 3141 3142 args = [ 3143 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 3144 for e in (expression.args.get(k) for k in ("offset", "expression")) 3145 if e 3146 ] 3147 3148 args_sql = ", ".join(self.sql(e) for e in args) 3149 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 3150 expressions = self.expressions(expression, flat=True) 3151 limit_options = self.sql(expression, "limit_options") 3152 expressions = f" BY {expressions}" if expressions else "" 3153 3154 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
3156 def offset_sql(self, expression: exp.Offset) -> str: 3157 this = self.sql(expression, "this") 3158 value = expression.expression 3159 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 3160 expressions = self.expressions(expression, flat=True) 3161 expressions = f" BY {expressions}" if expressions else "" 3162 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
3164 def setitem_sql(self, expression: exp.SetItem) -> str: 3165 kind = self.sql(expression, "kind") 3166 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 3167 kind = "" 3168 else: 3169 kind = f"{kind} " if kind else "" 3170 this = self.sql(expression, "this") 3171 expressions = self.expressions(expression) 3172 collate = self.sql(expression, "collate") 3173 collate = f" COLLATE {collate}" if collate else "" 3174 global_ = "GLOBAL " if expression.args.get("global_") else "" 3175 return f"{global_}{kind}{this}{expressions}{collate}"
3182 def queryband_sql(self, expression: exp.QueryBand) -> str: 3183 this = self.sql(expression, "this") 3184 update = " UPDATE" if expression.args.get("update") else "" 3185 scope = self.sql(expression, "scope") 3186 scope = f" FOR {scope}" if scope else "" 3187 3188 return f"QUERY_BAND = {this}{update}{scope}"
3193 def lock_sql(self, expression: exp.Lock) -> str: 3194 if not self.LOCKING_READS_SUPPORTED: 3195 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 3196 return "" 3197 3198 update = expression.args["update"] 3199 key = expression.args.get("key") 3200 if update: 3201 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 3202 else: 3203 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 3204 expressions = self.expressions(expression, flat=True) 3205 expressions = f" OF {expressions}" if expressions else "" 3206 wait = expression.args.get("wait") 3207 3208 if wait is not None: 3209 if isinstance(wait, exp.Literal): 3210 wait = f" WAIT {self.sql(wait)}" 3211 else: 3212 wait = " NOWAIT" if wait else " SKIP LOCKED" 3213 3214 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:
3222 def escape_str( 3223 self, 3224 text: str, 3225 escape_backslash: bool = True, 3226 delimiter: str | None = None, 3227 escaped_delimiter: str | None = None, 3228 is_byte_string: bool = False, 3229 is_bytes: bool = False, 3230 ) -> str: 3231 if is_byte_string: 3232 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3233 else: 3234 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3235 3236 if supports_escape_sequences: 3237 text = "".join( 3238 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3239 for ch in text 3240 ) 3241 3242 if is_bytes and "x" in self.dialect.tokenizer_class.NUMERIC_ESCAPES: 3243 # Escape high bytes so they aren't re-encoded as UTF-8 characters. 3244 text = _HIGH_BYTE_RE.sub(lambda m: f"\\x{ord(m.group()):02x}", text) 3245 3246 delimiter = delimiter or self.dialect.QUOTE_END 3247 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3248 3249 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3251 def loaddata_sql(self, expression: exp.LoadData) -> str: 3252 is_overwrite = expression.args.get("overwrite") 3253 overwrite = " OVERWRITE" if is_overwrite else "" 3254 this = self.sql(expression, "this") 3255 3256 files = expression.args.get("files") 3257 if files: 3258 files_sql = self.expressions(files, flat=True) 3259 files_sql = f"FILES{self.wrap(files_sql)}" 3260 if is_overwrite: 3261 this = f" {this}" 3262 elif expression.args.get("temp"): 3263 this = f" INTO TEMP TABLE {this}" 3264 else: 3265 this = f" INTO TABLE {this}" 3266 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3267 3268 local = " LOCAL" if expression.args.get("local") else "" 3269 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3270 this = f" INTO TABLE {this}" 3271 partition = self.sql(expression, "partition") 3272 partition = f" {partition}" if partition else "" 3273 input_format = self.sql(expression, "input_format") 3274 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3275 serde = self.sql(expression, "serde") 3276 serde = f" SERDE {serde}" if serde else "" 3277 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3291 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3292 this = self.sql(expression, "this") 3293 this = f"{this} " if this else this 3294 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3295 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3297 def withfill_sql(self, expression: exp.WithFill) -> str: 3298 from_sql = self.sql(expression, "from_") 3299 from_sql = f" FROM {from_sql}" if from_sql else "" 3300 to_sql = self.sql(expression, "to") 3301 to_sql = f" TO {to_sql}" if to_sql else "" 3302 step_sql = self.sql(expression, "step") 3303 step_sql = f" STEP {step_sql}" if step_sql else "" 3304 interpolated_values = [ 3305 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3306 if isinstance(e, exp.Alias) 3307 else self.sql(e, "this") 3308 for e in expression.args.get("interpolate") or [] 3309 ] 3310 interpolate = ( 3311 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3312 ) 3313 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3367 def ordered_sql(self, expression: exp.Ordered) -> str: 3368 desc = expression.args.get("desc") 3369 asc = not desc 3370 3371 nulls_first = expression.args.get("nulls_first") 3372 nulls_last = not nulls_first 3373 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3374 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3375 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3376 3377 this = self.sql(expression, "this") 3378 3379 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3380 nulls_sort_change = "" 3381 if nulls_first and ( 3382 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3383 ): 3384 nulls_sort_change = " NULLS FIRST" 3385 elif ( 3386 nulls_last 3387 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3388 and not nulls_are_last 3389 ): 3390 nulls_sort_change = " NULLS LAST" 3391 3392 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3393 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3394 window = expression.find_ancestor(exp.Window, exp.Select) 3395 3396 if isinstance(window, exp.Window): 3397 window_this = window.this 3398 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3399 window_this = window_this.this 3400 spec = window.args.get("spec") 3401 else: 3402 window_this = None 3403 spec = None 3404 3405 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3406 # without a spec or with a ROWS spec, but not with RANGE 3407 if not ( 3408 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3409 and (not spec or spec.text("kind").upper() == "ROWS") 3410 ): 3411 if window_this and spec: 3412 self.unsupported( 3413 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3414 ) 3415 nulls_sort_change = "" 3416 elif self.NULL_ORDERING_SUPPORTED is False and ( 3417 (asc and nulls_sort_change == " NULLS LAST") 3418 or (desc and nulls_sort_change == " NULLS FIRST") 3419 ): 3420 # BigQuery does not allow these ordering/nulls combinations when used under 3421 # an aggregation func or under a window containing one 3422 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3423 3424 if isinstance(ancestor, exp.Window): 3425 ancestor = ancestor.this 3426 if isinstance(ancestor, exp.AggFunc): 3427 self.unsupported( 3428 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3429 ) 3430 nulls_sort_change = "" 3431 elif self.NULL_ORDERING_SUPPORTED is None: 3432 if expression.this.is_int: 3433 self.unsupported( 3434 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3435 ) 3436 elif not isinstance(expression.this, exp.Rand): 3437 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3438 target = self.sql(resolved) if resolved is not None else this 3439 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3440 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3441 nulls_sort_change = "" 3442 3443 with_fill = self.sql(expression, "with_fill") 3444 with_fill = f" {with_fill}" if with_fill else "" 3445 3446 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3456 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3457 partition = self.partition_by_sql(expression) 3458 order = self.sql(expression, "order") 3459 measures = self.expressions(expression, key="measures") 3460 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3461 rows = self.sql(expression, "rows") 3462 rows = self.seg(rows) if rows else "" 3463 after = self.sql(expression, "after") 3464 after = self.seg(after) if after else "" 3465 pattern = self.sql(expression, "pattern") 3466 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3467 definition_sqls = [ 3468 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3469 for definition in expression.args.get("define", []) 3470 ] 3471 definitions = self.expressions(sqls=definition_sqls) 3472 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3473 body = "".join( 3474 ( 3475 partition, 3476 order, 3477 measures, 3478 rows, 3479 after, 3480 pattern, 3481 define, 3482 ) 3483 ) 3484 alias = self.sql(expression, "alias") 3485 alias = f" {alias}" if alias else "" 3486 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3488 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3489 limit = expression.args.get("limit") 3490 3491 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3492 count = limit.args.get("count") 3493 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3494 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3495 limit = exp.Limit( 3496 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3497 ) 3498 elif ( 3499 self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit) and not limit.is_limit_all 3500 ): 3501 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3502 3503 return csv( 3504 *sqls, 3505 *[self.sql(join) for join in expression.args.get("joins") or []], 3506 self.sql(expression, "match"), 3507 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3508 self.sql(expression, "prewhere"), 3509 self.sql(expression, "where"), 3510 self.sql(expression, "connect"), 3511 self.sql(expression, "group"), 3512 self.sql(expression, "having"), 3513 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3514 self.sql(expression, "order"), 3515 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3516 *self.after_limit_modifiers(expression), 3517 self.sql(expression, "for_"), 3518 self.options_modifier(expression), 3519 sep="", 3520 )
3526 def forclause_sql(self, expression: exp.ForClause) -> str: 3527 kind = expression.args["kind"] 3528 if kind == "BROWSE": 3529 return f"{self.sep()}FOR BROWSE" 3530 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3531 # the target dialect doesn't support QueryOption, so we drop the clause. 3532 options = self.expressions(expression, key="expressions") 3533 if not options: 3534 return "" 3535 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]:
3554 def select_sql(self, expression: exp.Select) -> str: 3555 into = expression.args.get("into") 3556 if not self.SUPPORTS_SELECT_INTO and into: 3557 into.pop() 3558 3559 hint = self.sql(expression, "hint") 3560 distinct = self.sql(expression, "distinct") 3561 distinct = f" {distinct}" if distinct else "" 3562 kind = self.sql(expression, "kind") 3563 3564 limit = expression.args.get("limit") 3565 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3566 top = self.limit_sql(limit, top=True) 3567 limit.pop() 3568 else: 3569 top = "" 3570 3571 expressions = self.expressions(expression) 3572 3573 if kind: 3574 if kind in self.SELECT_KINDS: 3575 kind = f" AS {kind}" 3576 else: 3577 if kind == "STRUCT": 3578 expressions = self.expressions( 3579 sqls=[ 3580 self.sql( 3581 exp.Struct( 3582 expressions=[ 3583 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3584 if isinstance(e, exp.Alias) 3585 else e 3586 for e in expression.expressions 3587 ] 3588 ) 3589 ) 3590 ] 3591 ) 3592 kind = "" 3593 3594 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3595 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3596 3597 exclude = expression.args.get("exclude") 3598 3599 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3600 exclude_sql = self.expressions(sqls=exclude, flat=True) 3601 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3602 3603 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3604 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3605 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3606 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3607 sql = self.query_modifiers( 3608 expression, 3609 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3610 self.sql(expression, "into", comment=False), 3611 self.sql(expression, "from_", comment=False), 3612 ) 3613 3614 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3615 if expression.args.get("with_"): 3616 sql = self.maybe_comment(sql, expression) 3617 expression.pop_comments() 3618 3619 sql = self.prepend_ctes(expression, sql) 3620 3621 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3622 expression.set("exclude", None) 3623 subquery = expression.subquery(copy=False) 3624 star = exp.Star(except_=exclude) 3625 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3626 3627 if not self.SUPPORTS_SELECT_INTO and into: 3628 if into.args.get("temporary"): 3629 table_kind = " TEMPORARY" 3630 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3631 table_kind = " UNLOGGED" 3632 else: 3633 table_kind = "" 3634 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3635 3636 return sql
3648 def star_sql(self, expression: exp.Star) -> str: 3649 except_ = self.expressions(expression, key="except_", flat=True) 3650 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3651 replace = self.expressions(expression, key="replace", flat=True) 3652 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3653 rename = self.expressions(expression, key="rename", flat=True) 3654 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3655 ilike = self.sql(expression, "ilike") 3656 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3657 return f"*{ilike}{except_}{replace}{rename}"
3673 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3674 alias = self.sql(expression, "alias") 3675 alias = f"{sep}{alias}" if alias else "" 3676 sample = self.sql(expression, "sample") 3677 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3678 alias = f"{sample}{alias}" 3679 3680 # Set to None so it's not generated again by self.query_modifiers() 3681 expression.set("sample", None) 3682 3683 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3684 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3685 return self.prepend_ctes(expression, sql)
3691 def unnest_sql(self, expression: exp.Unnest) -> str: 3692 args = self.expressions(expression, flat=True) 3693 3694 alias = expression.args.get("alias") 3695 offset = expression.args.get("offset") 3696 3697 if self.UNNEST_WITH_ORDINALITY: 3698 if alias and isinstance(offset, exp.Expr): 3699 alias.append("columns", offset) 3700 expression.set("offset", None) 3701 3702 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3703 columns = alias.columns 3704 alias = self.sql(columns[0]) if columns else "" 3705 else: 3706 alias = self.sql(alias) 3707 3708 alias = f" AS {alias}" if alias else alias 3709 if self.UNNEST_WITH_ORDINALITY: 3710 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3711 else: 3712 if isinstance(offset, exp.Expr): 3713 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3714 elif offset: 3715 suffix = f"{alias} WITH OFFSET" 3716 else: 3717 suffix = alias 3718 3719 return f"UNNEST({args}){suffix}"
3728 def window_sql(self, expression: exp.Window) -> str: 3729 this = self.sql(expression, "this") 3730 partition = self.partition_by_sql(expression) 3731 order = expression.args.get("order") 3732 order = self.order_sql(order, flat=True) if order else "" 3733 spec = self.sql(expression, "spec") 3734 alias = self.sql(expression, "alias") 3735 over = self.sql(expression, "over") or "OVER" 3736 3737 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3738 3739 first = expression.args.get("first") 3740 if first is None: 3741 first = "" 3742 else: 3743 first = "FIRST" if first else "LAST" 3744 3745 if not partition and not order and not spec and alias: 3746 # WINDOW w2 AS w1 is unparseable in DuckDB/Postgres; parens are required. 3747 if expression.arg_key == "windows": 3748 return f"{this} ({alias})" 3749 return f"{this} {alias}" 3750 3751 args = self.format_args( 3752 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3753 ) 3754 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
def
partitionbyexcluding_sql(self, expression: sqlglot.expressions.query.PartitionByExcluding) -> str:
3763 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3764 kind = self.sql(expression, "kind") 3765 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3766 end = ( 3767 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3768 or "CURRENT ROW" 3769 ) 3770 3771 window_spec = f"{kind} BETWEEN {start} AND {end}" 3772 3773 exclude = self.sql(expression, "exclude") 3774 if exclude: 3775 if self.SUPPORTS_WINDOW_EXCLUDE: 3776 window_spec += f" EXCLUDE {exclude}" 3777 else: 3778 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3779 3780 return window_spec
3787 def between_sql(self, expression: exp.Between) -> str: 3788 this = self.sql(expression, "this") 3789 low = self.sql(expression, "low") 3790 high = self.sql(expression, "high") 3791 symmetric = expression.args.get("symmetric") 3792 3793 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3794 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3795 3796 flag = ( 3797 " SYMMETRIC" 3798 if symmetric 3799 else " ASYMMETRIC" 3800 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3801 else "" # silently drop ASYMMETRIC – semantics identical 3802 ) 3803 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]:
3805 def bracket_offset_expressions( 3806 self, expression: exp.Bracket, index_offset: int | None = None 3807 ) -> list[exp.Expr]: 3808 if expression.args.get("json_access"): 3809 return expression.expressions 3810 3811 return apply_index_offset( 3812 expression.this, 3813 expression.expressions, 3814 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3815 dialect=self.dialect, 3816 )
3829 def any_sql(self, expression: exp.Any) -> str: 3830 this = self.sql(expression, "this") 3831 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3832 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3833 this = self.wrap(this) 3834 return f"ANY{this}" 3835 return f"ANY {this}"
3840 def case_sql(self, expression: exp.Case) -> str: 3841 this = self.sql(expression, "this") 3842 statements = [f"CASE {this}" if this else "CASE"] 3843 3844 for e in expression.args["ifs"]: 3845 statements.append(f"WHEN {self.sql(e, 'this')}") 3846 statements.append(f"THEN {self.sql(e, 'true')}") 3847 3848 default = self.sql(expression, "default") 3849 3850 if default: 3851 statements.append(f"ELSE {default}") 3852 3853 statements.append("END") 3854 3855 if self.pretty and self.too_wide(statements): 3856 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3857 3858 return " ".join(statements)
3870 def extract_sql(self, expression: exp.Extract) -> str: 3871 import sqlglot.dialects.dialect 3872 3873 this = ( 3874 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3875 if self.NORMALIZE_EXTRACT_DATE_PARTS 3876 else expression.this 3877 ) 3878 if self.EXTRACT_ALLOWS_QUOTES: 3879 this_sql = self.sql(this) 3880 elif isinstance(this, exp.WeekStart): 3881 this_sql = self.weekstart_name(this) 3882 else: 3883 this_sql = this.name 3884 expression_sql = self.sql(expression, "expression") 3885 3886 return f"EXTRACT({this_sql} FROM {expression_sql})"
3888 def trim_sql(self, expression: exp.Trim) -> str: 3889 trim_type = self.sql(expression, "position") 3890 3891 if trim_type == "LEADING": 3892 func_name = "LTRIM" 3893 elif trim_type == "TRAILING": 3894 func_name = "RTRIM" 3895 else: 3896 func_name = "TRIM" 3897 3898 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3900 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3901 args = expression.expressions 3902 if isinstance(expression, exp.ConcatWs): 3903 args = args[1:] # Skip the delimiter 3904 3905 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3906 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3907 3908 concat_coalesce = ( 3909 self.dialect.CONCAT_WS_COALESCE 3910 if isinstance(expression, exp.ConcatWs) 3911 else self.dialect.CONCAT_COALESCE 3912 ) 3913 3914 if not concat_coalesce and expression.args.get("coalesce"): 3915 3916 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3917 if not e.type: 3918 import sqlglot.optimizer.annotate_types 3919 3920 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3921 3922 if e.is_string or e.is_type(exp.DType.ARRAY): 3923 return e 3924 3925 return exp.func("coalesce", e, exp.Literal.string("")) 3926 3927 args = [_wrap_with_coalesce(e) for e in args] 3928 3929 return args
3931 def concat_sql(self, expression: exp.Concat) -> str: 3932 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3933 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3934 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3935 # instead of coalescing them to empty string. 3936 import sqlglot.dialects.dialect 3937 3938 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3939 3940 expressions = self.convert_concat_args(expression) 3941 3942 # Some dialects don't allow a single-argument CONCAT call 3943 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3944 return self.sql(expressions[0]) 3945 3946 return self.func("CONCAT", *expressions)
3948 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3949 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3950 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3951 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3952 all_args = expression.expressions 3953 expression.set("coalesce", True) 3954 return self.sql( 3955 exp.case() 3956 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3957 .else_(expression) 3958 ) 3959 3960 return self.func( 3961 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3962 )
3968 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3969 this = self.sql(expression, "this") 3970 this = f" {this}" if this else "" 3971 expressions = self.expressions(expression, flat=True) 3972 expressions = f" ({expressions})" if expressions else "" 3973 reference = self.sql(expression, "reference") 3974 reference = f" {reference}" if reference else "" 3975 delete = self.sql(expression, "delete") 3976 delete = f" ON DELETE {delete}" if delete else "" 3977 update = self.sql(expression, "update") 3978 update = f" ON UPDATE {update}" if update else "" 3979 options = self.expressions(expression, key="options", flat=True, sep=" ") 3980 options = f" {options}" if options else "" 3981 return f"FOREIGN KEY{this}{expressions}{reference}{delete}{update}{options}"
3983 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3984 this = self.sql(expression, "this") 3985 this = f" {this}" if this else "" 3986 expressions = self.expressions(expression, flat=True) 3987 include = self.sql(expression, "include") 3988 options = self.expressions(expression, key="options", flat=True, sep=" ") 3989 options = f" {options}" if options else "" 3990 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3999 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 4000 if self.MATCH_AGAINST_TABLE_PREFIX: 4001 expressions = [] 4002 for expr in expression.expressions: 4003 if isinstance(expr, exp.Table): 4004 expressions.append(f"TABLE {self.sql(expr)}") 4005 else: 4006 expressions.append(expr) 4007 else: 4008 expressions = expression.expressions 4009 4010 modifier = expression.args.get("modifier") 4011 modifier = f" {modifier}" if modifier else "" 4012 return ( 4013 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 4014 )
4028 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 4029 if isinstance(expression, exp.JSONPathPart): 4030 transform = self.TRANSFORMS.get(expression.__class__) 4031 if not callable(transform): 4032 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 4033 return "" 4034 4035 return transform(self, expression) 4036 4037 if isinstance(expression, int): 4038 return str(expression) 4039 4040 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 4041 escaped = expression.replace("'", "\\'") 4042 escaped = f"'{escaped}'" 4043 else: 4044 escaped = expression.replace('"', '\\"') 4045 escaped = f'"{escaped}"' 4046 4047 return escaped
4052 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 4053 # Output the Teradata column FORMAT override. 4054 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 4055 this = self.sql(expression, "this") 4056 fmt = self.sql(expression, "format") 4057 return f"{this} (FORMAT {fmt})"
4089 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 4090 null_handling = expression.args.get("null_handling") 4091 null_handling = f" {null_handling}" if null_handling else "" 4092 return_type = self.sql(expression, "return_type") 4093 return_type = f" RETURNING {return_type}" if return_type else "" 4094 strict = " STRICT" if expression.args.get("strict") else "" 4095 return self.func( 4096 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 4097 )
4099 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 4100 this = self.sql(expression, "this") 4101 order = self.sql(expression, "order") 4102 null_handling = expression.args.get("null_handling") 4103 null_handling = f" {null_handling}" if null_handling else "" 4104 return_type = self.sql(expression, "return_type") 4105 return_type = f" RETURNING {return_type}" if return_type else "" 4106 strict = " STRICT" if expression.args.get("strict") else "" 4107 return self.func( 4108 "JSON_ARRAYAGG", 4109 this, 4110 suffix=f"{order}{null_handling}{return_type}{strict})", 4111 )
4113 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 4114 path = self.sql(expression, "path") 4115 path = f" PATH {path}" if path else "" 4116 nested_schema = self.sql(expression, "nested_schema") 4117 4118 if nested_schema: 4119 return f"NESTED{path} {nested_schema}" 4120 4121 this = self.sql(expression, "this") 4122 kind = self.sql(expression, "kind") 4123 kind = f" {kind}" if kind else "" 4124 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 4125 4126 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 4127 return f"{this}{kind}{format_json}{path}{ordinality}"
4132 def jsontable_sql(self, expression: exp.JSONTable) -> str: 4133 this = self.sql(expression, "this") 4134 path = self.sql(expression, "path") 4135 path = f", {path}" if path else "" 4136 error_handling = expression.args.get("error_handling") 4137 error_handling = f" {error_handling}" if error_handling else "" 4138 empty_handling = expression.args.get("empty_handling") 4139 empty_handling = f" {empty_handling}" if empty_handling else "" 4140 schema = self.sql(expression, "schema") 4141 return self.func( 4142 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 4143 )
4145 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 4146 this = self.sql(expression, "this") 4147 kind = self.sql(expression, "kind") 4148 path = self.sql(expression, "path") 4149 path = f" {path}" if path else "" 4150 as_json = " AS JSON" if expression.args.get("as_json") else "" 4151 return f"{this} {kind}{path}{as_json}"
4153 def openjson_sql(self, expression: exp.OpenJSON) -> str: 4154 this = self.sql(expression, "this") 4155 path = self.sql(expression, "path") 4156 path = f", {path}" if path else "" 4157 expressions = self.expressions(expression) 4158 with_ = ( 4159 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 4160 if expressions 4161 else "" 4162 ) 4163 return f"OPENJSON({this}{path}){with_}"
4165 def in_sql(self, expression: exp.In) -> str: 4166 query = expression.args.get("query") 4167 unnest = expression.args.get("unnest") 4168 field = expression.args.get("field") 4169 is_global = " GLOBAL" if expression.args.get("is_global") else "" 4170 4171 if query: 4172 in_sql = self.sql(query) 4173 elif unnest: 4174 in_sql = self.in_unnest_op(unnest) 4175 elif field: 4176 in_sql = self.sql(field) 4177 else: 4178 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 4179 4180 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
4185 def interval_sql(self, expression: exp.Interval) -> str: 4186 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 4187 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 4188 exp.AutoRefreshProperty, 4189 ) 4190 interval_keyword = "INTERVAL" if include_keyword else "" 4191 unit_expression = expression.args.get("unit") 4192 unit = self.sql(unit_expression) if unit_expression else "" 4193 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 4194 unit = self.TIME_PART_SINGULARS.get(unit, unit) 4195 unit = f" {unit}" if unit else "" 4196 4197 if self.SINGLE_STRING_INTERVAL: 4198 this = expression.this.name if expression.this else "" 4199 if this: 4200 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 4201 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 4202 return f"{interval_keyword}{self.sql(exp.Literal.string(this))}{unit}" 4203 return f"{interval_keyword}{self.sql(exp.Literal.string(f'{this}{unit}'))}" 4204 return f"{interval_keyword}{unit}" 4205 4206 this = self.sql(expression, "this") 4207 if this: 4208 if not include_keyword and expression.this.is_string: 4209 this = expression.this.name 4210 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 4211 this = f"({this})" 4212 if include_keyword: 4213 this = f" {this}" 4214 4215 return f"{interval_keyword}{this}{unit}"
4220 def reference_sql(self, expression: exp.Reference) -> str: 4221 this = self.sql(expression, "this") 4222 expressions = self.expressions(expression, flat=True) 4223 expressions = f"({expressions})" if expressions else "" 4224 options = self.expressions(expression, key="options", flat=True, sep=" ") 4225 options = f" {options}" if options else "" 4226 return f"REFERENCES {this}{expressions}{options}"
4228 def anonymous_sql(self, expression: exp.Anonymous) -> str: 4229 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 4230 parent = expression.parent 4231 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 4232 4233 return self.func( 4234 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 4235 )
4255 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4256 alias = expression.args["alias"] 4257 4258 parent = expression.parent 4259 pivot = parent and parent.parent 4260 4261 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4262 identifier_alias = isinstance(alias, exp.Identifier) 4263 literal_alias = isinstance(alias, exp.Literal) 4264 4265 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4266 alias.replace(exp.Literal.string(alias.output_name)) 4267 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4268 alias.replace(exp.to_identifier(alias.output_name)) 4269 4270 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:
4311 def connector_sql( 4312 self, 4313 expression: exp.Connector, 4314 op: str, 4315 stack: list[str | exp.Expr] | None = None, 4316 ) -> str: 4317 if stack is not None: 4318 stack.append(expression.right) 4319 if expression.comments and self.comments: 4320 op = self.maybe_comment(op, comments=expression.comments) 4321 4322 stack.extend((op, expression.left)) 4323 return op 4324 4325 stack = [expression] 4326 sqls: list[str] = [] 4327 ops = set() 4328 4329 while stack: 4330 node = stack.pop() 4331 if isinstance(node, exp.Connector): 4332 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4333 else: 4334 sql = self.sql(node) 4335 if sqls and sqls[-1] in ops: 4336 sqls[-1] += f" {sql}" 4337 else: 4338 sqls.append(sql) 4339 4340 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4341 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4361 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4362 format_sql = self.sql(expression, "format") 4363 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4364 to_sql = self.sql(expression, "to") 4365 to_sql = f" {to_sql}" if to_sql else "" 4366 action = self.sql(expression, "action") 4367 action = f" {action}" if action else "" 4368 default = self.sql(expression, "default") 4369 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4370 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4400 def comment_sql(self, expression: exp.Comment) -> str: 4401 this = self.sql(expression, "this") 4402 kind = expression.args["kind"] 4403 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4404 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4405 expression_sql = self.sql(expression, "expression") 4406 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4408 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4409 this = self.sql(expression, "this") 4410 delete = " DELETE" if expression.args.get("delete") else "" 4411 recompress = self.sql(expression, "recompress") 4412 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4413 to_disk = self.sql(expression, "to_disk") 4414 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4415 to_volume = self.sql(expression, "to_volume") 4416 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4417 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4419 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4420 where = self.sql(expression, "where") 4421 group = self.sql(expression, "group") 4422 aggregates = self.expressions(expression, key="aggregates") 4423 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4424 4425 if not (where or group or aggregates) and len(expression.expressions) == 1: 4426 return f"TTL {self.expressions(expression, flat=True)}" 4427 4428 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4447 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4448 this = self.sql(expression, "this") 4449 4450 exists = "" 4451 if expression.args.get("exists"): 4452 if self.SUPPORTS_ALTER_COLUMN_IF_EXISTS: 4453 exists = " IF EXISTS" 4454 else: 4455 self.unsupported("ALTER COLUMN IF EXISTS is not supported by this dialect") 4456 4457 dtype = self.sql(expression, "dtype") 4458 if dtype: 4459 collate = self.sql(expression, "collate") 4460 collate = f" COLLATE {collate}" if collate else "" 4461 using = self.sql(expression, "using") 4462 using = f" USING {using}" if using else "" 4463 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4464 null_constraint = self._alter_column_null_constraint_sql(expression) 4465 4466 return ( 4467 f"ALTER COLUMN{exists} {this} {alter_set_type}{dtype}" 4468 f"{collate}{using}{null_constraint}" 4469 ) 4470 4471 default = self.sql(expression, "default") 4472 if default: 4473 return f"ALTER COLUMN{exists} {this} SET DEFAULT {default}" 4474 4475 comment = self.sql(expression, "comment") 4476 if comment: 4477 return f"ALTER COLUMN{exists} {this} COMMENT {comment}" 4478 4479 visible = expression.args.get("visible") 4480 if visible: 4481 return f"ALTER COLUMN{exists} {this} SET {visible}" 4482 4483 allow_null = expression.args.get("allow_null") 4484 drop = expression.args.get("drop") 4485 4486 if not drop and not allow_null: 4487 self.unsupported("Unsupported ALTER COLUMN syntax") 4488 4489 if allow_null is not None: 4490 keyword = "DROP" if drop else "SET" 4491 return f"ALTER COLUMN{exists} {this} {keyword} NOT NULL" 4492 4493 return f"ALTER COLUMN{exists} {this} DROP DEFAULT"
4506 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4507 this = self.sql(expression, "this") 4508 rename_from = self.sql(expression, "rename_from") 4509 if rename_from: 4510 if not self.SUPPORTS_CHANGE_COLUMN: 4511 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4512 return f"CHANGE COLUMN {rename_from} {this}" 4513 if not self.SUPPORTS_MODIFY_COLUMN: 4514 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4515 return f"MODIFY COLUMN {this}"
4531 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4532 compound = " COMPOUND" if expression.args.get("compound") else "" 4533 this = self.sql(expression, "this") 4534 expressions = self.expressions(expression, flat=True) 4535 expressions = f"({expressions})" if expressions else "" 4536 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4538 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4539 if not self.RENAME_TABLE_WITH_DB: 4540 # Remove db from tables 4541 expression = expression.transform( 4542 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4543 ).assert_is(exp.AlterRename) 4544 this = self.sql(expression, "this") 4545 to_kw = " TO" if include_to else "" 4546 return f"RENAME{to_kw} {this}"
4561 def alter_sql(self, expression: exp.Alter) -> str: 4562 actions = expression.actions 4563 4564 if ( 4565 not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN 4566 and actions 4567 and isinstance(actions[0], exp.ColumnDef) 4568 ): 4569 actions_sql = self.expressions(expression, key="actions", flat=True) 4570 actions_sql = f"ADD {actions_sql}" 4571 else: 4572 actions_list = [] 4573 for action in actions: 4574 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4575 action_sql = self.add_column_sql(action) 4576 else: 4577 action_sql = self.sql(action) 4578 if isinstance(action, exp.Query): 4579 action_sql = f"AS {action_sql}" 4580 4581 actions_list.append(action_sql) 4582 4583 actions_sql = self.format_args(*actions_list).lstrip("\n") 4584 4585 iceberg = ( 4586 "ICEBERG " 4587 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4588 else "" 4589 ) 4590 exists = " IF EXISTS" if expression.args.get("exists") else "" 4591 on_cluster = self.sql(expression, "cluster") 4592 on_cluster = f" {on_cluster}" if on_cluster else "" 4593 only = " ONLY" if expression.args.get("only") else "" 4594 options = expression.args.get("options") or [] 4595 options_sql = self.expressions( 4596 sqls=[o for o in options if not isinstance(o, self.ALTER_PARTITION_OPTIONS)] 4597 ) 4598 options_sql = f", {options_sql}" if options_sql and actions_sql else options_sql 4599 partitions_sql = self.expressions( 4600 sqls=[o for o in options if isinstance(o, self.ALTER_PARTITION_OPTIONS)], 4601 sep=" ", 4602 flat=True, 4603 ) 4604 if partitions_sql and (actions_sql or options_sql): 4605 partitions_sql = f" {partitions_sql}" 4606 kind = self.sql(expression, "kind") 4607 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4608 check = " WITH CHECK" if expression.args.get("check") else "" 4609 cascade = ( 4610 " CASCADE" 4611 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4612 else "" 4613 ) 4614 this = self.sql(expression, "this") 4615 this = f" {this}" if this else "" 4616 4617 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options_sql}{partitions_sql}{cascade}"
4624 def add_column_sql(self, expression: exp.Expr) -> str: 4625 column_text = "" 4626 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4627 if isinstance(expression, exp.ColumnDef): 4628 column_text = " COLUMN" 4629 elif isinstance(expression, exp.Schema): 4630 column_text = " COLUMNS" 4631 4632 if isinstance(expression, exp.Schema) and not self.ALTER_TABLE_ADD_COLUMNS_WRAPPED: 4633 sql = self.expressions(expression, flat=True) 4634 else: 4635 sql = self.sql(expression) 4636 4637 return f"ADD{column_text} {sql}"
4653 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4654 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4655 location = self.sql(expression, "location") 4656 location = f" {location}" if location else "" 4657 return f"ADD {exists}{self.sql(expression.this)}{location}"
4659 def distinct_sql(self, expression: exp.Distinct) -> str: 4660 this = self.expressions(expression, flat=True) 4661 4662 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4663 case = exp.case() 4664 for arg in expression.expressions: 4665 case = case.when(arg.is_(exp.null()), exp.null()) 4666 this = self.sql(case.else_(f"({this})")) 4667 4668 this = f" {this}" if this else "" 4669 4670 on = self.sql(expression, "on") 4671 on = f" ON {on}" if on else "" 4672 return f"DISTINCT{this}{on}"
def
nullif_zero_divisor( self, expression: sqlglot.expressions.core.Div | sqlglot.expressions.core.Mod) -> None:
4706 def div_sql(self, expression: exp.Div) -> str: 4707 l, r = expression.left, expression.right 4708 self.nullif_zero_divisor(expression) 4709 4710 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4711 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4712 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4713 4714 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4715 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4716 # Native integer division depends on the target's operand types, which may differ 4717 # from the source's (e.g. FLOOR(int) is DOUBLE in DuckDB), unless the operands are 4718 # integer literals or casts 4719 if all(e.is_int or isinstance(e, exp.Cast) for e in (l, r)) and ( 4720 expression.args.get("safe") or not self.SAFE_INT_DIVISION 4721 ): 4722 return self.sql(exp.IntDiv(this=l, expression=expression.right)) 4723 return self._truncated_div_sql(l, expression.right) 4724 4725 return self.binary(expression, "/")
4750 def escape_sql(self, expression: exp.Escape) -> str: 4751 this = expression.this 4752 if ( 4753 isinstance(this, (exp.Like, exp.ILike)) 4754 and isinstance(this.expression, (exp.All, exp.Any)) 4755 and not self.SUPPORTS_LIKE_QUANTIFIERS 4756 ): 4757 return self._like_sql(this, escape=expression) 4758 return self.binary(expression, "ESCAPE")
4769 def is_sql(self, expression: exp.Is) -> str: 4770 negate = expression.args.get("negate") 4771 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4772 positive = bool(expression.expression.this) != bool(negate) 4773 return self.sql(expression.this if positive else exp.not_(expression.this)) 4774 return self.binary(expression, "IS NOT" if negate else "IS")
4846 def mod_sql(self, expression: exp.Mod) -> str: 4847 self.nullif_zero_divisor(expression) 4848 this = self.sql(expression, "this") 4849 expr = self.sql(expression, "expression") 4850 sql = f"{this} {self.maybe_comment(self.MOD_OPERATOR, comments=expression.comments)} {expr}" 4851 4852 parent = expression.parent 4853 if isinstance(parent, self.MOD_PAREN_PARENT_TYPES) and parent.expression is expression: 4854 return f"({sql})" 4855 4856 return sql
4886 def log_sql(self, expression: exp.Log) -> str: 4887 this = expression.this 4888 expr = expression.expression 4889 4890 if self.dialect.LOG_BASE_FIRST is False: 4891 this, expr = expr, this 4892 elif self.dialect.LOG_BASE_FIRST is None and expr: 4893 if this.name in ("2", "10"): 4894 return self.func(f"LOG{this.name}", expr) 4895 4896 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4897 4898 return self.func("LOG", this, expr)
4907 def binary(self, expression: exp.Binary, op: str) -> str: 4908 sqls: list[str] = [] 4909 stack: list[None | str | exp.Expr] = [expression] 4910 binary_type = type(expression) 4911 is_is = binary_type is exp.Is and op in ("IS", "IS NOT") 4912 negate = expression.args.get("negate") 4913 4914 while stack: 4915 node = stack.pop() 4916 4917 if type(node) is binary_type and ( 4918 node is expression 4919 or (is_is and not isinstance(node.expression, exp.Boolean)) 4920 or node.args.get("negate") == negate 4921 ): 4922 op_func = node.args.get("operator") 4923 if op_func: 4924 op = f"OPERATOR({self.sql(op_func)})" 4925 elif is_is: 4926 op = "IS NOT" if node.args.get("negate") else "IS" 4927 4928 stack.append(node.args.get("expression")) 4929 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4930 stack.append(node.args.get("this")) 4931 else: 4932 sqls.append(self.sql(node)) 4933 4934 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4943 def function_fallback_sql(self, expression: exp.Func) -> str: 4944 args = [] 4945 4946 for key in expression.arg_types: 4947 arg_value = expression.args.get(key) 4948 4949 if isinstance(arg_value, list): 4950 for value in arg_value: 4951 args.append(value) 4952 elif arg_value is not None: 4953 args.append(arg_value) 4954 4955 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:
4972 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4973 arg_sqls = tuple( 4974 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4975 ) 4976 if self.pretty and self.too_wide(arg_sqls): 4977 return self.indent( 4978 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4979 ) 4980 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:
4985 def format_time( 4986 self, 4987 expression: exp.Expr, 4988 inverse_time_mapping: dict[str, str] | None = None, 4989 inverse_time_trie: dict | None = None, 4990 ) -> str | None: 4991 return format_time( 4992 self.sql(expression, "format"), 4993 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4994 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4995 )
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:
4997 def expressions( 4998 self, 4999 expression: exp.Expr | None = None, 5000 key: str | None = None, 5001 sqls: t.Collection[str | exp.Expr] | None = None, 5002 flat: bool = False, 5003 indent: bool = True, 5004 skip_first: bool = False, 5005 skip_last: bool = False, 5006 sep: str = ", ", 5007 prefix: str = "", 5008 dynamic: bool = False, 5009 new_line: bool = False, 5010 ) -> str: 5011 expressions = expression.args.get(key or "expressions") if expression else sqls 5012 5013 if not expressions: 5014 return "" 5015 5016 if flat: 5017 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 5018 5019 num_sqls = len(expressions) 5020 result_sqls = [] 5021 5022 for i, e in enumerate(expressions): 5023 sql = self.sql(e, comment=False) 5024 if not sql: 5025 continue 5026 5027 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 5028 5029 if self.pretty: 5030 if self.leading_comma: 5031 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 5032 else: 5033 result_sqls.append( 5034 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 5035 ) 5036 else: 5037 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 5038 5039 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 5040 if new_line: 5041 result_sqls.insert(0, "") 5042 result_sqls.append("") 5043 result_sql = "\n".join(s.rstrip() for s in result_sqls) 5044 else: 5045 result_sql = "".join(result_sqls) 5046 5047 return ( 5048 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 5049 if indent 5050 else result_sql 5051 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
5053 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 5054 flat = flat or isinstance(expression.parent, exp.Properties) 5055 expressions_sql = self.expressions(expression, flat=flat) 5056 if flat: 5057 return f"{op} {expressions_sql}" 5058 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
5060 def naked_property(self, expression: exp.Property) -> str: 5061 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 5062 if not property_name: 5063 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 5064 return f"{property_name} {self.sql(expression, 'this')}"
5072 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 5073 this = self.sql(expression, "this") 5074 expressions = self.no_identify(self.expressions, expression) 5075 expressions = ( 5076 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 5077 ) 5078 return f"{this}{expressions}" if expressions.strip() != "" else this
5097 def when_sql(self, expression: exp.When) -> str: 5098 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 5099 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 5100 condition = self.sql(expression, "condition") 5101 condition = f" AND {condition}" if condition else "" 5102 5103 then_expression = expression.args.get("then") 5104 if isinstance(then_expression, exp.Insert): 5105 this = ( 5106 "ALL BY NAME" 5107 if self.MERGE_ALL_BY_NAME and isinstance(then_expression.this, exp.Star) 5108 else self.sql(then_expression, "this") 5109 ) 5110 this = f"INSERT {this}" if this else "INSERT" 5111 then = self.sql(then_expression, "expression") 5112 then = f"{this} VALUES {then}" if then else this 5113 elif isinstance(then_expression, exp.Update): 5114 if isinstance(then_expression.args.get("expressions"), exp.Star): 5115 then = ( 5116 "UPDATE ALL BY NAME" 5117 if self.MERGE_ALL_BY_NAME 5118 else f"UPDATE {self.sql(then_expression, 'expressions')}" 5119 ) 5120 elif ( 5121 self.MERGE_ALL_BY_NAME 5122 and len(then_expression.expressions) == 1 5123 and isinstance(then_expression.expressions[0], exp.Star) 5124 ): 5125 then = "UPDATE ALL BY NAME" 5126 else: 5127 expressions_sql = self.expressions(then_expression) 5128 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 5129 else: 5130 then = self.sql(then_expression) 5131 5132 if isinstance(then_expression, (exp.Insert, exp.Update)): 5133 where = self.sql(then_expression, "where") 5134 if where and not self.SUPPORTS_MERGE_WHERE: 5135 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 5136 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 5137 where = "" 5138 then = f"{then}{where}" 5139 return f"WHEN {matched}{source}{condition} THEN {then}"
5144 def merge_sql(self, expression: exp.Merge) -> str: 5145 table = expression.this 5146 table_alias = "" 5147 5148 hints = table.args.get("hints") 5149 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 5150 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 5151 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 5152 5153 this = self.sql(table) 5154 using = f"USING {self.sql(expression, 'using')}" 5155 whens = self.sql(expression, "whens") 5156 5157 on = self.sql(expression, "on") 5158 on = f"ON {on}" if on else "" 5159 5160 if not on: 5161 on = self.expressions(expression, key="using_cond") 5162 on = f"USING ({on})" if on else "" 5163 5164 returning = self.sql(expression, "returning") 5165 if returning: 5166 whens = f"{whens}{returning}" 5167 5168 sep = self.sep() 5169 5170 return self.prepend_ctes( 5171 expression, 5172 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 5173 )
@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:
5179 @unsupported_args("default") 5180 def tonumber_sql(self, expression: exp.ToNumber) -> str: 5181 if not self.SUPPORTS_TO_NUMBER: 5182 self.unsupported("Unsupported TO_NUMBER function") 5183 return self._tonumber_cast_sql(expression) 5184 5185 fmt = expression.args.get("format") 5186 if not fmt: 5187 self.unsupported("Conversion format is required for TO_NUMBER") 5188 return self._tonumber_cast_sql(expression) 5189 5190 return self.func("TO_NUMBER", expression.this, fmt)
5197 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 5198 this = self.sql(expression, "this") 5199 kind = self.sql(expression, "kind") 5200 settings_sql = self.expressions(expression, key="settings", sep=" ") 5201 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 5202 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:
5226 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 5227 expressions = self.expressions(expression, flat=True) 5228 expressions = f" {self.wrap(expressions)}" if expressions else "" 5229 buckets = self.sql(expression, "buckets") 5230 kind = self.sql(expression, "kind") 5231 buckets = f" BUCKETS {buckets}" if buckets else "" 5232 order = self.sql(expression, "order") 5233 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
5238 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 5239 expressions = self.expressions(expression, key="expressions", flat=True) 5240 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 5241 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 5242 buckets = self.sql(expression, "buckets") 5243 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
5245 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 5246 this = self.sql(expression, "this") 5247 having = self.sql(expression, "having") 5248 5249 if having: 5250 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 5251 5252 return self.func("ANY_VALUE", this)
5254 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 5255 transform = self.func("TRANSFORM", *expression.expressions) 5256 row_format_before = self.sql(expression, "row_format_before") 5257 row_format_before = f" {row_format_before}" if row_format_before else "" 5258 record_writer = self.sql(expression, "record_writer") 5259 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 5260 using = f" USING {self.sql(expression, 'command_script')}" 5261 schema = self.sql(expression, "schema") 5262 schema = f" AS {schema}" if schema else "" 5263 row_format_after = self.sql(expression, "row_format_after") 5264 row_format_after = f" {row_format_after}" if row_format_after else "" 5265 record_reader = self.sql(expression, "record_reader") 5266 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 5267 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:
5269 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 5270 key_block_size = self.sql(expression, "key_block_size") 5271 if key_block_size: 5272 return f"KEY_BLOCK_SIZE = {key_block_size}" 5273 5274 using = self.sql(expression, "using") 5275 if using: 5276 return f"USING {using}" 5277 5278 parser = self.sql(expression, "parser") 5279 if parser: 5280 return f"WITH PARSER {parser}" 5281 5282 comment = self.sql(expression, "comment") 5283 if comment: 5284 return f"COMMENT {comment}" 5285 5286 visible = expression.args.get("visible") 5287 if visible is not None: 5288 return "VISIBLE" if visible else "INVISIBLE" 5289 5290 engine_attr = self.sql(expression, "engine_attr") 5291 if engine_attr: 5292 return f"ENGINE_ATTRIBUTE = {engine_attr}" 5293 5294 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 5295 if secondary_engine_attr: 5296 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 5297 5298 self.unsupported("Unsupported index constraint option.") 5299 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
5305 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 5306 kind = self.sql(expression, "kind") 5307 kind = f"{kind} INDEX" if kind else "INDEX" 5308 this = self.sql(expression, "this") 5309 this = f" {this}" if this else "" 5310 index_type = self.sql(expression, "index_type") 5311 index_type = f" USING {index_type}" if index_type else "" 5312 expressions = self.expressions(expression, flat=True) 5313 expressions = f" ({expressions})" if expressions else "" 5314 options = self.expressions(expression, key="options", sep=" ") 5315 options = f" {options}" if options else "" 5316 return f"{kind}{this}{index_type}{expressions}{options}"
5318 def nvl2_sql(self, expression: exp.Nvl2) -> str: 5319 if self.NVL2_SUPPORTED: 5320 return self.function_fallback_sql(expression) 5321 5322 case = exp.Case().when( 5323 expression.this.is_(exp.null()).not_(copy=False), 5324 expression.args["true"], 5325 copy=False, 5326 ) 5327 else_cond = expression.args.get("false") 5328 if else_cond: 5329 case.else_(else_cond, copy=False) 5330 5331 return self.sql(case)
5339 def comprehension_sql(self, expression: exp.Comprehension) -> str: 5340 this = self.sql(expression, "this") 5341 expr = self.sql(expression, "expression") 5342 position = self.sql(expression, "position") 5343 position = f", {position}" if position else "" 5344 iterator = self.sql(expression, "iterator") 5345 condition = self.sql(expression, "condition") 5346 condition = f" IF {condition}" if condition else "" 5347 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
5397 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5398 this_sql = self.sql(expression, "this") 5399 if isinstance(expression.this, exp.Table): 5400 this_sql = f"TABLE {this_sql}" 5401 5402 return self.func( 5403 "FORECAST", 5404 this_sql, 5405 expression.args.get("data_col"), 5406 expression.args.get("timestamp_col"), 5407 expression.args.get("model"), 5408 expression.args.get("id_cols"), 5409 expression.args.get("horizon"), 5410 expression.args.get("forecast_end_timestamp"), 5411 expression.args.get("confidence_level"), 5412 expression.args.get("output_historical_time_series"), 5413 expression.args.get("context_window"), 5414 )
5416 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5417 this_sql = self.sql(expression, "this") 5418 if isinstance(expression.this, exp.Table): 5419 this_sql = f"TABLE {this_sql}" 5420 5421 return self.func( 5422 "FEATURES_AT_TIME", 5423 this_sql, 5424 expression.args.get("time"), 5425 expression.args.get("num_rows"), 5426 expression.args.get("ignore_feature_nulls"), 5427 )
5429 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5430 this_sql = self.sql(expression, "this") 5431 if isinstance(expression.this, exp.Table): 5432 this_sql = f"TABLE {this_sql}" 5433 5434 query_table = self.sql(expression, "query_table") 5435 if isinstance(expression.args["query_table"], exp.Table): 5436 query_table = f"TABLE {query_table}" 5437 5438 return self.func( 5439 "VECTOR_SEARCH", 5440 this_sql, 5441 expression.args.get("column_to_search"), 5442 query_table, 5443 expression.args.get("query_column_to_search"), 5444 expression.args.get("top_k"), 5445 expression.args.get("distance_type"), 5446 expression.args.get("options"), 5447 )
@unsupported_args('partition_start', 'partition_end', 'force', 'mode')
def
refresh_sql(self, expression: sqlglot.expressions.core.Refresh) -> str:
5454 @unsupported_args("partition_start", "partition_end", "force", "mode") 5455 def refresh_sql(self, expression: exp.Refresh) -> str: 5456 this = self.sql(expression, "this") 5457 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5458 return f"REFRESH {kind}{this}"
5460 def toarray_sql(self, expression: exp.ToArray) -> str: 5461 arg = expression.this 5462 if not arg.type: 5463 import sqlglot.optimizer.annotate_types 5464 5465 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5466 5467 if arg.is_type(exp.DType.ARRAY): 5468 return self.sql(arg) 5469 5470 cond_for_null = arg.is_(exp.null()) 5471 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5473 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5474 this = expression.this 5475 time_format = self.format_time(expression) 5476 5477 if time_format: 5478 return self.sql( 5479 exp.cast( 5480 exp.StrToTime(this=this, format=expression.args["format"]), 5481 exp.DType.TIME, 5482 ) 5483 ) 5484 5485 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5486 return self.sql(this) 5487 5488 return self.sql(exp.cast(this, exp.DType.TIME))
5490 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5491 this = expression.this 5492 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5493 return self.sql(this) 5494 5495 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5497 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5498 this = expression.this 5499 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5500 return self.sql(this) 5501 5502 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5504 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5505 this = expression.this 5506 time_format = self.format_time(expression) 5507 safe = expression.args.get("safe") 5508 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5509 return self.sql( 5510 exp.cast( 5511 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5512 exp.DType.DATE, 5513 ) 5514 ) 5515 5516 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5517 return self.sql(this) 5518 5519 if safe: 5520 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5521 5522 return self.sql(exp.cast(this, exp.DType.DATE))
5534 def lastday_sql(self, expression: exp.LastDay) -> str: 5535 if self.LAST_DAY_SUPPORTS_DATE_PART: 5536 return self.function_fallback_sql(expression) 5537 5538 unit = expression.args.get("unit") 5539 if unit and unit.name.upper() != "MONTH": 5540 self.unsupported("Date parts are not supported in LAST_DAY.") 5541 5542 return self.func("LAST_DAY", expression.this)
def
arrayinsert_sql( self, expression: sqlglot.expressions.array.ArrayInsert, index_offset: int = 0) -> str:
5554 def arrayinsert_sql(self, expression: exp.ArrayInsert, index_offset: int = 0) -> str: 5555 this = expression.this 5556 position = expression.args["position"] 5557 offset = index_offset - (expression.args.get("offset") or 0) 5558 5559 if offset: 5560 if position.is_int: 5561 value = position.to_py() 5562 if value >= 0: 5563 position = exp.Literal.number(value + offset) 5564 elif offset < 0 and value == -1: 5565 # 1-based -1 appends, which a 0-based position can only express as the size 5566 position = exp.ArraySize(this=this.copy()) 5567 else: 5568 # Negative positions count from the end, so they shift in the opposite 5569 # direction, e.g. 0-based -1 (before the last element) is 1-based -2 5570 position = exp.Literal.number(value - offset) 5571 else: 5572 self.unsupported("ARRAY_INSERT position can only be converted if it's a literal") 5573 5574 return self.func("ARRAY_INSERT", this, position, expression.expression)
5576 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5577 if self.CAN_IMPLEMENT_ARRAY_ANY: 5578 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5579 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5580 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5581 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5582 5583 import sqlglot.dialects.dialect 5584 5585 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5586 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5587 self.unsupported("ARRAY_ANY is unsupported") 5588 5589 return self.function_fallback_sql(expression)
5591 def struct_sql(self, expression: exp.Struct) -> str: 5592 expression.set( 5593 "expressions", 5594 [ 5595 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5596 if isinstance(e, exp.PropertyEQ) 5597 else e 5598 for e in expression.expressions 5599 ], 5600 ) 5601 5602 return self.function_fallback_sql(expression)
5610 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5611 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5612 tables = f" {self.expressions(expression)}" 5613 5614 exists = " IF EXISTS" if expression.args.get("exists") else "" 5615 5616 on_cluster = self.sql(expression, "cluster") 5617 on_cluster = f" {on_cluster}" if on_cluster else "" 5618 5619 identity = self.sql(expression, "identity") 5620 identity = f" {identity} IDENTITY" if identity else "" 5621 5622 option = self.sql(expression, "option") 5623 option = f" {option}" if option else "" 5624 5625 partition = self.sql(expression, "partition") 5626 partition = f" {partition}" if partition else "" 5627 5628 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5632 def convert_sql(self, expression: exp.Convert) -> str: 5633 to = expression.this 5634 value = expression.expression 5635 style = expression.args.get("style") 5636 safe = expression.args.get("safe") 5637 strict = expression.args.get("strict") 5638 5639 if not to or not value: 5640 return "" 5641 5642 # Retrieve length of datatype and override to default if not specified 5643 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5644 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5645 5646 transformed: exp.Expr | None = None 5647 cast = exp.Cast if strict else exp.TryCast 5648 5649 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5650 if isinstance(style, exp.Literal) and style.is_int: 5651 import sqlglot.dialects.tsql 5652 5653 style_value = style.name 5654 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5655 if not converted_style: 5656 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5657 5658 fmt = exp.Literal.string(converted_style) 5659 5660 if to.this == exp.DType.DATE: 5661 transformed = exp.StrToDate(this=value, format=fmt) 5662 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5663 transformed = exp.StrToTime(this=value, format=fmt) 5664 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5665 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5666 elif to.this == exp.DType.TEXT: 5667 transformed = exp.TimeToStr(this=value, format=fmt) 5668 5669 if not transformed: 5670 transformed = cast(this=value, to=to, safe=safe) 5671 5672 return self.sql(transformed)
5749 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5750 option = self.sql(expression, "this") 5751 5752 if expression.expressions: 5753 upper = option.upper() 5754 5755 # Snowflake FILE_FORMAT options are separated by whitespace 5756 sep = " " if upper == "FILE_FORMAT" else ", " 5757 5758 # Databricks copy/format options do not set their list of values with EQ 5759 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5760 values = self.expressions(expression, flat=True, sep=sep) 5761 return f"{option}{op}({values})" 5762 5763 value = self.sql(expression, "expression") 5764 5765 if not value: 5766 return option 5767 5768 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5769 5770 return f"{option}{op}{value}"
5772 def credentials_sql(self, expression: exp.Credentials) -> str: 5773 cred_expr = expression.args.get("credentials") 5774 if isinstance(cred_expr, exp.Literal): 5775 # Redshift case: CREDENTIALS <string> 5776 credentials = self.sql(expression, "credentials") 5777 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5778 else: 5779 # Snowflake case: CREDENTIALS = (...) 5780 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5781 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5782 5783 storage = self.sql(expression, "storage") 5784 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5785 5786 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5787 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5788 5789 iam_role = self.sql(expression, "iam_role") 5790 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5791 5792 region = self.sql(expression, "region") 5793 region = f" REGION {region}" if region else "" 5794 5795 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5797 def copy_sql(self, expression: exp.Copy) -> str: 5798 this = self.sql(expression, "this") 5799 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5800 5801 credentials = self.sql(expression, "credentials") 5802 credentials = self.seg(credentials) if credentials else "" 5803 files = self.expressions(expression, key="files", flat=True) 5804 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5805 5806 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5807 params = self.expressions( 5808 expression, 5809 key="params", 5810 sep=sep, 5811 new_line=True, 5812 skip_last=True, 5813 skip_first=True, 5814 indent=self.COPY_PARAMS_ARE_WRAPPED, 5815 ) 5816 5817 if params: 5818 if self.COPY_PARAMS_ARE_WRAPPED: 5819 params = f" WITH ({params})" 5820 elif not self.pretty and (files or credentials): 5821 params = f" {params}" 5822 5823 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5828 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5829 on_sql = "ON" if expression.args.get("on") else "OFF" 5830 filter_col: str | None = self.sql(expression, "filter_column") 5831 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5832 retention_period: str | None = self.sql(expression, "retention_period") 5833 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5834 5835 if filter_col or retention_period: 5836 on_sql = self.func("ON", filter_col, retention_period) 5837 5838 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5840 def maskingpolicycolumnconstraint_sql( 5841 self, expression: exp.MaskingPolicyColumnConstraint 5842 ) -> str: 5843 this = self.sql(expression, "this") 5844 expressions = self.expressions(expression, flat=True) 5845 expressions = f" USING ({expressions})" if expressions else "" 5846 return f"MASKING POLICY {this}{expressions}"
5856 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5857 this = self.sql(expression, "this") 5858 expr = expression.expression 5859 5860 if isinstance(expr, exp.Func): 5861 # T-SQL's CLR functions are case sensitive 5862 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5863 else: 5864 expr = self.sql(expression, "expression") 5865 5866 return self.scope_resolution(expr, this)
5874 def rand_sql(self, expression: exp.Rand) -> str: 5875 lower = self.sql(expression, "lower") 5876 upper = self.sql(expression, "upper") 5877 5878 if lower and upper: 5879 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5880 return self.func("RAND", expression.this)
5882 def changes_sql(self, expression: exp.Changes) -> str: 5883 information = self.sql(expression, "information") 5884 information = f"INFORMATION => {information}" 5885 at_before = self.sql(expression, "at_before") 5886 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5887 end = self.sql(expression, "end") 5888 end = f"{self.seg('')}{end}" if end else "" 5889 5890 return f"CHANGES ({information}){at_before}{end}"
5892 def pad_sql(self, expression: exp.Pad) -> str: 5893 prefix = "L" if expression.args.get("is_left") else "R" 5894 5895 fill_pattern = self.sql(expression, "fill_pattern") or None 5896 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5897 fill_pattern = "' '" 5898 5899 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5905 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5906 generate_series = exp.GenerateSeries(**expression.args) 5907 5908 parent = expression.parent 5909 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5910 parent = parent.parent 5911 5912 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5913 return self.sql(exp.Unnest(expressions=[generate_series])) 5914 5915 if isinstance(parent, exp.Select): 5916 self.unsupported("GenerateSeries projection unnesting is not supported.") 5917 5918 return self.sql(generate_series)
5920 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5921 if self.SUPPORTS_CONVERT_TIMEZONE: 5922 return self.function_fallback_sql(expression) 5923 5924 source_tz = expression.args.get("source_tz") 5925 target_tz = expression.args.get("target_tz") 5926 timestamp = expression.args.get("timestamp") 5927 5928 if source_tz and timestamp: 5929 timestamp = exp.AtTimeZone( 5930 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5931 ) 5932 5933 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5934 5935 return self.sql(expr)
5937 def json_sql(self, expression: exp.JSON) -> str: 5938 this = self.sql(expression, "this") 5939 this = f" {this}" if this else "" 5940 5941 _with = expression.args.get("with_") 5942 5943 if _with is None: 5944 with_sql = "" 5945 elif not _with: 5946 with_sql = " WITHOUT" 5947 else: 5948 with_sql = " WITH" 5949 5950 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5951 5952 return f"JSON{this}{with_sql}{unique_sql}"
5954 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5955 path = self.sql(expression, "path") 5956 returning = self.sql(expression, "returning") 5957 returning = f" RETURNING {returning}" if returning else "" 5958 5959 on_condition = self.sql(expression, "on_condition") 5960 on_condition = f" {on_condition}" if on_condition else "" 5961 5962 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5968 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5969 else_ = "ELSE " if expression.args.get("else_") else "" 5970 condition = self.sql(expression, "expression") 5971 condition = f"WHEN {condition} THEN " if condition else else_ 5972 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5973 return f"{condition}{insert}"
5975 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5976 kind = self.sql(expression, "kind") 5977 source = self.sql(expression, "source") 5978 5979 if kind: 5980 expressions = self.seg(self.expressions(expression, sep=" ")) 5981 sql = f"INSERT {kind}{expressions}{self.seg(source)}" 5982 else: 5983 # If kind isn't present, we have Hive's FROM-first form: 5984 # FROM x INSERT ... SELECT ... [INSERT ... SELECT ...] 5985 inserts = self.sep().join(self.sql(e) for e in expression.expressions) 5986 sql = f"FROM {source}{self.seg(inserts)}" 5987 5988 return self.prepend_ctes(expression, sql)
5990 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5991 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5992 empty = expression.args.get("empty") 5993 empty = ( 5994 f"DEFAULT {empty} ON EMPTY" 5995 if isinstance(empty, exp.Expr) 5996 else self.sql(expression, "empty") 5997 ) 5998 5999 error = expression.args.get("error") 6000 error = ( 6001 f"DEFAULT {error} ON ERROR" 6002 if isinstance(error, exp.Expr) 6003 else self.sql(expression, "error") 6004 ) 6005 6006 if error and empty: 6007 error = ( 6008 f"{empty} {error}" 6009 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 6010 else f"{error} {empty}" 6011 ) 6012 empty = "" 6013 6014 null = self.sql(expression, "null") 6015 6016 return f"{empty}{error}{null}"
6022 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 6023 this = self.sql(expression, "this") 6024 path = self.sql(expression, "path") 6025 6026 passing = self.expressions(expression, "passing") 6027 passing = f" PASSING {passing}" if passing else "" 6028 6029 on_condition = self.sql(expression, "on_condition") 6030 on_condition = f" {on_condition}" if on_condition else "" 6031 6032 path = f"{path}{passing}{on_condition}" 6033 6034 return self.func("JSON_EXISTS", this, path)
6084 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 6085 array_agg = self.function_fallback_sql(expression) 6086 column_expr = expression.this 6087 if isinstance(column_expr, exp.Order): 6088 column_expr = column_expr.this 6089 6090 return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
6092 def timeslice_sql(self, expression: exp.TimeSlice) -> str: 6093 # Only the SQLGlot dialect serializes the internal origin for round-tripping. 6094 origin = expression.args.get("origin") if self.dialect == "" else None 6095 kind = expression.args.get("kind") 6096 if origin is not None and kind is None: 6097 kind = exp.Literal.string("START") 6098 6099 return self.func( 6100 "TIME_SLICE", expression.this, expression.expression, expression.unit, kind, origin 6101 )
6103 def slice_sql(self, expression: exp.Slice) -> str: 6104 if "[" in self.dialect.tokenizer_class._IDENTIFIERS: 6105 self.unsupported("Slices are not supported") 6106 6107 step = self.sql(expression, "step") 6108 # An omitted end is written as `-` before a step, since `::` is the cast operator 6109 end = self.sql(expression.expression) or ("-" if step else "") 6110 begin = self.sql(expression.this) 6111 6112 sql = f"{end}:{step}" if step else end 6113 return f"{begin}:{sql}" if sql else f"{begin}:"
6186 def overlay_sql(self, expression: exp.Overlay) -> str: 6187 this = self.sql(expression, "this") 6188 expr = self.sql(expression, "expression") 6189 from_sql = self.sql(expression, "from_") 6190 for_sql = self.sql(expression, "for_") 6191 for_sql = f" FOR {for_sql}" if for_sql else "" 6192 6193 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
6200 def string_sql(self, expression: exp.String) -> str: 6201 this = expression.this 6202 zone = expression.args.get("zone") 6203 6204 if zone: 6205 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 6206 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 6207 # set for source_tz to transpile the time conversion before the STRING cast 6208 this = exp.ConvertTimezone( 6209 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 6210 ) 6211 6212 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
6222 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 6223 filler = self.sql(expression, "this") 6224 filler = f" {filler}" if filler else "" 6225 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 6226 return f"TRUNCATE{filler} {with_count}"
6228 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 6229 if self.SUPPORTS_UNIX_SECONDS: 6230 return self.function_fallback_sql(expression) 6231 6232 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 6233 6234 return self.sql( 6235 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 6236 )
6238 def arraysort_sql(self, expression: exp.ArraySort) -> str: 6239 asc = expression.args.get("asc") 6240 nulls_first = expression.args.get("nulls_first") 6241 if (asc and asc != exp.true()) or (nulls_first and nulls_first != exp.false()): 6242 self.unsupported("ARRAY_SORT only sorts ascending with NULLs last") 6243 return self.func("ARRAY_SORT", expression.this, expression.expression)
6245 def arraysize_sql(self, expression: exp.ArraySize) -> str: 6246 dim = expression.expression 6247 6248 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 6249 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 6250 if not (dim.is_int and dim.name == "1"): 6251 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 6252 dim = None 6253 6254 # If dimension is required but not specified, default initialize it 6255 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 6256 dim = exp.Literal.number(1) 6257 6258 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
6260 def attach_sql(self, expression: exp.Attach) -> str: 6261 this = self.sql(expression, "this") 6262 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 6263 expressions = self.expressions(expression) 6264 expressions = f" ({expressions})" if expressions else "" 6265 6266 return f"ATTACH{exists_sql} {this}{expressions}"
6268 def detach_sql(self, expression: exp.Detach) -> str: 6269 kind = self.sql(expression, "kind") 6270 kind = f" {kind}" if kind else "" 6271 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 6272 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 6273 exists = " IF EXISTS" if expression.args.get("exists") else "" 6274 if exists: 6275 kind = kind or " DATABASE" 6276 6277 this = self.sql(expression, "this") 6278 this = f" {this}" if this else "" 6279 cluster = self.sql(expression, "cluster") 6280 cluster = f" {cluster}" if cluster else "" 6281 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 6282 sync = " SYNC" if expression.args.get("sync") else "" 6283 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
6296 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 6297 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 6298 encode = f"{encode} {self.sql(expression, 'this')}" 6299 6300 properties = expression.args.get("properties") 6301 if properties: 6302 encode = f"{encode} {self.properties(properties)}" 6303 6304 return encode
6306 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 6307 this = self.sql(expression, "this") 6308 include = f"INCLUDE {this}" 6309 6310 column_def = self.sql(expression, "column_def") 6311 if column_def: 6312 include = f"{include} {column_def}" 6313 6314 alias = self.sql(expression, "alias") 6315 if alias: 6316 include = f"{include} AS {alias}" 6317 6318 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
6331 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 6332 partitions = self.expressions(expression, "partition_expressions") 6333 create = self.expressions(expression, "create_expressions") 6334 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
6336 def partitionbyrangepropertydynamic_sql( 6337 self, expression: exp.PartitionByRangePropertyDynamic 6338 ) -> str: 6339 start = self.sql(expression, "start") 6340 end = self.sql(expression, "end") 6341 6342 every = expression.args["every"] 6343 if isinstance(every, exp.Interval) and every.this.is_string: 6344 every.this.replace(exp.Literal.number(every.name)) 6345 6346 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
6359 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 6360 kind = self.sql(expression, "kind") 6361 option = self.sql(expression, "option") 6362 option = f" {option}" if option else "" 6363 this = self.sql(expression, "this") 6364 this = f" {this}" if this else "" 6365 columns = self.expressions(expression) 6366 columns = f" {columns}" if columns else "" 6367 return f"{kind}{option} STATISTICS{this}{columns}"
6369 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 6370 this = self.sql(expression, "this") 6371 columns = self.expressions(expression) 6372 inner_expression = self.sql(expression, "expression") 6373 inner_expression = f" {inner_expression}" if inner_expression else "" 6374 update_options = self.sql(expression, "update_options") 6375 update_options = f" {update_options} UPDATE" if update_options else "" 6376 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
6387 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 6388 kind = self.sql(expression, "kind") 6389 this = self.sql(expression, "this") 6390 this = f" {this}" if this else "" 6391 inner_expression = self.sql(expression, "expression") 6392 return f"VALIDATE {kind}{this}{inner_expression}"
6394 def analyze_sql(self, expression: exp.Analyze) -> str: 6395 options = self.expressions(expression, key="options", sep=" ") 6396 options = f" {options}" if options else "" 6397 kind = self.sql(expression, "kind") 6398 kind = f" {kind}" if kind else "" 6399 tables = self.expressions(expression, key="tables", flat=True) 6400 tables = f" {tables}" if tables else "" 6401 mode = self.sql(expression, "mode") 6402 mode = f" {mode}" if mode else "" 6403 properties = self.sql(expression, "properties") 6404 properties = f" {properties}" if properties else "" 6405 partition = self.sql(expression, "partition") 6406 partition = f" {partition}" if partition else "" 6407 inner_expression = self.sql(expression, "expression") 6408 inner_expression = f" {inner_expression}" if inner_expression else "" 6409 return f"ANALYZE{options}{kind}{tables}{partition}{mode}{inner_expression}{properties}"
6411 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6412 this = self.sql(expression, "this") 6413 namespaces = self.expressions(expression, key="namespaces") 6414 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6415 passing = self.expressions(expression, key="passing") 6416 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6417 columns = self.expressions(expression, key="columns") 6418 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6419 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6420 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6426 def export_sql(self, expression: exp.Export) -> str: 6427 this = self.sql(expression, "this") 6428 connection = self.sql(expression, "connection") 6429 connection = f"WITH CONNECTION {connection} " if connection else "" 6430 options = self.sql(expression, "options") 6431 return f"EXPORT DATA {connection}{options} AS {this}"
6437 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6438 variables = self.expressions(expression, "this") 6439 default = self.sql(expression, "default") 6440 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6441 6442 kind = self.sql(expression, "kind") 6443 if isinstance(expression.args.get("kind"), exp.Schema): 6444 kind = f"TABLE {kind}" 6445 6446 kind = f" {kind}" if kind else "" 6447 6448 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
6450 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6451 kind = self.sql(expression, "kind") 6452 this = self.sql(expression, "this") 6453 set = self.sql(expression, "expression") 6454 to = self.sql(expression, "to") 6455 to = f" TO {to}" if to else "" 6456 default = self.sql(expression, "default") 6457 default = f" DEFAULT {default}" if default else "" 6458 using = self.sql(expression, "using") 6459 using = f" USING {using}" if using else "" 6460 6461 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6462 6463 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:
6491 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6492 # Snowflake GET/PUT statements: 6493 # PUT <file> <internalStage> <properties> 6494 # GET <internalStage> <file> <properties> 6495 props = expression.args.get("properties") 6496 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6497 this = self.sql(expression, "this") 6498 target = self.sql(expression, "target") 6499 6500 if isinstance(expression, exp.Put): 6501 return f"PUT {this} {target}{props_sql}" 6502 else: 6503 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
6505 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6506 this = self.sql(expression, "this") 6507 expr = self.sql(expression, "expression") 6508 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6509 return f"TRANSLATE({this} USING {expr}{with_error})"
6511 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6512 if self.SUPPORTS_DECODE_CASE: 6513 return self.func("DECODE", *expression.expressions) 6514 6515 decode_expr, *expressions = expression.expressions 6516 6517 ifs = [] 6518 for search, result in zip(expressions[::2], expressions[1::2]): 6519 if isinstance(search, exp.Literal): 6520 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6521 elif isinstance(search, exp.Null): 6522 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6523 else: 6524 if isinstance(search, exp.Binary): 6525 search = exp.paren(search) 6526 6527 cond = exp.or_( 6528 decode_expr.eq(search), 6529 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6530 copy=False, 6531 ) 6532 ifs.append(exp.If(this=cond, true=result)) 6533 6534 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6535 return self.sql(case)
6537 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6538 this = self.sql(expression, "this") 6539 this = self.seg(this, sep="") 6540 dimensions = self.expressions( 6541 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6542 ) 6543 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6544 metrics = self.expressions( 6545 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6546 ) 6547 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6548 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6549 facts = self.seg(f"FACTS {facts}") if facts else "" 6550 where = self.sql(expression, "where") 6551 where = self.seg(f"WHERE {where}") if where else "" 6552 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6553 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6555 def getextract_sql(self, expression: exp.GetExtract) -> str: 6556 this = expression.this 6557 expr = expression.expression 6558 6559 if not this.type or not expression.type: 6560 import sqlglot.optimizer.annotate_types 6561 6562 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6563 6564 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6565 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6566 6567 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6584 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6585 method = self.sql(expression, "method") 6586 kind = expression.args.get("kind") 6587 if not kind: 6588 return f"REFRESH {method}" 6589 6590 every = self.sql(expression, "every") 6591 unit = self.sql(expression, "unit") 6592 every = f" EVERY {every} {unit}" if every else "" 6593 starts = self.sql(expression, "starts") 6594 starts = f" STARTS {starts}" if starts else "" 6595 6596 return f"REFRESH {method} ON {kind}{every}{starts}"
6605 def uuid_sql(self, expression: exp.Uuid) -> str: 6606 is_string = expression.args.get("is_string", False) 6607 uuid_func_sql = self.func("UUID") 6608 6609 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6610 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6611 6612 return uuid_func_sql
6614 def initcap_sql(self, expression: exp.Initcap) -> str: 6615 delimiters = expression.expression 6616 6617 if delimiters: 6618 # do not generate delimiters arg if we are round-tripping from default delimiters 6619 if ( 6620 delimiters.is_string 6621 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6622 ): 6623 delimiters = None 6624 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6625 self.unsupported("INITCAP does not support custom delimiters") 6626 delimiters = None 6627 6628 return self.func("INITCAP", expression.this, delimiters)
6638 def weekstart_name(self, expression: exp.WeekStart) -> str: 6639 import sqlglot.dialects.dialect 6640 6641 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6642 this = expression.this.name.upper() 6643 6644 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6645 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6646 6647 if dow_from_week_start_day != dow_from_week_offset: 6648 self.unsupported( 6649 f"WEEK({this}) is not supported; falling back to the default week start day" 6650 ) 6651 6652 return "WEEK"
6654 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6655 name = self.weekstart_name(expression) 6656 6657 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6658 if isinstance(expression.parent, exp.DateTrunc): 6659 return self.sql(exp.Literal.string(name)) 6660 6661 return name
def
functionspecification_sql(self, expression: sqlglot.expressions.query.FunctionSpecification) -> str:
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: