sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from functools import reduce, wraps 8 9from sqlglot import exp 10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 11from sqlglot.expressions import apply_index_offset 12from sqlglot.expressions.core import maybe_parse 13from sqlglot.helper import csv, name_sequence, seq_get 14from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 15from sqlglot.time import format_time 16from sqlglot.tokens import TokenType 17 18if t.TYPE_CHECKING: 19 from sqlglot._typing import E 20 from sqlglot.dialects.dialect import DialectType 21 22 G = t.TypeVar("G", bound="Generator") 23 GeneratorMethod = t.Callable[[G, E], str] 24 25logger = logging.getLogger("sqlglot") 26 27ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 28UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 29 30 31def unsupported_args( 32 *args: str | tuple[str, str], 33) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 34 """ 35 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 36 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 37 """ 38 diagnostic_by_arg: dict[str, str | None] = {} 39 for arg in args: 40 if isinstance(arg, str): 41 diagnostic_by_arg[arg] = None 42 else: 43 diagnostic_by_arg[arg[0]] = arg[1] 44 45 def decorator(func: GeneratorMethod) -> GeneratorMethod: 46 @wraps(func) 47 def _func(generator: G, expression: E) -> str: 48 expression_name = expression.__class__.__name__ 49 dialect_name = generator.dialect.__class__.__name__ 50 51 for arg_name, diagnostic in diagnostic_by_arg.items(): 52 if expression.args.get(arg_name): 53 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 54 arg_name, expression_name, dialect_name 55 ) 56 generator.unsupported(diagnostic) 57 58 return func(generator, expression) 59 60 return _func 61 62 return decorator 63 64 65AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = { 66 "windows": lambda self, e: ( 67 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 68 if e.args.get("windows") 69 else "" 70 ), 71 "qualify": lambda self, e: self.sql(e, "qualify"), 72} 73 74 75_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {} 76 77 78def _build_dispatch( 79 cls: type[Generator], 80) -> dict[type[exp.Expr], t.Callable[..., str]]: 81 dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS) 82 83 for attr_name in dir(cls): 84 if not attr_name.endswith("_sql") or attr_name.startswith("_"): 85 continue 86 87 expr_key = attr_name[:-4] 88 expr_cls = exp.EXPR_CLASSES.get(expr_key) 89 90 if expr_cls and expr_cls not in dispatch: 91 dispatch[expr_cls] = getattr(cls, attr_name) 92 93 return dispatch 94 95 96class Generator: 97 """ 98 Generator converts a given syntax tree to the corresponding SQL string. 99 100 Args: 101 pretty: Whether to format the produced SQL string. 102 Default: False. 103 identify: Determines when an identifier should be quoted. Possible values are: 104 False (default): Never quote, except in cases where it's mandatory by the dialect. 105 True: Always quote except for specials cases. 106 'safe': Only quote identifiers that are case insensitive. 107 normalize: Whether to normalize identifiers to lowercase. 108 Default: False. 109 pad: The pad size in a formatted string. For example, this affects the indentation of 110 a projection in a query, relative to its nesting level. 111 Default: 2. 112 indent: The indentation size in a formatted string. For example, this affects the 113 indentation of subqueries and filters under a `WHERE` clause. 114 Default: 2. 115 normalize_functions: How to normalize function names. Possible values are: 116 "upper" or True (default): Convert names to uppercase. 117 "lower": Convert names to lowercase. 118 False: Disables function name normalization. 119 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 120 Default ErrorLevel.WARN. 121 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 122 This is only relevant if unsupported_level is ErrorLevel.RAISE. 123 Default: 3 124 leading_comma: Whether the comma is leading or trailing in select expressions. 125 This is only relevant when generating in pretty mode. 126 Default: False 127 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 128 The default is on the smaller end because the length only represents a segment and not the true 129 line length. 130 Default: 80 131 comments: Whether to preserve comments in the output SQL code. 132 Default: True 133 """ 134 135 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 136 **JSON_PATH_PART_TRANSFORMS, 137 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 138 exp.AllowedValuesProperty: lambda self, e: ( 139 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 140 ), 141 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 142 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 143 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 144 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 145 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 146 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 147 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 148 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 149 exp.CaseSpecificColumnConstraint: lambda _, e: ( 150 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 151 ), 152 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 153 exp.Ceil: lambda self, e: self.ceil_floor(e), 154 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 155 exp.CharacterSetProperty: lambda self, e: ( 156 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 157 ), 158 exp.ClusteredColumnConstraint: lambda self, e: ( 159 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 160 ), 161 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 162 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 163 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 164 exp.ConvertToCharset: lambda self, e: self.func( 165 "CONVERT", e.this, e.args["dest"], e.args.get("source") 166 ), 167 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 168 exp.CredentialsProperty: lambda self, e: ( 169 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 170 ), 171 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 172 exp.SessionUser: lambda *_: "SESSION_USER", 173 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 174 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 175 exp.ApiProperty: lambda *_: "API", 176 exp.ApplicationProperty: lambda *_: "APPLICATION", 177 exp.CatalogProperty: lambda *_: "CATALOG", 178 exp.ComputeProperty: lambda *_: "COMPUTE", 179 exp.DatabaseProperty: lambda *_: "DATABASE", 180 exp.DynamicProperty: lambda *_: "DYNAMIC", 181 exp.EmptyProperty: lambda *_: "EMPTY", 182 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 183 exp.EndStatement: lambda *_: "END", 184 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 185 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 186 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 187 exp.EphemeralColumnConstraint: lambda self, e: ( 188 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 189 ), 190 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 191 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 192 exp.Except: lambda self, e: self.set_operations(e), 193 exp.ExternalProperty: lambda *_: "EXTERNAL", 194 exp.Floor: lambda self, e: self.ceil_floor(e), 195 exp.Get: lambda self, e: self.get_put_sql(e), 196 exp.GlobalProperty: lambda *_: "GLOBAL", 197 exp.HeapProperty: lambda *_: "HEAP", 198 exp.HybridProperty: lambda *_: "HYBRID", 199 exp.IcebergProperty: lambda *_: "ICEBERG", 200 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 201 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 202 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 203 exp.Intersect: lambda self, e: self.set_operations(e), 204 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 205 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 206 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 207 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 208 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 209 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 210 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 211 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 212 exp.LanguageProperty: lambda self, e: self.naked_property(e), 213 exp.LocationProperty: lambda self, e: self.naked_property(e), 214 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 215 exp.MaskingProperty: lambda *_: "MASKING", 216 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 217 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 218 exp.NetworkProperty: lambda *_: "NETWORK", 219 exp.NonClusteredColumnConstraint: lambda self, e: ( 220 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 221 ), 222 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 223 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 224 exp.OnCommitProperty: lambda _, e: ( 225 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 226 ), 227 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 228 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 229 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 230 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 231 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 232 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 233 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 234 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 235 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 236 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 237 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 238 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 239 f"PROJECTION POLICY {self.sql(e, 'this')}" 240 ), 241 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 242 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 243 exp.Put: lambda self, e: self.get_put_sql(e), 244 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 245 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 246 ), 247 exp.ReturnsProperty: lambda self, e: ( 248 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 249 ), 250 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 251 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 252 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 253 exp.SecureProperty: lambda *_: "SECURE", 254 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 255 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 256 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 257 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 258 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 259 exp.SqlReadWriteProperty: lambda _, e: e.name, 260 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 261 exp.StabilityProperty: lambda _, e: e.name, 262 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 263 exp.StreamingTableProperty: lambda *_: "STREAMING", 264 exp.StrictProperty: lambda *_: "STRICT", 265 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 266 exp.TableColumn: lambda self, e: self.sql(e.this), 267 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 268 exp.TemporaryProperty: lambda *_: "TEMPORARY", 269 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 270 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 271 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 272 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 273 exp.TransientProperty: lambda *_: "TRANSIENT", 274 exp.VirtualProperty: lambda *_: "VIRTUAL", 275 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 276 exp.Union: lambda self, e: self.set_operations(e), 277 exp.UnloggedProperty: lambda *_: "UNLOGGED", 278 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 279 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 280 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 281 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 282 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 283 exp.UtcTimestamp: lambda self, e: self.sql( 284 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 285 ), 286 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 287 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 288 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 289 exp.VolatileProperty: lambda *_: "VOLATILE", 290 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 291 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 292 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 293 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 294 exp.ForceProperty: lambda *_: "FORCE", 295 } 296 297 # Whether null ordering is supported in order by 298 # True: Full Support, None: No support, False: No support for certain cases 299 # such as window specifications, aggregate functions etc 300 NULL_ORDERING_SUPPORTED: bool | None = True 301 302 # Window functions that support NULLS FIRST/LAST 303 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 304 305 # Whether ignore nulls is inside the agg or outside. 306 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 307 IGNORE_NULLS_IN_FUNC = False 308 309 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 310 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 311 IGNORE_NULLS_BEFORE_ORDER = True 312 313 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 314 LOCKING_READS_SUPPORTED = False 315 316 # Whether the EXCEPT and INTERSECT operations can return duplicates 317 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 318 319 # Wrap derived values in parens, usually standard but spark doesn't support it 320 WRAP_DERIVED_VALUES = True 321 322 # Whether create function uses an AS before the RETURN 323 CREATE_FUNCTION_RETURN_AS = True 324 325 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 326 MATCHED_BY_SOURCE = True 327 328 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 329 SUPPORTS_MERGE_WHERE = False 330 331 # Whether the INTERVAL expression works only with values like '1 day' 332 SINGLE_STRING_INTERVAL = False 333 334 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 335 INTERVAL_ALLOWS_PLURAL_FORM = True 336 337 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 338 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 339 AUTO_REFRESH_BARE_INTERVALS = False 340 341 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 342 LIMIT_FETCH = "ALL" 343 344 # Whether limit and fetch allows expresions or just limits 345 LIMIT_ONLY_LITERALS = False 346 347 # Whether a table is allowed to be renamed with a db 348 RENAME_TABLE_WITH_DB = True 349 350 # The separator for grouping sets and rollups 351 GROUPINGS_SEP = "," 352 353 # The string used for creating an index on a table 354 INDEX_ON = "ON" 355 356 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 357 INOUT_SEPARATOR = " " 358 359 # Whether join hints should be generated 360 JOIN_HINTS = True 361 362 # Whether directed joins are supported 363 DIRECTED_JOINS = False 364 365 # Whether table hints should be generated 366 TABLE_HINTS = True 367 368 # Whether query hints should be generated 369 QUERY_HINTS = True 370 371 # What kind of separator to use for query hints 372 QUERY_HINT_SEP = ", " 373 374 # Whether comparing against booleans (e.g. x IS TRUE) is supported 375 IS_BOOL_ALLOWED = True 376 377 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 378 DUPLICATE_KEY_UPDATE_WITH_SET = True 379 380 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 381 LIMIT_IS_TOP = False 382 383 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 384 RETURNING_END = True 385 386 # Whether to generate an unquoted value for EXTRACT's date part argument 387 EXTRACT_ALLOWS_QUOTES = True 388 389 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 390 TZ_TO_WITH_TIME_ZONE = False 391 392 # Whether the NVL2 function is supported 393 NVL2_SUPPORTED = True 394 395 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 396 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 397 398 # Whether VALUES statements can be used as derived tables. 399 # MySQL 5 and Redshift do not allow this, so when False, it will convert 400 # SELECT * VALUES into SELECT UNION 401 VALUES_AS_TABLE = True 402 403 # Whether the word COLUMN is included when adding a column with ALTER TABLE 404 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 405 406 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 407 UNNEST_WITH_ORDINALITY = True 408 409 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 410 SEMI_ANTI_JOIN_WITH_SIDE = True 411 412 # Whether to include the type of a computed column in the CREATE DDL 413 COMPUTED_COLUMN_WITH_TYPE = True 414 415 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 416 SUPPORTS_TABLE_COPY = True 417 418 # Whether parentheses are required around the table sample's expression 419 TABLESAMPLE_REQUIRES_PARENS = True 420 421 # Whether a table sample clause's size needs to be followed by the ROWS keyword 422 TABLESAMPLE_SIZE_IS_ROWS = True 423 424 # The keyword(s) to use when generating a sample clause 425 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 426 427 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 428 TABLESAMPLE_WITH_METHOD = True 429 430 # The keyword to use when specifying the seed of a sample clause 431 TABLESAMPLE_SEED_KEYWORD = "SEED" 432 433 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 434 HISTORICAL_DATA_POST_ALIAS = False 435 436 # Whether COLLATE is a function instead of a binary operator 437 COLLATE_IS_FUNC = False 438 439 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 440 DATA_TYPE_SPECIFIERS_ALLOWED = False 441 442 # Whether conditions require booleans WHERE x = 0 vs WHERE x 443 ENSURE_BOOLS = False 444 445 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 446 CTE_RECURSIVE_KEYWORD_REQUIRED = True 447 448 # Whether CONCAT requires >1 arguments 449 SUPPORTS_SINGLE_ARG_CONCAT = True 450 451 # Whether LAST_DAY function supports a date part argument 452 LAST_DAY_SUPPORTS_DATE_PART = True 453 454 # Whether named columns are allowed in table aliases 455 SUPPORTS_TABLE_ALIAS_COLUMNS = True 456 457 # Whether named columns are allowed in CTE definitions 458 SUPPORTS_NAMED_CTE_COLUMNS = True 459 460 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 461 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 462 463 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 464 PIVOT_ALIAS_WITH_AS = True 465 466 # What delimiter to use for separating JSON key/value pairs 467 JSON_KEY_VALUE_PAIR_SEP = ":" 468 469 # INSERT OVERWRITE TABLE x override 470 INSERT_OVERWRITE = " OVERWRITE TABLE" 471 472 # Whether the SELECT .. INTO syntax is used instead of CTAS 473 SUPPORTS_SELECT_INTO = False 474 475 # Whether UNLOGGED tables can be created 476 SUPPORTS_UNLOGGED_TABLES = False 477 478 # Whether the CREATE TABLE LIKE statement is supported 479 SUPPORTS_CREATE_TABLE_LIKE = True 480 481 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 482 SUPPORTS_MODIFY_COLUMN = False 483 484 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 485 SUPPORTS_CHANGE_COLUMN = False 486 487 # Whether the LikeProperty needs to be specified inside of the schema clause 488 LIKE_PROPERTY_INSIDE_SCHEMA = False 489 490 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 491 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 492 MULTI_ARG_DISTINCT = True 493 494 # Whether the JSON extraction operators expect a value of type JSON 495 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 496 497 # Whether bracketed keys like ["foo"] are supported in JSON paths 498 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 499 500 # Whether to escape keys using single quotes in JSON paths 501 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 502 503 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 504 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 505 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 506 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 507 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 508 509 # The JSONPathPart expressions supported by this dialect 510 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 511 512 # Whether any(f(x) for x in array) can be implemented by this dialect 513 CAN_IMPLEMENT_ARRAY_ANY = False 514 515 # Whether the function TO_NUMBER is supported 516 SUPPORTS_TO_NUMBER = True 517 518 # Whether EXCLUDE in window specification is supported 519 SUPPORTS_WINDOW_EXCLUDE = False 520 521 # Whether or not set op modifiers apply to the outer set op or select. 522 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 523 # True means limit 1 happens after the set op, False means it it happens on y. 524 SET_OP_MODIFIERS = True 525 526 # Whether parameters from COPY statement are wrapped in parentheses 527 COPY_PARAMS_ARE_WRAPPED = True 528 529 # Whether values of params are set with "=" token or empty space 530 COPY_PARAMS_EQ_REQUIRED = False 531 532 # Whether COPY statement has INTO keyword 533 COPY_HAS_INTO_KEYWORD = True 534 535 # Whether the conditional TRY(expression) function is supported 536 TRY_SUPPORTED = True 537 538 # Whether the UESCAPE syntax in unicode strings is supported 539 SUPPORTS_UESCAPE = True 540 541 # Function used to replace escaped unicode codes in unicode strings 542 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 543 544 # The keyword to use when generating a star projection with excluded columns 545 STAR_EXCEPT = "EXCEPT" 546 547 # The HEX function name 548 HEX_FUNC = "HEX" 549 550 # The keywords to use when prefixing & separating WITH based properties 551 WITH_PROPERTIES_PREFIX = "WITH" 552 553 # Whether to quote the generated expression of exp.JsonPath 554 QUOTE_JSON_PATH = True 555 556 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 557 PAD_FILL_PATTERN_IS_REQUIRED = False 558 559 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 560 SUPPORTS_EXPLODING_PROJECTIONS = True 561 562 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 563 ARRAY_CONCAT_IS_VAR_LEN = True 564 565 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 566 SUPPORTS_CONVERT_TIMEZONE = False 567 568 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 569 SUPPORTS_MEDIAN = True 570 571 # Whether UNIX_SECONDS(timestamp) is supported 572 SUPPORTS_UNIX_SECONDS = False 573 574 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 575 ALTER_SET_WRAPPED = False 576 577 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 578 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 579 # TODO: The normalization should be done by default once we've tested it across all dialects. 580 NORMALIZE_EXTRACT_DATE_PARTS = False 581 582 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 583 PARSE_JSON_NAME: str | None = "PARSE_JSON" 584 585 # The function name of the exp.ArraySize expression 586 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 587 588 # The syntax to use when altering the type of a column 589 ALTER_SET_TYPE = "SET DATA TYPE" 590 591 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 592 # None -> Doesn't support it at all 593 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 594 # True (Postgres) -> Explicitly requires it 595 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 596 597 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 598 SUPPORTS_DECODE_CASE = True 599 600 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 601 SUPPORTS_BETWEEN_FLAGS = False 602 603 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 604 SUPPORTS_LIKE_QUANTIFIERS = True 605 606 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 607 MATCH_AGAINST_TABLE_PREFIX: str | None = None 608 609 # Whether to include the VARIABLE keyword for SET assignments 610 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 611 612 # The keyword to use for default value assignment in DECLARE statements 613 DECLARE_DEFAULT_ASSIGNMENT = "=" 614 615 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 616 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 617 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 618 UPDATE_STATEMENT_SUPPORTS_FROM = True 619 620 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 621 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 622 623 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 624 # - Snowflake: DROP ICEBERG TABLE a.b; 625 # - DuckDB: DROP TABLE a.b; 626 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 627 628 TYPE_MAPPING: t.ClassVar = { 629 exp.DType.DATETIME2: "TIMESTAMP", 630 exp.DType.NCHAR: "CHAR", 631 exp.DType.NVARCHAR: "VARCHAR", 632 exp.DType.MEDIUMTEXT: "TEXT", 633 exp.DType.LONGTEXT: "TEXT", 634 exp.DType.TINYTEXT: "TEXT", 635 exp.DType.BLOB: "VARBINARY", 636 exp.DType.MEDIUMBLOB: "BLOB", 637 exp.DType.LONGBLOB: "BLOB", 638 exp.DType.TINYBLOB: "BLOB", 639 exp.DType.INET: "INET", 640 exp.DType.ROWVERSION: "VARBINARY", 641 exp.DType.SMALLDATETIME: "TIMESTAMP", 642 } 643 644 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 645 646 # mapping of DType to its default parameters, bounds 647 TYPE_PARAM_SETTINGS: t.ClassVar[ 648 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 649 ] = {} 650 651 TIME_PART_SINGULARS: t.ClassVar = { 652 "MICROSECONDS": "MICROSECOND", 653 "SECONDS": "SECOND", 654 "MINUTES": "MINUTE", 655 "HOURS": "HOUR", 656 "DAYS": "DAY", 657 "WEEKS": "WEEK", 658 "MONTHS": "MONTH", 659 "QUARTERS": "QUARTER", 660 "YEARS": "YEAR", 661 } 662 663 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 664 "cluster": lambda self, e: self.sql(e, "cluster"), 665 "distribute": lambda self, e: self.sql(e, "distribute"), 666 "sort": lambda self, e: self.sql(e, "sort"), 667 **AFTER_HAVING_MODIFIER_TRANSFORMS, 668 } 669 670 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 671 672 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 673 674 PARAMETER_TOKEN = "@" 675 NAMED_PLACEHOLDER_TOKEN = ":" 676 677 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 678 679 PROPERTIES_LOCATION: t.ClassVar = { 680 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 681 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 682 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 683 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 684 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 685 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 686 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 687 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 688 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 689 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 690 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 691 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 692 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 693 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 694 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 695 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 696 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 697 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 698 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 699 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 700 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 701 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 702 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 703 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 704 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 705 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 706 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 707 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 708 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 709 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 710 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 711 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 712 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 713 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 714 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 715 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 717 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 718 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 719 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 720 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 721 exp.HeapProperty: exp.Properties.Location.POST_WITH, 722 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 723 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 725 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 728 exp.JournalProperty: exp.Properties.Location.POST_NAME, 729 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 730 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 731 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 734 exp.LogProperty: exp.Properties.Location.POST_NAME, 735 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 736 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 737 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 738 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 740 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 741 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 743 exp.Order: exp.Properties.Location.POST_SCHEMA, 744 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 746 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 747 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 748 exp.Property: exp.Properties.Location.POST_WITH, 749 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 750 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 751 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 753 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 754 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 759 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 760 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 761 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 762 exp.Set: exp.Properties.Location.POST_SCHEMA, 763 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 764 exp.SetProperty: exp.Properties.Location.POST_CREATE, 765 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 766 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 767 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 768 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 769 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 770 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 771 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 772 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 773 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 775 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 776 exp.Tags: exp.Properties.Location.POST_WITH, 777 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 778 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 779 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 780 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 781 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 782 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 783 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 784 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 785 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 786 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 787 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 788 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 789 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 790 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 791 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 792 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 793 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 794 } 795 796 # Keywords that can't be used as unquoted identifier names 797 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 798 799 # Exprs whose comments are separated from them for better formatting 800 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 801 exp.Command, 802 exp.Create, 803 exp.Describe, 804 exp.Delete, 805 exp.Drop, 806 exp.From, 807 exp.Insert, 808 exp.Join, 809 exp.MultitableInserts, 810 exp.Order, 811 exp.Group, 812 exp.Having, 813 exp.Select, 814 exp.SetOperation, 815 exp.Update, 816 exp.Where, 817 exp.With, 818 ) 819 820 # Exprs that should not have their comments generated in maybe_comment 821 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 822 exp.Binary, 823 exp.SetOperation, 824 ) 825 826 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 827 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 828 exp.Column, 829 exp.Literal, 830 exp.Neg, 831 exp.Paren, 832 ) 833 834 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 835 exp.DType.NVARCHAR, 836 exp.DType.VARCHAR, 837 exp.DType.CHAR, 838 exp.DType.NCHAR, 839 } 840 841 # Exprs that need to have all CTEs under them bubbled up to them 842 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 843 844 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 845 846 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 847 848 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 849 850 __slots__ = ( 851 "pretty", 852 "identify", 853 "normalize", 854 "pad", 855 "_indent", 856 "normalize_functions", 857 "unsupported_level", 858 "max_unsupported", 859 "leading_comma", 860 "max_text_width", 861 "comments", 862 "dialect", 863 "unsupported_messages", 864 "_escaped_quote_end", 865 "_escaped_byte_quote_end", 866 "_escaped_identifier_end", 867 "_next_name", 868 "_identifier_start", 869 "_identifier_end", 870 "_quote_json_path_key_using_brackets", 871 "_dispatch", 872 ) 873 874 def __init__( 875 self, 876 pretty: bool | int | None = None, 877 identify: str | bool = False, 878 normalize: bool = False, 879 pad: int = 2, 880 indent: int = 2, 881 normalize_functions: str | bool | None = None, 882 unsupported_level: ErrorLevel = ErrorLevel.WARN, 883 max_unsupported: int = 3, 884 leading_comma: bool = False, 885 max_text_width: int = 80, 886 comments: bool = True, 887 dialect: DialectType = None, 888 ): 889 import sqlglot 890 import sqlglot.dialects.dialect 891 892 self.pretty = pretty if pretty is not None else sqlglot.pretty 893 self.identify = identify 894 self.normalize = normalize 895 self.pad = pad 896 self._indent = indent 897 self.unsupported_level = unsupported_level 898 self.max_unsupported = max_unsupported 899 self.leading_comma = leading_comma 900 self.max_text_width = max_text_width 901 self.comments = comments 902 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 903 904 # This is both a Dialect property and a Generator argument, so we prioritize the latter 905 self.normalize_functions = ( 906 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 907 ) 908 909 self.unsupported_messages: list[str] = [] 910 self._escaped_quote_end: str = ( 911 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 912 ) 913 self._escaped_byte_quote_end: str = ( 914 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 915 if self.dialect.BYTE_END 916 else "" 917 ) 918 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 919 920 self._next_name = name_sequence("_t") 921 922 self._identifier_start = self.dialect.IDENTIFIER_START 923 self._identifier_end = self.dialect.IDENTIFIER_END 924 925 self._quote_json_path_key_using_brackets = True 926 927 cls = type(self) 928 dispatch = _DISPATCH_CACHE.get(cls) 929 if dispatch is None: 930 dispatch = _build_dispatch(cls) 931 _DISPATCH_CACHE[cls] = dispatch 932 self._dispatch = dispatch 933 934 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 935 """ 936 Generates the SQL string corresponding to the given syntax tree. 937 938 Args: 939 expression: The syntax tree. 940 copy: Whether to copy the expression. The generator performs mutations so 941 it is safer to copy. 942 943 Returns: 944 The SQL string corresponding to `expression`. 945 """ 946 if copy: 947 expression = expression.copy() 948 949 expression = self.preprocess(expression) 950 951 self.unsupported_messages = [] 952 sql = self.sql(expression).strip() 953 954 if self.pretty: 955 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 956 957 if self.unsupported_level == ErrorLevel.IGNORE: 958 return sql 959 960 if self.unsupported_level == ErrorLevel.WARN: 961 for msg in self.unsupported_messages: 962 logger.warning(msg) 963 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 964 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 965 966 return sql 967 968 def preprocess(self, expression: exp.Expr) -> exp.Expr: 969 """Apply generic preprocessing transformations to a given expression.""" 970 expression = self._move_ctes_to_top_level(expression) 971 972 if self.ENSURE_BOOLS: 973 import sqlglot.transforms 974 975 expression = sqlglot.transforms.ensure_bools(expression) 976 977 return expression 978 979 def _move_ctes_to_top_level(self, expression: E) -> E: 980 if ( 981 not expression.parent 982 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 983 and any(node.parent is not expression for node in expression.find_all(exp.With)) 984 ): 985 import sqlglot.transforms 986 987 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 988 return expression 989 990 def unsupported(self, message: str) -> None: 991 if self.unsupported_level == ErrorLevel.IMMEDIATE: 992 raise UnsupportedError(message) 993 self.unsupported_messages.append(message) 994 995 def sep(self, sep: str = " ") -> str: 996 return f"{sep.strip()}\n" if self.pretty else sep 997 998 def seg(self, sql: str, sep: str = " ") -> str: 999 return f"{self.sep(sep)}{sql}" 1000 1001 def sanitize_comment(self, comment: str) -> str: 1002 comment = " " + comment if comment[0].strip() else comment 1003 comment = comment + " " if comment[-1].strip() else comment 1004 1005 # Escape block comment markers to prevent premature closure or unintended nesting. 1006 # This is necessary because single-line comments (--) are converted to block comments 1007 # (/* */) on output, and any */ in the original text would close the comment early. 1008 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1009 1010 return comment 1011 1012 def maybe_comment( 1013 self, 1014 sql: str, 1015 expression: exp.Expr | None = None, 1016 comments: list[str] | None = None, 1017 separated: bool = False, 1018 ) -> str: 1019 comments = ( 1020 ((expression and expression.comments) if comments is None else comments) # type: ignore 1021 if self.comments 1022 else None 1023 ) 1024 1025 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1026 return sql 1027 1028 comments_list = [ 1029 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1030 for comment in comments 1031 if comment 1032 ] 1033 1034 if not comments_list: 1035 return sql 1036 1037 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1038 comments_sql = self.sep().join(comments_list) 1039 return ( 1040 f"{self.sep()}{comments_sql}{sql}" 1041 if not sql or sql[0].isspace() 1042 else f"{comments_sql}{self.sep()}{sql}" 1043 ) 1044 1045 return f"{sql} {' '.join(comments_list)}" 1046 1047 def wrap(self, expression: exp.Expr | str) -> str: 1048 this_sql = ( 1049 self.sql(expression) 1050 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1051 else self.sql(expression, "this") 1052 ) 1053 if not this_sql: 1054 return "()" 1055 1056 this_sql = self.indent(this_sql, level=1, pad=0) 1057 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1058 1059 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1060 original = self.identify 1061 self.identify = False 1062 result = func(*args, **kwargs) 1063 self.identify = original 1064 return result 1065 1066 def normalize_func(self, name: str) -> str: 1067 if self.normalize_functions == "upper" or self.normalize_functions is True: 1068 return name.upper() 1069 if self.normalize_functions == "lower": 1070 return name.lower() 1071 return name 1072 1073 def indent( 1074 self, 1075 sql: str, 1076 level: int = 0, 1077 pad: int | None = None, 1078 skip_first: bool = False, 1079 skip_last: bool = False, 1080 ) -> str: 1081 if not self.pretty or not sql: 1082 return sql 1083 1084 pad = self.pad if pad is None else pad 1085 lines = sql.split("\n") 1086 1087 return "\n".join( 1088 ( 1089 line 1090 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1091 else f"{' ' * (level * self._indent + pad)}{line}" 1092 ) 1093 for i, line in enumerate(lines) 1094 ) 1095 1096 def sql( 1097 self, 1098 expression: str | exp.Expr | None, 1099 key: str | None = None, 1100 comment: bool = True, 1101 ) -> str: 1102 if not expression: 1103 return "" 1104 1105 if isinstance(expression, str): 1106 return expression 1107 1108 if key: 1109 value = expression.args.get(key) 1110 if value: 1111 return self.sql(value) 1112 return "" 1113 1114 handler = self._dispatch.get(expression.__class__) 1115 1116 if handler: 1117 sql = handler(self, expression) 1118 elif isinstance(expression, exp.Func): 1119 sql = self.function_fallback_sql(expression) 1120 elif isinstance(expression, exp.Property): 1121 sql = self.property_sql(expression) 1122 else: 1123 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1124 1125 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1126 1127 def uncache_sql(self, expression: exp.Uncache) -> str: 1128 table = self.sql(expression, "this") 1129 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1130 return f"UNCACHE TABLE{exists_sql} {table}" 1131 1132 def cache_sql(self, expression: exp.Cache) -> str: 1133 lazy = " LAZY" if expression.args.get("lazy") else "" 1134 table = self.sql(expression, "this") 1135 options = expression.args.get("options") 1136 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1137 sql = self.sql(expression, "expression") 1138 sql = f" AS{self.sep()}{sql}" if sql else "" 1139 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1140 return self.prepend_ctes(expression, sql) 1141 1142 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1143 default = "DEFAULT " if expression.args.get("default") else "" 1144 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1145 1146 def column_parts(self, expression: exp.Column) -> str: 1147 return ".".join( 1148 self.sql(part) 1149 for part in ( 1150 expression.args.get("catalog"), 1151 expression.args.get("db"), 1152 expression.args.get("table"), 1153 expression.args.get("this"), 1154 ) 1155 if part 1156 ) 1157 1158 def column_sql(self, expression: exp.Column) -> str: 1159 join_mark = " (+)" if expression.args.get("join_mark") else "" 1160 1161 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1162 join_mark = "" 1163 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1164 1165 return f"{self.column_parts(expression)}{join_mark}" 1166 1167 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1168 return self.column_sql(expression) 1169 1170 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1171 this = self.sql(expression, "this") 1172 this = f" {this}" if this else "" 1173 position = self.sql(expression, "position") 1174 return f"{position}{this}" 1175 1176 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1177 column = self.sql(expression, "this") 1178 kind = self.sql(expression, "kind") 1179 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1180 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1181 kind = f"{sep}{kind}" if kind else "" 1182 constraints = f" {constraints}" if constraints else "" 1183 position = self.sql(expression, "position") 1184 position = f" {position}" if position else "" 1185 1186 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1187 kind = "" 1188 1189 return f"{exists}{column}{kind}{constraints}{position}" 1190 1191 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1192 this = self.sql(expression, "this") 1193 kind_sql = self.sql(expression, "kind").strip() 1194 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1195 1196 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1197 this = self.sql(expression, "this") 1198 if expression.args.get("not_null"): 1199 persisted = " PERSISTED NOT NULL" 1200 elif expression.args.get("persisted"): 1201 persisted = " PERSISTED" 1202 else: 1203 persisted = "" 1204 1205 return f"AS {this}{persisted}" 1206 1207 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1208 return self.token_sql(TokenType.AUTO_INCREMENT) 1209 1210 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1211 if isinstance(expression.this, list): 1212 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1213 else: 1214 this = self.sql(expression, "this") 1215 1216 return f"COMPRESS {this}" 1217 1218 def generatedasidentitycolumnconstraint_sql( 1219 self, expression: exp.GeneratedAsIdentityColumnConstraint 1220 ) -> str: 1221 this = "" 1222 if expression.this is not None: 1223 on_null = " ON NULL" if expression.args.get("on_null") else "" 1224 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1225 1226 start = expression.args.get("start") 1227 start = f"START WITH {start}" if start else "" 1228 increment = expression.args.get("increment") 1229 increment = f" INCREMENT BY {increment}" if increment else "" 1230 minvalue = expression.args.get("minvalue") 1231 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1232 maxvalue = expression.args.get("maxvalue") 1233 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1234 cycle = expression.args.get("cycle") 1235 cycle_sql = "" 1236 1237 if cycle is not None: 1238 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1239 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1240 1241 sequence_opts = "" 1242 if start or increment or cycle_sql: 1243 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1244 sequence_opts = f" ({sequence_opts.strip()})" 1245 1246 expr = self.sql(expression, "expression") 1247 expr = f"({expr})" if expr else "IDENTITY" 1248 1249 return f"GENERATED{this} AS {expr}{sequence_opts}" 1250 1251 def generatedasrowcolumnconstraint_sql( 1252 self, expression: exp.GeneratedAsRowColumnConstraint 1253 ) -> str: 1254 start = "START" if expression.args.get("start") else "END" 1255 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1256 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1257 1258 def periodforsystemtimeconstraint_sql( 1259 self, expression: exp.PeriodForSystemTimeConstraint 1260 ) -> str: 1261 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1262 1263 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1264 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1265 1266 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1267 desc = expression.args.get("desc") 1268 if desc is not None: 1269 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1270 options = self.expressions(expression, key="options", flat=True, sep=" ") 1271 options = f" {options}" if options else "" 1272 return f"PRIMARY KEY{options}" 1273 1274 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1275 this = self.sql(expression, "this") 1276 this = f" {this}" if this else "" 1277 index_type = expression.args.get("index_type") 1278 index_type = f" USING {index_type}" if index_type else "" 1279 on_conflict = self.sql(expression, "on_conflict") 1280 on_conflict = f" {on_conflict}" if on_conflict else "" 1281 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1282 options = self.expressions(expression, key="options", flat=True, sep=" ") 1283 options = f" {options}" if options else "" 1284 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1285 1286 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1287 input_ = expression.args.get("input_") 1288 output = expression.args.get("output") 1289 variadic = expression.args.get("variadic") 1290 1291 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1292 if variadic: 1293 return "VARIADIC" 1294 1295 if input_ and output: 1296 return f"IN{self.INOUT_SEPARATOR}OUT" 1297 if input_: 1298 return "IN" 1299 if output: 1300 return "OUT" 1301 1302 return "" 1303 1304 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1305 return self.sql(expression, "this") 1306 1307 def create_sql(self, expression: exp.Create) -> str: 1308 kind = self.sql(expression, "kind") 1309 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1310 1311 properties = expression.args.get("properties") 1312 1313 if ( 1314 kind == "TRIGGER" 1315 and properties 1316 and properties.expressions 1317 and isinstance(properties.expressions[0], exp.TriggerProperties) 1318 and properties.expressions[0].args.get("constraint") 1319 ): 1320 kind = f"CONSTRAINT {kind}" 1321 1322 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1323 1324 this = self.createable_sql(expression, properties_locs) 1325 1326 properties_sql = "" 1327 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1328 exp.Properties.Location.POST_WITH 1329 ): 1330 props_ast = exp.Properties( 1331 expressions=[ 1332 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1333 *properties_locs[exp.Properties.Location.POST_WITH], 1334 ] 1335 ) 1336 props_ast.parent = expression 1337 properties_sql = self.sql(props_ast) 1338 1339 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1340 properties_sql = self.sep() + properties_sql 1341 elif not self.pretty: 1342 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1343 properties_sql = f" {properties_sql}" 1344 1345 begin = " BEGIN" if expression.args.get("begin") else "" 1346 1347 expression_sql = self.sql(expression, "expression") 1348 if expression_sql: 1349 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1350 1351 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1352 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1353 ): 1354 postalias_props_sql = "" 1355 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1356 postalias_props_sql = self.properties( 1357 exp.Properties( 1358 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1359 ), 1360 wrapped=False, 1361 ) 1362 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1363 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1364 1365 postindex_props_sql = "" 1366 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1367 postindex_props_sql = self.properties( 1368 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1369 wrapped=False, 1370 prefix=" ", 1371 ) 1372 1373 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1374 indexes = f" {indexes}" if indexes else "" 1375 index_sql = indexes + postindex_props_sql 1376 1377 replace = " OR REPLACE" if expression.args.get("replace") else "" 1378 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1379 unique = " UNIQUE" if expression.args.get("unique") else "" 1380 1381 clustered = expression.args.get("clustered") 1382 if clustered is None: 1383 clustered_sql = "" 1384 elif clustered: 1385 clustered_sql = " CLUSTERED COLUMNSTORE" 1386 else: 1387 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1388 1389 postcreate_props_sql = "" 1390 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1391 postcreate_props_sql = self.properties( 1392 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1393 sep=" ", 1394 prefix=" ", 1395 wrapped=False, 1396 ) 1397 1398 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1399 1400 postexpression_props_sql = "" 1401 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1402 postexpression_props_sql = self.properties( 1403 exp.Properties( 1404 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1405 ), 1406 sep=" ", 1407 prefix=" ", 1408 wrapped=False, 1409 ) 1410 1411 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1412 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1413 no_schema_binding = ( 1414 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1415 ) 1416 1417 clone = self.sql(expression, "clone") 1418 clone = f" {clone}" if clone else "" 1419 1420 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1421 properties_expression = f"{expression_sql}{properties_sql}" 1422 else: 1423 properties_expression = f"{properties_sql}{expression_sql}" 1424 1425 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1426 return self.prepend_ctes(expression, expression_sql) 1427 1428 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1429 start = self.sql(expression, "start") 1430 start = f"START WITH {start}" if start else "" 1431 increment = self.sql(expression, "increment") 1432 increment = f" INCREMENT BY {increment}" if increment else "" 1433 minvalue = self.sql(expression, "minvalue") 1434 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1435 maxvalue = self.sql(expression, "maxvalue") 1436 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1437 owned = self.sql(expression, "owned") 1438 owned = f" OWNED BY {owned}" if owned else "" 1439 1440 cache = expression.args.get("cache") 1441 if cache is None: 1442 cache_str = "" 1443 elif cache is True: 1444 cache_str = " CACHE" 1445 else: 1446 cache_str = f" CACHE {cache}" 1447 1448 options = self.expressions(expression, key="options", flat=True, sep=" ") 1449 options = f" {options}" if options else "" 1450 1451 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1452 1453 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1454 timing = expression.args.get("timing", "") 1455 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1456 timing_events = f"{timing} {events}".strip() if timing or events else "" 1457 1458 parts = [timing_events, "ON", self.sql(expression, "table")] 1459 1460 if referenced_table := expression.args.get("referenced_table"): 1461 parts.extend(["FROM", self.sql(referenced_table)]) 1462 1463 if deferrable := expression.args.get("deferrable"): 1464 parts.append(deferrable) 1465 1466 if initially := expression.args.get("initially"): 1467 parts.append(f"INITIALLY {initially}") 1468 1469 if referencing := expression.args.get("referencing"): 1470 parts.append(self.sql(referencing)) 1471 1472 if for_each := expression.args.get("for_each"): 1473 parts.append(f"FOR EACH {for_each}") 1474 1475 if when := expression.args.get("when"): 1476 parts.append(f"WHEN ({self.sql(when)})") 1477 1478 parts.append(self.sql(expression, "execute")) 1479 1480 return self.sep().join(parts) 1481 1482 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1483 parts = [] 1484 1485 if old_alias := expression.args.get("old"): 1486 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1487 1488 if new_alias := expression.args.get("new"): 1489 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1490 1491 return f"REFERENCING {' '.join(parts)}" 1492 1493 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1494 columns = expression.args.get("columns") 1495 if columns: 1496 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1497 1498 return self.sql(expression, "this") 1499 1500 def clone_sql(self, expression: exp.Clone) -> str: 1501 this = self.sql(expression, "this") 1502 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1503 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1504 return f"{shallow}{keyword} {this}" 1505 1506 def describe_sql(self, expression: exp.Describe) -> str: 1507 style = expression.args.get("style") 1508 style = f" {style}" if style else "" 1509 partition = self.sql(expression, "partition") 1510 partition = f" {partition}" if partition else "" 1511 format = self.sql(expression, "format") 1512 format = f" {format}" if format else "" 1513 as_json = " AS JSON" if expression.args.get("as_json") else "" 1514 1515 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1516 1517 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1518 tag = self.sql(expression, "tag") 1519 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1520 1521 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1522 with_ = self.sql(expression, "with_") 1523 if with_: 1524 sql = f"{with_}{self.sep()}{sql}" 1525 return sql 1526 1527 def with_sql(self, expression: exp.With) -> str: 1528 udfs = self.expressions(expression, key="udfs", flat=True) 1529 udfs = f"WITH {udfs}" if udfs else "" 1530 1531 sql = self.expressions(expression, flat=True) 1532 1533 recursive = ( 1534 "RECURSIVE " 1535 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1536 else "" 1537 ) 1538 search = self.sql(expression, "search") 1539 search = f" {search}" if search else "" 1540 1541 sql = f"WITH {recursive}{sql}{search}" if sql else "" 1542 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1543 1544 def cte_sql(self, expression: exp.CTE) -> str: 1545 alias = expression.args.get("alias") 1546 if alias: 1547 alias.add_comments(expression.pop_comments()) 1548 1549 alias_sql = self.sql(expression, "alias") 1550 1551 materialized = expression.args.get("materialized") 1552 if materialized is False: 1553 materialized = "NOT MATERIALIZED " 1554 elif materialized: 1555 materialized = "MATERIALIZED " 1556 1557 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1558 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1559 1560 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1561 1562 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1563 alias = self.sql(expression, "this") 1564 columns = self.expressions(expression, key="columns", flat=True) 1565 columns = f"({columns})" if columns else "" 1566 1567 if ( 1568 columns 1569 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1570 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1571 ): 1572 columns = "" 1573 self.unsupported("Named columns are not supported in table alias.") 1574 1575 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1576 alias = self._next_name() 1577 1578 return f"{alias}{columns}" 1579 1580 def bitstring_sql(self, expression: exp.BitString) -> str: 1581 this = self.sql(expression, "this") 1582 if self.dialect.BIT_START: 1583 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1584 return f"{int(this, 2)}" 1585 1586 def hexstring_sql( 1587 self, expression: exp.HexString, binary_function_repr: str | None = None 1588 ) -> str: 1589 this = self.sql(expression, "this") 1590 is_integer_type = expression.args.get("is_integer") 1591 1592 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1593 not self.dialect.HEX_START and not binary_function_repr 1594 ): 1595 # Integer representation will be returned if: 1596 # - The read dialect treats the hex value as integer literal but not the write 1597 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1598 return f"{int(this, 16)}" 1599 1600 if not is_integer_type: 1601 # Read dialect treats the hex value as BINARY/BLOB 1602 if binary_function_repr: 1603 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1604 return self.func(binary_function_repr, exp.Literal.string(this)) 1605 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1606 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1607 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1608 1609 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1610 1611 def bytestring_sql(self, expression: exp.ByteString) -> str: 1612 this = self.sql(expression, "this") 1613 if self.dialect.BYTE_START: 1614 escaped_byte_string = self.escape_str( 1615 this, 1616 escape_backslash=False, 1617 delimiter=self.dialect.BYTE_END, 1618 escaped_delimiter=self._escaped_byte_quote_end, 1619 is_byte_string=True, 1620 ) 1621 is_bytes = expression.args.get("is_bytes", False) 1622 delimited_byte_string = ( 1623 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1624 ) 1625 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1626 return self.sql( 1627 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1628 ) 1629 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1630 return self.sql( 1631 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1632 ) 1633 1634 return delimited_byte_string 1635 1636 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1637 return self.sql(exp.Literal.string(this)) 1638 1639 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1640 return "" 1641 1642 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1643 this = self.sql(expression, "this") 1644 escape = expression.args.get("escape") 1645 1646 if self.dialect.UNICODE_START: 1647 escape_substitute = r"\\\1" 1648 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1649 else: 1650 escape_substitute = r"\\u\1" 1651 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1652 1653 if escape: 1654 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1655 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1656 else: 1657 escape_pattern = ESCAPED_UNICODE_RE 1658 escape_sql = "" 1659 1660 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1661 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1662 1663 return f"{left_quote}{this}{right_quote}{escape_sql}" 1664 1665 def rawstring_sql(self, expression: exp.RawString) -> str: 1666 string = expression.this 1667 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1668 string = string.replace("\\", "\\\\") 1669 1670 string = self.escape_str(string, escape_backslash=False) 1671 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1672 1673 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1674 this = self.sql(expression, "this") 1675 specifier = self.sql(expression, "expression") 1676 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1677 return f"{this}{specifier}" 1678 1679 def datatype_param_bound_limiter( 1680 self, 1681 expression: exp.DataType, 1682 type_value: exp.DType, 1683 defaults: tuple[int, ...], 1684 bounds: tuple[int | None, ...], 1685 ) -> exp.DataType: 1686 params = expression.expressions 1687 1688 if not params: 1689 if defaults: 1690 expression.set( 1691 "expressions", 1692 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1693 ) 1694 return expression 1695 1696 if not bounds: 1697 return expression 1698 1699 for i, param in enumerate(params): 1700 bound = bounds[i] if i < len(bounds) else None 1701 if bound is None: 1702 continue 1703 1704 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1705 if ( 1706 isinstance(param_value, exp.Literal) 1707 and param_value.is_number 1708 and int(param_value.to_py()) > bound 1709 ): 1710 self.unsupported( 1711 f"{type_value.value} parameter {param_value.name} exceeds " 1712 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1713 ) 1714 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1715 1716 return expression 1717 1718 def datatype_sql(self, expression: exp.DataType) -> str: 1719 nested = "" 1720 values = "" 1721 1722 expr_nested = expression.args.get("nested") 1723 type_value = expression.this 1724 1725 if ( 1726 not expr_nested 1727 and isinstance(type_value, exp.DType) 1728 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1729 ): 1730 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1731 1732 interior = ( 1733 self.expressions( 1734 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1735 ) 1736 if expr_nested and self.pretty 1737 else self.expressions(expression, flat=True) 1738 ) 1739 1740 if type_value in self.UNSUPPORTED_TYPES: 1741 self.unsupported( 1742 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1743 ) 1744 1745 type_sql: t.Any = "" 1746 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1747 type_sql = self.sql(expression, "kind") 1748 elif type_value == exp.DType.CHARACTER_SET: 1749 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1750 else: 1751 type_sql = ( 1752 self.TYPE_MAPPING.get(type_value, type_value.value) 1753 if isinstance(type_value, exp.DType) 1754 else type_value 1755 ) 1756 1757 if interior: 1758 if expr_nested: 1759 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1760 if expression.args.get("values") is not None: 1761 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1762 values = self.expressions(expression, key="values", flat=True) 1763 values = f"{delimiters[0]}{values}{delimiters[1]}" 1764 elif type_value == exp.DType.INTERVAL: 1765 nested = f" {interior}" 1766 else: 1767 nested = f"({interior})" 1768 1769 type_sql = f"{type_sql}{nested}{values}" 1770 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1771 exp.DType.TIMETZ, 1772 exp.DType.TIMESTAMPTZ, 1773 ): 1774 type_sql = f"{type_sql} WITH TIME ZONE" 1775 1776 collate = self.sql(expression, "collate") 1777 if collate: 1778 type_sql = f"{type_sql} COLLATE {collate}" 1779 1780 return type_sql 1781 1782 def directory_sql(self, expression: exp.Directory) -> str: 1783 local = "LOCAL " if expression.args.get("local") else "" 1784 row_format = self.sql(expression, "row_format") 1785 row_format = f" {row_format}" if row_format else "" 1786 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1787 1788 def delete_sql(self, expression: exp.Delete) -> str: 1789 hint = self.sql(expression, "hint") 1790 this = self.sql(expression, "this") 1791 this = f" FROM {this}" if this else "" 1792 using = self.expressions(expression, key="using") 1793 using = f" USING {using}" if using else "" 1794 cluster = self.sql(expression, "cluster") 1795 cluster = f" {cluster}" if cluster else "" 1796 where = self.sql(expression, "where") 1797 returning = self.sql(expression, "returning") 1798 order = self.sql(expression, "order") 1799 limit = self.sql(expression, "limit") 1800 tables = self.expressions(expression, key="tables") 1801 tables = f" {tables}" if tables else "" 1802 if self.RETURNING_END: 1803 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1804 else: 1805 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1806 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1807 1808 def drop_sql(self, expression: exp.Drop) -> str: 1809 this = self.sql(expression, "this") 1810 expressions = self.expressions(expression, flat=True) 1811 expressions = f" ({expressions})" if expressions else "" 1812 kind = expression.args["kind"] 1813 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1814 iceberg = ( 1815 " ICEBERG" 1816 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1817 else "" 1818 ) 1819 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1820 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1821 on_cluster = self.sql(expression, "cluster") 1822 on_cluster = f" {on_cluster}" if on_cluster else "" 1823 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1824 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1825 cascade = " CASCADE" if expression.args.get("cascade") else "" 1826 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1827 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1828 purge = " PURGE" if expression.args.get("purge") else "" 1829 sync = " SYNC" if expression.args.get("sync") else "" 1830 force = " FORCE" if expression.args.get("force") else "" 1831 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1832 1833 def set_operation(self, expression: exp.SetOperation) -> str: 1834 op_type = type(expression) 1835 op_name = op_type.key.upper() 1836 1837 distinct = expression.args.get("distinct") 1838 if ( 1839 distinct is False 1840 and op_type in (exp.Except, exp.Intersect) 1841 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1842 ): 1843 self.unsupported(f"{op_name} ALL is not supported") 1844 1845 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1846 1847 if distinct is None: 1848 distinct = default_distinct 1849 if distinct is None: 1850 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1851 1852 if distinct is default_distinct: 1853 distinct_or_all = "" 1854 else: 1855 distinct_or_all = " DISTINCT" if distinct else " ALL" 1856 1857 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1858 side_kind = f"{side_kind} " if side_kind else "" 1859 1860 by_name = " BY NAME" if expression.args.get("by_name") else "" 1861 on = self.expressions(expression, key="on", flat=True) 1862 on = f" ON ({on})" if on else "" 1863 1864 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1865 1866 def set_operations(self, expression: exp.SetOperation) -> str: 1867 if not self.SET_OP_MODIFIERS: 1868 limit = expression.args.get("limit") 1869 order = expression.args.get("order") 1870 1871 if limit or order: 1872 select = self._move_ctes_to_top_level( 1873 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1874 ) 1875 1876 if limit: 1877 select = select.limit(limit.pop(), copy=False) 1878 if order: 1879 select = select.order_by(order.pop(), copy=False) 1880 return self.sql(select) 1881 1882 sqls: list[str] = [] 1883 stack: list[str | exp.Expr] = [expression] 1884 1885 while stack: 1886 node = stack.pop() 1887 1888 if isinstance(node, exp.SetOperation): 1889 stack.append(node.expression) 1890 stack.append( 1891 self.maybe_comment( 1892 self.set_operation(node), comments=node.comments, separated=True 1893 ) 1894 ) 1895 stack.append(node.this) 1896 else: 1897 sqls.append(self.sql(node)) 1898 1899 this = self.sep().join(sqls) 1900 this = self.query_modifiers(expression, this) 1901 return self.prepend_ctes(expression, this) 1902 1903 def fetch_sql(self, expression: exp.Fetch) -> str: 1904 direction = expression.args.get("direction") 1905 direction = f" {direction}" if direction else "" 1906 count = self.sql(expression, "count") 1907 count = f" {count}" if count else "" 1908 limit_options = self.sql(expression, "limit_options") 1909 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1910 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1911 1912 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1913 percent = " PERCENT" if expression.args.get("percent") else "" 1914 rows = " ROWS" if expression.args.get("rows") else "" 1915 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1916 if not with_ties and rows: 1917 with_ties = " ONLY" 1918 return f"{percent}{rows}{with_ties}" 1919 1920 def filter_sql(self, expression: exp.Filter) -> str: 1921 this = self.sql(expression, "this") 1922 where = self.sql(expression, "expression").strip() 1923 return f"{this} FILTER({where})" 1924 1925 def hint_sql(self, expression: exp.Hint) -> str: 1926 if not self.QUERY_HINTS: 1927 self.unsupported("Hints are not supported") 1928 return "" 1929 1930 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1931 1932 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1933 using = self.sql(expression, "using") 1934 using = f" USING {using}" if using else "" 1935 columns = self.expressions(expression, key="columns", flat=True) 1936 columns = f"({columns})" if columns else "" 1937 partition_by = self.expressions(expression, key="partition_by", flat=True) 1938 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1939 where = self.sql(expression, "where") 1940 include = self.expressions(expression, key="include", flat=True) 1941 if include: 1942 include = f" INCLUDE ({include})" 1943 with_storage = self.expressions(expression, key="with_storage", flat=True) 1944 with_storage = f" WITH ({with_storage})" if with_storage else "" 1945 tablespace = self.sql(expression, "tablespace") 1946 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1947 on = self.sql(expression, "on") 1948 on = f" ON {on}" if on else "" 1949 1950 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1951 1952 def index_sql(self, expression: exp.Index) -> str: 1953 unique = "UNIQUE " if expression.args.get("unique") else "" 1954 primary = "PRIMARY " if expression.args.get("primary") else "" 1955 amp = "AMP " if expression.args.get("amp") else "" 1956 name = self.sql(expression, "this") 1957 name = f"{name} " if name else "" 1958 table = self.sql(expression, "table") 1959 table = f"{self.INDEX_ON} {table}" if table else "" 1960 1961 index = "INDEX " if not table else "" 1962 1963 params = self.sql(expression, "params") 1964 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1965 1966 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 1967 this = expression.this 1968 if this and this.is_string: 1969 resolved = maybe_parse(this.name).sql(self.dialect) 1970 if "expressions" in expression.args: 1971 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 1972 # We can't safely emit the call to other dialects since name/arg semantics may differ 1973 self.unsupported( 1974 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 1975 ) 1976 return resolved 1977 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 1978 return self.func("IDENTIFIER", this) 1979 1980 def identifier_sql(self, expression: exp.Identifier) -> str: 1981 text = expression.name 1982 lower = text.lower() 1983 quoted = expression.quoted 1984 text = lower if self.normalize and not quoted else text 1985 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1986 if ( 1987 quoted 1988 or self.dialect.can_quote(expression, self.identify) 1989 or lower in self.RESERVED_KEYWORDS 1990 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1991 ): 1992 text = ( 1993 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1994 ) 1995 return text 1996 1997 def hex_sql(self, expression: exp.Hex) -> str: 1998 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1999 if self.dialect.HEX_LOWERCASE: 2000 text = self.func("LOWER", text) 2001 2002 return text 2003 2004 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2005 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2006 if not self.dialect.HEX_LOWERCASE: 2007 text = self.func("LOWER", text) 2008 return text 2009 2010 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2011 input_format = self.sql(expression, "input_format") 2012 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2013 output_format = self.sql(expression, "output_format") 2014 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2015 return self.sep().join((input_format, output_format)) 2016 2017 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2018 string = self.sql(exp.Literal.string(expression.name)) 2019 return f"{prefix}{string}" 2020 2021 def partition_sql(self, expression: exp.Partition) -> str: 2022 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2023 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2024 2025 def properties_sql(self, expression: exp.Properties) -> str: 2026 root_properties = [] 2027 with_properties = [] 2028 2029 for p in expression.expressions: 2030 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2031 if p_loc == exp.Properties.Location.POST_WITH: 2032 with_properties.append(p) 2033 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2034 root_properties.append(p) 2035 2036 root_props_ast = exp.Properties(expressions=root_properties) 2037 root_props_ast.parent = expression.parent 2038 2039 with_props_ast = exp.Properties(expressions=with_properties) 2040 with_props_ast.parent = expression.parent 2041 2042 root_props = self.root_properties(root_props_ast) 2043 with_props = self.with_properties(with_props_ast) 2044 2045 if root_props and with_props and not self.pretty: 2046 with_props = " " + with_props 2047 2048 return root_props + with_props 2049 2050 def root_properties(self, properties: exp.Properties) -> str: 2051 if properties.expressions: 2052 return self.expressions(properties, indent=False, sep=" ") 2053 return "" 2054 2055 def properties( 2056 self, 2057 properties: exp.Properties, 2058 prefix: str = "", 2059 sep: str = ", ", 2060 suffix: str = "", 2061 wrapped: bool = True, 2062 ) -> str: 2063 if properties.expressions: 2064 expressions = self.expressions(properties, sep=sep, indent=False) 2065 if expressions: 2066 expressions = self.wrap(expressions) if wrapped else expressions 2067 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2068 return "" 2069 2070 def with_properties(self, properties: exp.Properties) -> str: 2071 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2072 2073 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2074 properties_locs = defaultdict(list) 2075 for p in properties.expressions: 2076 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2077 if p_loc != exp.Properties.Location.UNSUPPORTED: 2078 properties_locs[p_loc].append(p) 2079 else: 2080 self.unsupported(f"Unsupported property {p.key}") 2081 2082 return properties_locs 2083 2084 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2085 if isinstance(expression.this, exp.Dot): 2086 return self.sql(expression, "this") 2087 return f"'{expression.name}'" if string_key else expression.name 2088 2089 def property_sql(self, expression: exp.Property) -> str: 2090 property_cls = expression.__class__ 2091 if property_cls == exp.Property: 2092 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2093 2094 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2095 if not property_name: 2096 self.unsupported(f"Unsupported property {expression.key}") 2097 2098 return f"{property_name}={self.sql(expression, 'this')}" 2099 2100 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2101 return f"UUID {self.sql(expression, 'this')}" 2102 2103 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2104 if self.SUPPORTS_CREATE_TABLE_LIKE: 2105 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2106 options = f" {options}" if options else "" 2107 2108 like = f"LIKE {self.sql(expression, 'this')}{options}" 2109 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2110 like = f"({like})" 2111 2112 return like 2113 2114 if expression.expressions: 2115 self.unsupported("Transpilation of LIKE property options is unsupported") 2116 2117 select = exp.select("*").from_(expression.this).limit(0) 2118 return f"AS {self.sql(select)}" 2119 2120 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2121 no = "NO " if expression.args.get("no") else "" 2122 protection = " PROTECTION" if expression.args.get("protection") else "" 2123 return f"{no}FALLBACK{protection}" 2124 2125 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2126 no = "NO " if expression.args.get("no") else "" 2127 local = expression.args.get("local") 2128 local = f"{local} " if local else "" 2129 dual = "DUAL " if expression.args.get("dual") else "" 2130 before = "BEFORE " if expression.args.get("before") else "" 2131 after = "AFTER " if expression.args.get("after") else "" 2132 return f"{no}{local}{dual}{before}{after}JOURNAL" 2133 2134 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2135 freespace = self.sql(expression, "this") 2136 percent = " PERCENT" if expression.args.get("percent") else "" 2137 return f"FREESPACE={freespace}{percent}" 2138 2139 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2140 if expression.args.get("default"): 2141 property = "DEFAULT" 2142 elif expression.args.get("on"): 2143 property = "ON" 2144 else: 2145 property = "OFF" 2146 return f"CHECKSUM={property}" 2147 2148 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2149 if expression.args.get("no"): 2150 return "NO MERGEBLOCKRATIO" 2151 if expression.args.get("default"): 2152 return "DEFAULT MERGEBLOCKRATIO" 2153 2154 percent = " PERCENT" if expression.args.get("percent") else "" 2155 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2156 2157 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2158 expressions = self.expressions(expression, flat=True) 2159 expressions = f"({expressions})" if expressions else "" 2160 return f"USING {self.sql(expression, 'this')}{expressions}" 2161 2162 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2163 default = expression.args.get("default") 2164 minimum = expression.args.get("minimum") 2165 maximum = expression.args.get("maximum") 2166 if default or minimum or maximum: 2167 if default: 2168 prop = "DEFAULT" 2169 elif minimum: 2170 prop = "MINIMUM" 2171 else: 2172 prop = "MAXIMUM" 2173 return f"{prop} DATABLOCKSIZE" 2174 units = expression.args.get("units") 2175 units = f" {units}" if units else "" 2176 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2177 2178 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2179 autotemp = expression.args.get("autotemp") 2180 always = expression.args.get("always") 2181 default = expression.args.get("default") 2182 manual = expression.args.get("manual") 2183 never = expression.args.get("never") 2184 2185 if autotemp is not None: 2186 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2187 elif always: 2188 prop = "ALWAYS" 2189 elif default: 2190 prop = "DEFAULT" 2191 elif manual: 2192 prop = "MANUAL" 2193 elif never: 2194 prop = "NEVER" 2195 return f"BLOCKCOMPRESSION={prop}" 2196 2197 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2198 no = expression.args.get("no") 2199 no = " NO" if no else "" 2200 concurrent = expression.args.get("concurrent") 2201 concurrent = " CONCURRENT" if concurrent else "" 2202 target = self.sql(expression, "target") 2203 target = f" {target}" if target else "" 2204 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2205 2206 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2207 if isinstance(expression.this, list): 2208 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2209 if expression.this: 2210 modulus = self.sql(expression, "this") 2211 remainder = self.sql(expression, "expression") 2212 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2213 2214 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2215 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2216 return f"FROM ({from_expressions}) TO ({to_expressions})" 2217 2218 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2219 this = self.sql(expression, "this") 2220 2221 for_values_or_default = expression.expression 2222 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2223 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2224 else: 2225 for_values_or_default = " DEFAULT" 2226 2227 return f"PARTITION OF {this}{for_values_or_default}" 2228 2229 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2230 kind = expression.args.get("kind") 2231 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2232 for_or_in = expression.args.get("for_or_in") 2233 for_or_in = f" {for_or_in}" if for_or_in else "" 2234 lock_type = expression.args.get("lock_type") 2235 override = " OVERRIDE" if expression.args.get("override") else "" 2236 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2237 2238 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2239 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2240 statistics = expression.args.get("statistics") 2241 statistics_sql = "" 2242 if statistics is not None: 2243 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2244 return f"{data_sql}{statistics_sql}" 2245 2246 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2247 this = self.sql(expression, "this") 2248 this = f"HISTORY_TABLE={this}" if this else "" 2249 data_consistency: str | None = self.sql(expression, "data_consistency") 2250 data_consistency = ( 2251 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2252 ) 2253 retention_period: str | None = self.sql(expression, "retention_period") 2254 retention_period = ( 2255 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2256 ) 2257 2258 if this: 2259 on_sql = self.func("ON", this, data_consistency, retention_period) 2260 else: 2261 on_sql = "ON" if expression.args.get("on") else "OFF" 2262 2263 sql = f"SYSTEM_VERSIONING={on_sql}" 2264 2265 return f"WITH({sql})" if expression.args.get("with_") else sql 2266 2267 def insert_sql(self, expression: exp.Insert) -> str: 2268 hint = self.sql(expression, "hint") 2269 overwrite = expression.args.get("overwrite") 2270 2271 if isinstance(expression.this, exp.Directory): 2272 this = " OVERWRITE" if overwrite else " INTO" 2273 else: 2274 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2275 2276 stored = self.sql(expression, "stored") 2277 stored = f" {stored}" if stored else "" 2278 alternative = expression.args.get("alternative") 2279 alternative = f" OR {alternative}" if alternative else "" 2280 ignore = " IGNORE" if expression.args.get("ignore") else "" 2281 is_function = expression.args.get("is_function") 2282 if is_function: 2283 this = f"{this} FUNCTION" 2284 this = f"{this} {self.sql(expression, 'this')}" 2285 2286 exists = " IF EXISTS" if expression.args.get("exists") else "" 2287 where = self.sql(expression, "where") 2288 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2289 using = self.expressions(expression, key="using", flat=True) 2290 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2291 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2292 on_conflict = self.sql(expression, "conflict") 2293 on_conflict = f" {on_conflict}" if on_conflict else "" 2294 by_name = " BY NAME" if expression.args.get("by_name") else "" 2295 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2296 returning = self.sql(expression, "returning") 2297 2298 if self.RETURNING_END: 2299 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2300 else: 2301 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2302 2303 partition_by = self.sql(expression, "partition") 2304 partition_by = f" {partition_by}" if partition_by else "" 2305 settings = self.sql(expression, "settings") 2306 settings = f" {settings}" if settings else "" 2307 2308 source = self.sql(expression, "source") 2309 source = f"TABLE {source}" if source else "" 2310 2311 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2312 return self.prepend_ctes(expression, sql) 2313 2314 def introducer_sql(self, expression: exp.Introducer) -> str: 2315 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2316 2317 def kill_sql(self, expression: exp.Kill) -> str: 2318 kind = self.sql(expression, "kind") 2319 kind = f" {kind}" if kind else "" 2320 this = self.sql(expression, "this") 2321 this = f" {this}" if this else "" 2322 return f"KILL{kind}{this}" 2323 2324 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2325 return expression.name 2326 2327 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2328 return expression.name 2329 2330 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2331 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2332 2333 constraint = self.sql(expression, "constraint") 2334 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2335 2336 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2337 if conflict_keys: 2338 conflict_keys = f"({conflict_keys})" 2339 2340 index_predicate = self.sql(expression, "index_predicate") 2341 conflict_keys = f"{conflict_keys}{index_predicate} " 2342 2343 action = self.sql(expression, "action") 2344 2345 expressions = self.expressions(expression, flat=True) 2346 if expressions: 2347 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2348 expressions = f" {set_keyword}{expressions}" 2349 2350 where = self.sql(expression, "where") 2351 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2352 2353 def returning_sql(self, expression: exp.Returning) -> str: 2354 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2355 2356 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2357 fields = self.sql(expression, "fields") 2358 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2359 escaped = self.sql(expression, "escaped") 2360 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2361 items = self.sql(expression, "collection_items") 2362 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2363 keys = self.sql(expression, "map_keys") 2364 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2365 lines = self.sql(expression, "lines") 2366 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2367 null = self.sql(expression, "null") 2368 null = f" NULL DEFINED AS {null}" if null else "" 2369 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2370 2371 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2372 return f"WITH ({self.expressions(expression, flat=True)})" 2373 2374 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2375 this = f"{self.sql(expression, 'this')} INDEX" 2376 target = self.sql(expression, "target") 2377 target = f" FOR {target}" if target else "" 2378 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2379 2380 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2381 this = self.sql(expression, "this") 2382 kind = self.sql(expression, "kind") 2383 expr = self.sql(expression, "expression") 2384 return f"{this} ({kind} => {expr})" 2385 2386 def table_parts(self, expression: exp.Table) -> str: 2387 return ".".join( 2388 self.sql(part) 2389 for part in ( 2390 expression.args.get("catalog"), 2391 expression.args.get("db"), 2392 expression.args.get("this"), 2393 ) 2394 if part is not None 2395 ) 2396 2397 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2398 table = self.table_parts(expression) 2399 only = "ONLY " if expression.args.get("only") else "" 2400 partition = self.sql(expression, "partition") 2401 partition = f" {partition}" if partition else "" 2402 version = self.sql(expression, "version") 2403 version = f" {version}" if version else "" 2404 alias = self.sql(expression, "alias") 2405 alias = f"{sep}{alias}" if alias else "" 2406 2407 sample = self.sql(expression, "sample") 2408 post_alias = "" 2409 pre_alias = "" 2410 2411 if self.dialect.ALIAS_POST_TABLESAMPLE: 2412 pre_alias = sample 2413 else: 2414 post_alias = sample 2415 2416 if self.dialect.ALIAS_POST_VERSION: 2417 pre_alias = f"{pre_alias}{version}" 2418 else: 2419 post_alias = f"{post_alias}{version}" 2420 2421 hints = self.expressions(expression, key="hints", sep=" ") 2422 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2423 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2424 joins = self.indent( 2425 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2426 ) 2427 laterals = self.expressions(expression, key="laterals", sep="") 2428 2429 file_format = self.sql(expression, "format") 2430 pattern = self.sql(expression, "pattern") 2431 if file_format: 2432 pattern = f", PATTERN => {pattern}" if pattern else "" 2433 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2434 elif pattern: 2435 file_format = f" (PATTERN => {pattern})" 2436 2437 ordinality = expression.args.get("ordinality") or "" 2438 if ordinality: 2439 ordinality = f" WITH ORDINALITY{alias}" 2440 alias = "" 2441 2442 when = self.sql(expression, "when") 2443 if when: 2444 if self.HISTORICAL_DATA_POST_ALIAS: 2445 alias = f"{alias} {when}" 2446 else: 2447 table = f"{table} {when}" 2448 2449 changes = self.sql(expression, "changes") 2450 changes = f" {changes}" if changes else "" 2451 2452 rows_from = self.expressions(expression, key="rows_from") 2453 if rows_from: 2454 table = f"ROWS FROM {self.wrap(rows_from)}" 2455 2456 indexed = expression.args.get("indexed") 2457 if indexed is not None: 2458 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2459 else: 2460 indexed = "" 2461 2462 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2463 2464 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2465 table = self.func("TABLE", expression.this) 2466 alias = self.sql(expression, "alias") 2467 alias = f" AS {alias}" if alias else "" 2468 sample = self.sql(expression, "sample") 2469 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2470 joins = self.indent( 2471 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2472 ) 2473 return f"{table}{alias}{pivots}{sample}{joins}" 2474 2475 def tablesample_sql( 2476 self, 2477 expression: exp.TableSample, 2478 tablesample_keyword: str | None = None, 2479 ) -> str: 2480 method = self.sql(expression, "method") 2481 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2482 numerator = self.sql(expression, "bucket_numerator") 2483 denominator = self.sql(expression, "bucket_denominator") 2484 field = self.sql(expression, "bucket_field") 2485 field = f" ON {field}" if field else "" 2486 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2487 seed = self.sql(expression, "seed") 2488 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2489 2490 size = self.sql(expression, "size") 2491 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2492 size = f"{size} ROWS" 2493 2494 percent = self.sql(expression, "percent") 2495 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2496 percent = f"{percent} PERCENT" 2497 2498 expr = f"{bucket}{percent}{size}" 2499 if self.TABLESAMPLE_REQUIRES_PARENS: 2500 expr = f"({expr})" 2501 2502 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2503 2504 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2505 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2506 # the stored column name differs from the target dialect's natural output. 2507 columns = expression.args.get("columns") 2508 if not columns or len(expression.fields) != 1: 2509 return None 2510 2511 args = expression.args 2512 parser_cls = self.dialect.parser_class 2513 2514 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2515 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2516 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2517 2518 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2519 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2520 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2521 2522 if ( 2523 src_identify_pivot_strings == tgt_identify_pivot_strings 2524 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2525 and src_pivot_column_naming == tgt_pivot_column_naming 2526 ): 2527 return None 2528 2529 in_exprs = expression.fields[0].expressions 2530 step = len(columns) // len(in_exprs) 2531 2532 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2533 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2534 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2535 first_stored = columns[0].name 2536 2537 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2538 if not first_stored.startswith(first_base): 2539 return None 2540 2541 suffix = first_stored[len(first_base) :] 2542 2543 # Whether the target dialect would append an agg-name suffix for this pivot. 2544 # Spark single-agg uniquely drops the agg alias entirely. 2545 target_has_suffix = ( 2546 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2547 ) and any(a.alias for a in expression.expressions) 2548 source_has_suffix = suffix != "" 2549 2550 new_exprs: list[exp.Expression] = [] 2551 modified = False 2552 for val_idx, e in enumerate(in_exprs): 2553 if isinstance(e, exp.PivotAlias): 2554 new_exprs.append(e) 2555 continue 2556 2557 i = val_idx * step 2558 stored_full = columns[i].name 2559 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2560 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2561 2562 # Source had a suffix, but target won't apply one 2563 if source_has_suffix and not target_has_suffix: 2564 new_exprs.append( 2565 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2566 ) 2567 modified = True 2568 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2569 elif stored_value != target_value: 2570 new_exprs.append( 2571 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2572 ) 2573 modified = True 2574 else: 2575 new_exprs.append(e) 2576 2577 return new_exprs if modified else None 2578 2579 def pivot_sql(self, expression: exp.Pivot) -> str: 2580 expressions = self.expressions(expression, flat=True) 2581 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2582 2583 group = self.sql(expression, "group") 2584 2585 if expression.this: 2586 this = self.sql(expression, "this") 2587 if not expressions: 2588 sql = f"UNPIVOT {this}" 2589 else: 2590 on = f"{self.seg('ON')} {expressions}" 2591 into = self.sql(expression, "into") 2592 into = f"{self.seg('INTO')} {into}" if into else "" 2593 using = self.expressions(expression, key="using", flat=True) 2594 using = f"{self.seg('USING')} {using}" if using else "" 2595 sql = f"{direction} {this}{on}{into}{using}{group}" 2596 return self.prepend_ctes(expression, sql) 2597 2598 if not expression.unpivot: 2599 # Wrap IN-list values with explicit aliases where the target dialect would differ 2600 new_field_exprs = self._pivot_in_value_aliases(expression) 2601 if new_field_exprs is not None: 2602 expression.fields[0].set("expressions", new_field_exprs) 2603 2604 alias = self.sql(expression, "alias") 2605 if alias: 2606 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2607 2608 fields = self.expressions( 2609 expression, 2610 "fields", 2611 sep=" ", 2612 dynamic=True, 2613 new_line=True, 2614 skip_first=True, 2615 skip_last=True, 2616 ) 2617 2618 include_nulls = expression.args.get("include_nulls") 2619 if include_nulls is not None: 2620 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2621 else: 2622 nulls = "" 2623 2624 default_on_null = self.sql(expression, "default_on_null") 2625 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2626 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2627 return self.prepend_ctes(expression, sql) 2628 2629 def version_sql(self, expression: exp.Version) -> str: 2630 this = f"FOR {expression.name}" 2631 kind = expression.text("kind") 2632 expr = self.sql(expression, "expression") 2633 return f"{this} {kind} {expr}" 2634 2635 def tuple_sql(self, expression: exp.Tuple) -> str: 2636 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2637 2638 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2639 """ 2640 Returns (join_sql, from_sql) for UPDATE statements. 2641 - join_sql: placed after UPDATE table, before SET 2642 - from_sql: placed after SET clause (standard position) 2643 Dialects like MySQL need to convert FROM to JOIN syntax. 2644 """ 2645 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2646 return ("", self.sql(expression, "from_")) 2647 2648 # Qualify unqualified columns in SET clause with the target table 2649 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2650 target_table = expression.this 2651 if isinstance(target_table, exp.Table): 2652 target_name = exp.to_identifier(target_table.alias_or_name) 2653 for eq in expression.expressions: 2654 col = eq.this 2655 if isinstance(col, exp.Column) and not col.table: 2656 col.set("table", target_name) 2657 2658 table = from_expr.this 2659 if nested_joins := table.args.get("joins", []): 2660 table.set("joins", None) 2661 2662 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2663 for nested in nested_joins: 2664 if not nested.args.get("on") and not nested.args.get("using"): 2665 nested.set("on", exp.true()) 2666 join_sql += self.sql(nested) 2667 2668 return (join_sql, "") 2669 2670 def update_sql(self, expression: exp.Update) -> str: 2671 hint = self.sql(expression, "hint") 2672 this = self.sql(expression, "this") 2673 join_sql, from_sql = self._update_from_joins_sql(expression) 2674 set_sql = self.expressions(expression, flat=True) 2675 where_sql = self.sql(expression, "where") 2676 returning = self.sql(expression, "returning") 2677 order = self.sql(expression, "order") 2678 limit = self.sql(expression, "limit") 2679 if self.RETURNING_END: 2680 expression_sql = f"{from_sql}{where_sql}{returning}" 2681 else: 2682 expression_sql = f"{returning}{from_sql}{where_sql}" 2683 options = self.expressions(expression, key="options") 2684 options = f" OPTION({options})" if options else "" 2685 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2686 return self.prepend_ctes(expression, sql) 2687 2688 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2689 values_as_table = values_as_table and self.VALUES_AS_TABLE 2690 2691 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2692 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2693 args = self.expressions(expression) 2694 alias = self.sql(expression, "alias") 2695 values = f"VALUES{self.seg('')}{args}" 2696 values = ( 2697 f"({values})" 2698 if self.WRAP_DERIVED_VALUES 2699 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2700 else values 2701 ) 2702 values = self.query_modifiers(expression, values) 2703 return f"{values} AS {alias}" if alias else values 2704 2705 # Converts `VALUES...` expression into a series of select unions. 2706 alias_node = expression.args.get("alias") 2707 column_names = alias_node and alias_node.columns 2708 2709 selects: list[exp.Query] = [] 2710 2711 for i, tup in enumerate(expression.expressions): 2712 row = tup.expressions 2713 2714 if i == 0 and column_names: 2715 row = [ 2716 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2717 ] 2718 2719 selects.append(exp.Select(expressions=row)) 2720 2721 if self.pretty: 2722 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2723 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2724 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2725 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2726 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2727 2728 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2729 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2730 return f"({unions}){alias}" 2731 2732 def var_sql(self, expression: exp.Var) -> str: 2733 return self.sql(expression, "this") 2734 2735 @unsupported_args("expressions") 2736 def into_sql(self, expression: exp.Into) -> str: 2737 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2738 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2739 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2740 2741 def from_sql(self, expression: exp.From) -> str: 2742 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2743 2744 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2745 grouping_sets = self.expressions(expression, indent=False) 2746 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2747 2748 def rollup_sql(self, expression: exp.Rollup) -> str: 2749 expressions = self.expressions(expression, indent=False) 2750 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2751 2752 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2753 this = self.sql(expression, "this") 2754 2755 columns = self.expressions(expression, flat=True) 2756 2757 from_sql = self.sql(expression, "from_index") 2758 from_sql = f" FROM {from_sql}" if from_sql else "" 2759 2760 properties = expression.args.get("properties") 2761 properties_sql = ( 2762 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2763 ) 2764 2765 return f"{this}({columns}){from_sql}{properties_sql}" 2766 2767 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2768 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2769 2770 def cube_sql(self, expression: exp.Cube) -> str: 2771 expressions = self.expressions(expression, indent=False) 2772 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2773 2774 def group_sql(self, expression: exp.Group) -> str: 2775 group_by_all = expression.args.get("all") 2776 if group_by_all is True: 2777 modifier = " ALL" 2778 elif group_by_all is False: 2779 modifier = " DISTINCT" 2780 else: 2781 modifier = "" 2782 2783 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2784 2785 grouping_sets = self.expressions(expression, key="grouping_sets") 2786 cube = self.expressions(expression, key="cube") 2787 rollup = self.expressions(expression, key="rollup") 2788 2789 groupings = csv( 2790 self.seg(grouping_sets) if grouping_sets else "", 2791 self.seg(cube) if cube else "", 2792 self.seg(rollup) if rollup else "", 2793 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2794 sep=self.GROUPINGS_SEP, 2795 ) 2796 2797 if ( 2798 expression.expressions 2799 and groupings 2800 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2801 ): 2802 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2803 2804 return f"{group_by}{groupings}" 2805 2806 def having_sql(self, expression: exp.Having) -> str: 2807 this = self.indent(self.sql(expression, "this")) 2808 return f"{self.seg('HAVING')}{self.sep()}{this}" 2809 2810 def connect_sql(self, expression: exp.Connect) -> str: 2811 start = self.sql(expression, "start") 2812 start = self.seg(f"START WITH {start}") if start else "" 2813 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2814 connect = self.sql(expression, "connect") 2815 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2816 return start + connect 2817 2818 def prior_sql(self, expression: exp.Prior) -> str: 2819 return f"PRIOR {self.sql(expression, 'this')}" 2820 2821 def join_sql(self, expression: exp.Join) -> str: 2822 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2823 side = None 2824 else: 2825 side = expression.side 2826 2827 op_sql = " ".join( 2828 op 2829 for op in ( 2830 expression.method, 2831 "GLOBAL" if expression.args.get("global_") else None, 2832 side, 2833 expression.kind, 2834 expression.hint if self.JOIN_HINTS else None, 2835 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2836 ) 2837 if op 2838 ) 2839 match_cond = self.sql(expression, "match_condition") 2840 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2841 on_sql = self.sql(expression, "on") 2842 using = expression.args.get("using") 2843 2844 if not on_sql and using: 2845 on_sql = csv(*(self.sql(column) for column in using)) 2846 2847 this = expression.this 2848 this_sql = self.sql(this) 2849 2850 exprs = self.expressions(expression) 2851 if exprs: 2852 this_sql = f"{this_sql},{self.seg(exprs)}" 2853 2854 if on_sql: 2855 on_sql = self.indent(on_sql, skip_first=True) 2856 space = self.seg(" " * self.pad) if self.pretty else " " 2857 if using: 2858 on_sql = f"{space}USING ({on_sql})" 2859 else: 2860 on_sql = f"{space}ON {on_sql}" 2861 elif not op_sql: 2862 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2863 return f" {this_sql}" 2864 2865 return f", {this_sql}" 2866 2867 if op_sql != "STRAIGHT_JOIN": 2868 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2869 2870 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2871 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2872 2873 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2874 args = self.expressions(expression, flat=True) 2875 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2876 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2877 2878 def lateral_op(self, expression: exp.Lateral) -> str: 2879 cross_apply = expression.args.get("cross_apply") 2880 2881 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2882 if cross_apply is True: 2883 op = "INNER JOIN " 2884 elif cross_apply is False: 2885 op = "LEFT JOIN " 2886 else: 2887 op = "" 2888 2889 return f"{op}LATERAL" 2890 2891 def lateral_sql(self, expression: exp.Lateral) -> str: 2892 this = self.sql(expression, "this") 2893 2894 if expression.args.get("view"): 2895 alias = expression.args["alias"] 2896 columns = self.expressions(alias, key="columns", flat=True) 2897 table = f" {alias.name}" if alias.name else "" 2898 columns = f" AS {columns}" if columns else "" 2899 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2900 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2901 2902 alias = self.sql(expression, "alias") 2903 alias = f" AS {alias}" if alias else "" 2904 2905 ordinality = expression.args.get("ordinality") or "" 2906 if ordinality: 2907 ordinality = f" WITH ORDINALITY{alias}" 2908 alias = "" 2909 2910 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2911 2912 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2913 this = self.sql(expression, "this") 2914 2915 args = [ 2916 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2917 for e in (expression.args.get(k) for k in ("offset", "expression")) 2918 if e 2919 ] 2920 2921 args_sql = ", ".join(self.sql(e) for e in args) 2922 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2923 expressions = self.expressions(expression, flat=True) 2924 limit_options = self.sql(expression, "limit_options") 2925 expressions = f" BY {expressions}" if expressions else "" 2926 2927 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2928 2929 def offset_sql(self, expression: exp.Offset) -> str: 2930 this = self.sql(expression, "this") 2931 value = expression.expression 2932 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2933 expressions = self.expressions(expression, flat=True) 2934 expressions = f" BY {expressions}" if expressions else "" 2935 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2936 2937 def setitem_sql(self, expression: exp.SetItem) -> str: 2938 kind = self.sql(expression, "kind") 2939 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2940 kind = "" 2941 else: 2942 kind = f"{kind} " if kind else "" 2943 this = self.sql(expression, "this") 2944 expressions = self.expressions(expression) 2945 collate = self.sql(expression, "collate") 2946 collate = f" COLLATE {collate}" if collate else "" 2947 global_ = "GLOBAL " if expression.args.get("global_") else "" 2948 return f"{global_}{kind}{this}{expressions}{collate}" 2949 2950 def set_sql(self, expression: exp.Set) -> str: 2951 expressions = f" {self.expressions(expression, flat=True)}" 2952 tag = " TAG" if expression.args.get("tag") else "" 2953 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2954 2955 def queryband_sql(self, expression: exp.QueryBand) -> str: 2956 this = self.sql(expression, "this") 2957 update = " UPDATE" if expression.args.get("update") else "" 2958 scope = self.sql(expression, "scope") 2959 scope = f" FOR {scope}" if scope else "" 2960 2961 return f"QUERY_BAND = {this}{update}{scope}" 2962 2963 def pragma_sql(self, expression: exp.Pragma) -> str: 2964 return f"PRAGMA {self.sql(expression, 'this')}" 2965 2966 def lock_sql(self, expression: exp.Lock) -> str: 2967 if not self.LOCKING_READS_SUPPORTED: 2968 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2969 return "" 2970 2971 update = expression.args["update"] 2972 key = expression.args.get("key") 2973 if update: 2974 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2975 else: 2976 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2977 expressions = self.expressions(expression, flat=True) 2978 expressions = f" OF {expressions}" if expressions else "" 2979 wait = expression.args.get("wait") 2980 2981 if wait is not None: 2982 if isinstance(wait, exp.Literal): 2983 wait = f" WAIT {self.sql(wait)}" 2984 else: 2985 wait = " NOWAIT" if wait else " SKIP LOCKED" 2986 2987 return f"{lock_type}{expressions}{wait or ''}" 2988 2989 def literal_sql(self, expression: exp.Literal) -> str: 2990 text = expression.this or "" 2991 if expression.is_string: 2992 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2993 return text 2994 2995 def escape_str( 2996 self, 2997 text: str, 2998 escape_backslash: bool = True, 2999 delimiter: str | None = None, 3000 escaped_delimiter: str | None = None, 3001 is_byte_string: bool = False, 3002 ) -> str: 3003 if is_byte_string: 3004 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3005 else: 3006 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3007 3008 if supports_escape_sequences: 3009 text = "".join( 3010 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3011 for ch in text 3012 ) 3013 3014 delimiter = delimiter or self.dialect.QUOTE_END 3015 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3016 3017 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3018 3019 def loaddata_sql(self, expression: exp.LoadData) -> str: 3020 is_overwrite = expression.args.get("overwrite") 3021 overwrite = " OVERWRITE" if is_overwrite else "" 3022 this = self.sql(expression, "this") 3023 3024 files = expression.args.get("files") 3025 if files: 3026 files_sql = self.expressions(files, flat=True) 3027 files_sql = f"FILES{self.wrap(files_sql)}" 3028 if is_overwrite: 3029 this = f" {this}" 3030 elif expression.args.get("temp"): 3031 this = f" INTO TEMP TABLE {this}" 3032 else: 3033 this = f" INTO TABLE {this}" 3034 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3035 3036 local = " LOCAL" if expression.args.get("local") else "" 3037 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3038 this = f" INTO TABLE {this}" 3039 partition = self.sql(expression, "partition") 3040 partition = f" {partition}" if partition else "" 3041 input_format = self.sql(expression, "input_format") 3042 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3043 serde = self.sql(expression, "serde") 3044 serde = f" SERDE {serde}" if serde else "" 3045 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3046 3047 def null_sql(self, *_) -> str: 3048 return "NULL" 3049 3050 def boolean_sql(self, expression: exp.Boolean) -> str: 3051 return "TRUE" if expression.this else "FALSE" 3052 3053 def booland_sql(self, expression: exp.Booland) -> str: 3054 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3055 3056 def boolor_sql(self, expression: exp.Boolor) -> str: 3057 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3058 3059 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3060 this = self.sql(expression, "this") 3061 this = f"{this} " if this else this 3062 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3063 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3064 3065 def withfill_sql(self, expression: exp.WithFill) -> str: 3066 from_sql = self.sql(expression, "from_") 3067 from_sql = f" FROM {from_sql}" if from_sql else "" 3068 to_sql = self.sql(expression, "to") 3069 to_sql = f" TO {to_sql}" if to_sql else "" 3070 step_sql = self.sql(expression, "step") 3071 step_sql = f" STEP {step_sql}" if step_sql else "" 3072 interpolated_values = [ 3073 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3074 if isinstance(e, exp.Alias) 3075 else self.sql(e, "this") 3076 for e in expression.args.get("interpolate") or [] 3077 ] 3078 interpolate = ( 3079 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3080 ) 3081 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3082 3083 def cluster_sql(self, expression: exp.Cluster) -> str: 3084 return self.op_expressions("CLUSTER BY", expression) 3085 3086 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3087 if expression.this: 3088 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3089 return "" 3090 expressions = self.expressions(expression, flat=True) 3091 return f"CLUSTER BY ({expressions})" 3092 3093 def distribute_sql(self, expression: exp.Distribute) -> str: 3094 return self.op_expressions("DISTRIBUTE BY", expression) 3095 3096 def sort_sql(self, expression: exp.Sort) -> str: 3097 return self.op_expressions("SORT BY", expression) 3098 3099 def _resolve_ordered_for_null_ordering_simulation( 3100 self, expression: exp.Ordered 3101 ) -> exp.Expr | None: 3102 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3103 3104 Returns the underlying expression of the uniquely-matching projection 3105 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3106 simulation, since the CASE is evaluated in FROM-clause scope rather 3107 than alias scope (MySQL error 1052). Returns None if no safe 3108 substitution applies, leaving the original behaviour unchanged. 3109 """ 3110 this = expression.this 3111 if not (isinstance(this, exp.Column) and not this.table): 3112 return None 3113 3114 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3115 if not isinstance(ancestor, exp.Select): 3116 return None 3117 3118 column_name = this.name 3119 matched: list[exp.Expr] = [ 3120 p.this if isinstance(p, exp.Alias) else p 3121 for p in ancestor.selects 3122 if p.output_name == column_name 3123 ] 3124 match = matched[0] if len(matched) == 1 else None 3125 3126 # Skip the substitution when it would be identical to the existing 3127 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3128 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3129 return None 3130 3131 return match 3132 3133 def ordered_sql(self, expression: exp.Ordered) -> str: 3134 desc = expression.args.get("desc") 3135 asc = not desc 3136 3137 nulls_first = expression.args.get("nulls_first") 3138 nulls_last = not nulls_first 3139 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3140 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3141 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3142 3143 this = self.sql(expression, "this") 3144 3145 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3146 nulls_sort_change = "" 3147 if nulls_first and ( 3148 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3149 ): 3150 nulls_sort_change = " NULLS FIRST" 3151 elif ( 3152 nulls_last 3153 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3154 and not nulls_are_last 3155 ): 3156 nulls_sort_change = " NULLS LAST" 3157 3158 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3159 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3160 window = expression.find_ancestor(exp.Window, exp.Select) 3161 3162 if isinstance(window, exp.Window): 3163 window_this = window.this 3164 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3165 window_this = window_this.this 3166 spec = window.args.get("spec") 3167 else: 3168 window_this = None 3169 spec = None 3170 3171 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3172 # without a spec or with a ROWS spec, but not with RANGE 3173 if not ( 3174 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3175 and (not spec or spec.text("kind").upper() == "ROWS") 3176 ): 3177 if window_this and spec: 3178 self.unsupported( 3179 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3180 ) 3181 nulls_sort_change = "" 3182 elif self.NULL_ORDERING_SUPPORTED is False and ( 3183 (asc and nulls_sort_change == " NULLS LAST") 3184 or (desc and nulls_sort_change == " NULLS FIRST") 3185 ): 3186 # BigQuery does not allow these ordering/nulls combinations when used under 3187 # an aggregation func or under a window containing one 3188 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3189 3190 if isinstance(ancestor, exp.Window): 3191 ancestor = ancestor.this 3192 if isinstance(ancestor, exp.AggFunc): 3193 self.unsupported( 3194 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3195 ) 3196 nulls_sort_change = "" 3197 elif self.NULL_ORDERING_SUPPORTED is None: 3198 if expression.this.is_int: 3199 self.unsupported( 3200 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3201 ) 3202 elif not isinstance(expression.this, exp.Rand): 3203 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3204 target = self.sql(resolved) if resolved is not None else this 3205 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3206 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3207 nulls_sort_change = "" 3208 3209 with_fill = self.sql(expression, "with_fill") 3210 with_fill = f" {with_fill}" if with_fill else "" 3211 3212 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3213 3214 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3215 window_frame = self.sql(expression, "window_frame") 3216 window_frame = f"{window_frame} " if window_frame else "" 3217 3218 this = self.sql(expression, "this") 3219 3220 return f"{window_frame}{this}" 3221 3222 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3223 partition = self.partition_by_sql(expression) 3224 order = self.sql(expression, "order") 3225 measures = self.expressions(expression, key="measures") 3226 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3227 rows = self.sql(expression, "rows") 3228 rows = self.seg(rows) if rows else "" 3229 after = self.sql(expression, "after") 3230 after = self.seg(after) if after else "" 3231 pattern = self.sql(expression, "pattern") 3232 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3233 definition_sqls = [ 3234 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3235 for definition in expression.args.get("define", []) 3236 ] 3237 definitions = self.expressions(sqls=definition_sqls) 3238 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3239 body = "".join( 3240 ( 3241 partition, 3242 order, 3243 measures, 3244 rows, 3245 after, 3246 pattern, 3247 define, 3248 ) 3249 ) 3250 alias = self.sql(expression, "alias") 3251 alias = f" {alias}" if alias else "" 3252 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3253 3254 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3255 limit = expression.args.get("limit") 3256 3257 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3258 count = limit.args.get("count") 3259 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3260 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3261 limit = exp.Limit( 3262 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3263 ) 3264 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3265 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3266 3267 return csv( 3268 *sqls, 3269 *[self.sql(join) for join in expression.args.get("joins") or []], 3270 self.sql(expression, "match"), 3271 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3272 self.sql(expression, "prewhere"), 3273 self.sql(expression, "where"), 3274 self.sql(expression, "connect"), 3275 self.sql(expression, "group"), 3276 self.sql(expression, "having"), 3277 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3278 self.sql(expression, "order"), 3279 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3280 *self.after_limit_modifiers(expression), 3281 self.options_modifier(expression), 3282 self.sql(expression, "for_"), 3283 sep="", 3284 ) 3285 3286 def options_modifier(self, expression: exp.Expr) -> str: 3287 options = self.expressions(expression, key="options") 3288 return f" {options}" if options else "" 3289 3290 def forclause_sql(self, expression: exp.ForClause) -> str: 3291 kind = expression.args["kind"] 3292 if kind == "BROWSE": 3293 return f"{self.sep()}FOR BROWSE" 3294 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3295 # the target dialect doesn't support QueryOption, so we drop the clause. 3296 options = self.expressions(expression, key="expressions") 3297 if not options: 3298 return "" 3299 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3300 3301 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3302 self.unsupported("Unsupported query option.") 3303 return "" 3304 3305 def offset_limit_modifiers( 3306 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3307 ) -> list[str]: 3308 return [ 3309 self.sql(expression, "offset") if fetch else self.sql(limit), 3310 self.sql(limit) if fetch else self.sql(expression, "offset"), 3311 ] 3312 3313 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3314 locks = self.expressions(expression, key="locks", sep=" ") 3315 locks = f" {locks}" if locks else "" 3316 return [locks, self.sql(expression, "sample")] 3317 3318 def select_sql(self, expression: exp.Select) -> str: 3319 into = expression.args.get("into") 3320 if not self.SUPPORTS_SELECT_INTO and into: 3321 into.pop() 3322 3323 hint = self.sql(expression, "hint") 3324 distinct = self.sql(expression, "distinct") 3325 distinct = f" {distinct}" if distinct else "" 3326 kind = self.sql(expression, "kind") 3327 3328 limit = expression.args.get("limit") 3329 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3330 top = self.limit_sql(limit, top=True) 3331 limit.pop() 3332 else: 3333 top = "" 3334 3335 expressions = self.expressions(expression) 3336 3337 if kind: 3338 if kind in self.SELECT_KINDS: 3339 kind = f" AS {kind}" 3340 else: 3341 if kind == "STRUCT": 3342 expressions = self.expressions( 3343 sqls=[ 3344 self.sql( 3345 exp.Struct( 3346 expressions=[ 3347 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3348 if isinstance(e, exp.Alias) 3349 else e 3350 for e in expression.expressions 3351 ] 3352 ) 3353 ) 3354 ] 3355 ) 3356 kind = "" 3357 3358 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3359 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3360 3361 exclude = expression.args.get("exclude") 3362 3363 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3364 exclude_sql = self.expressions(sqls=exclude, flat=True) 3365 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3366 3367 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3368 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3369 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3370 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3371 sql = self.query_modifiers( 3372 expression, 3373 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3374 self.sql(expression, "into", comment=False), 3375 self.sql(expression, "from_", comment=False), 3376 ) 3377 3378 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3379 if expression.args.get("with_"): 3380 sql = self.maybe_comment(sql, expression) 3381 expression.pop_comments() 3382 3383 sql = self.prepend_ctes(expression, sql) 3384 3385 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3386 expression.set("exclude", None) 3387 subquery = expression.subquery(copy=False) 3388 star = exp.Star(except_=exclude) 3389 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3390 3391 if not self.SUPPORTS_SELECT_INTO and into: 3392 if into.args.get("temporary"): 3393 table_kind = " TEMPORARY" 3394 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3395 table_kind = " UNLOGGED" 3396 else: 3397 table_kind = "" 3398 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3399 3400 return sql 3401 3402 def schema_sql(self, expression: exp.Schema) -> str: 3403 this = self.sql(expression, "this") 3404 sql = self.schema_columns_sql(expression) 3405 return f"{this} {sql}" if this and sql else this or sql 3406 3407 def schema_columns_sql(self, expression: exp.Expr) -> str: 3408 if expression.expressions: 3409 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3410 return "" 3411 3412 def star_sql(self, expression: exp.Star) -> str: 3413 except_ = self.expressions(expression, key="except_", flat=True) 3414 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3415 replace = self.expressions(expression, key="replace", flat=True) 3416 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3417 rename = self.expressions(expression, key="rename", flat=True) 3418 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3419 ilike = self.sql(expression, "ilike") 3420 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3421 return f"*{ilike}{except_}{replace}{rename}" 3422 3423 def parameter_sql(self, expression: exp.Parameter) -> str: 3424 this = self.sql(expression, "this") 3425 return f"{self.PARAMETER_TOKEN}{this}" 3426 3427 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3428 this = self.sql(expression, "this") 3429 kind = expression.text("kind") 3430 if kind: 3431 kind = f"{kind}." 3432 return f"@@{kind}{this}" 3433 3434 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3435 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3436 3437 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3438 alias = self.sql(expression, "alias") 3439 alias = f"{sep}{alias}" if alias else "" 3440 sample = self.sql(expression, "sample") 3441 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3442 alias = f"{sample}{alias}" 3443 3444 # Set to None so it's not generated again by self.query_modifiers() 3445 expression.set("sample", None) 3446 3447 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3448 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3449 return self.prepend_ctes(expression, sql) 3450 3451 def qualify_sql(self, expression: exp.Qualify) -> str: 3452 this = self.indent(self.sql(expression, "this")) 3453 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3454 3455 def unnest_sql(self, expression: exp.Unnest) -> str: 3456 args = self.expressions(expression, flat=True) 3457 3458 alias = expression.args.get("alias") 3459 offset = expression.args.get("offset") 3460 3461 if self.UNNEST_WITH_ORDINALITY: 3462 if alias and isinstance(offset, exp.Expr): 3463 alias.append("columns", offset) 3464 expression.set("offset", None) 3465 3466 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3467 columns = alias.columns 3468 alias = self.sql(columns[0]) if columns else "" 3469 else: 3470 alias = self.sql(alias) 3471 3472 alias = f" AS {alias}" if alias else alias 3473 if self.UNNEST_WITH_ORDINALITY: 3474 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3475 else: 3476 if isinstance(offset, exp.Expr): 3477 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3478 elif offset: 3479 suffix = f"{alias} WITH OFFSET" 3480 else: 3481 suffix = alias 3482 3483 return f"UNNEST({args}){suffix}" 3484 3485 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3486 return "" 3487 3488 def where_sql(self, expression: exp.Where) -> str: 3489 this = self.indent(self.sql(expression, "this")) 3490 return f"{self.seg('WHERE')}{self.sep()}{this}" 3491 3492 def window_sql(self, expression: exp.Window) -> str: 3493 this = self.sql(expression, "this") 3494 partition = self.partition_by_sql(expression) 3495 order = expression.args.get("order") 3496 order = self.order_sql(order, flat=True) if order else "" 3497 spec = self.sql(expression, "spec") 3498 alias = self.sql(expression, "alias") 3499 over = self.sql(expression, "over") or "OVER" 3500 3501 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3502 3503 first = expression.args.get("first") 3504 if first is None: 3505 first = "" 3506 else: 3507 first = "FIRST" if first else "LAST" 3508 3509 if not partition and not order and not spec and alias: 3510 return f"{this} {alias}" 3511 3512 args = self.format_args( 3513 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3514 ) 3515 return f"{this} ({args})" 3516 3517 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3518 partition = self.expressions(expression, key="partition_by", flat=True) 3519 return f"PARTITION BY {partition}" if partition else "" 3520 3521 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3522 kind = self.sql(expression, "kind") 3523 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3524 end = ( 3525 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3526 or "CURRENT ROW" 3527 ) 3528 3529 window_spec = f"{kind} BETWEEN {start} AND {end}" 3530 3531 exclude = self.sql(expression, "exclude") 3532 if exclude: 3533 if self.SUPPORTS_WINDOW_EXCLUDE: 3534 window_spec += f" EXCLUDE {exclude}" 3535 else: 3536 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3537 3538 return window_spec 3539 3540 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3541 this = self.sql(expression, "this") 3542 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3543 return f"{this} WITHIN GROUP ({expression_sql})" 3544 3545 def between_sql(self, expression: exp.Between) -> str: 3546 this = self.sql(expression, "this") 3547 low = self.sql(expression, "low") 3548 high = self.sql(expression, "high") 3549 symmetric = expression.args.get("symmetric") 3550 3551 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3552 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3553 3554 flag = ( 3555 " SYMMETRIC" 3556 if symmetric 3557 else " ASYMMETRIC" 3558 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3559 else "" # silently drop ASYMMETRIC – semantics identical 3560 ) 3561 return f"{this} BETWEEN{flag} {low} AND {high}" 3562 3563 def bracket_offset_expressions( 3564 self, expression: exp.Bracket, index_offset: int | None = None 3565 ) -> list[exp.Expr]: 3566 if expression.args.get("json_access"): 3567 return expression.expressions 3568 3569 return apply_index_offset( 3570 expression.this, 3571 expression.expressions, 3572 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3573 dialect=self.dialect, 3574 ) 3575 3576 def bracket_sql(self, expression: exp.Bracket) -> str: 3577 expressions = self.bracket_offset_expressions(expression) 3578 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3579 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3580 3581 def all_sql(self, expression: exp.All) -> str: 3582 this = self.sql(expression, "this") 3583 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3584 this = self.wrap(this) 3585 return f"ALL {this}" 3586 3587 def any_sql(self, expression: exp.Any) -> str: 3588 this = self.sql(expression, "this") 3589 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3590 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3591 this = self.wrap(this) 3592 return f"ANY{this}" 3593 return f"ANY {this}" 3594 3595 def exists_sql(self, expression: exp.Exists) -> str: 3596 return f"EXISTS{self.wrap(expression)}" 3597 3598 def case_sql(self, expression: exp.Case) -> str: 3599 this = self.sql(expression, "this") 3600 statements = [f"CASE {this}" if this else "CASE"] 3601 3602 for e in expression.args["ifs"]: 3603 statements.append(f"WHEN {self.sql(e, 'this')}") 3604 statements.append(f"THEN {self.sql(e, 'true')}") 3605 3606 default = self.sql(expression, "default") 3607 3608 if default: 3609 statements.append(f"ELSE {default}") 3610 3611 statements.append("END") 3612 3613 if self.pretty and self.too_wide(statements): 3614 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3615 3616 return " ".join(statements) 3617 3618 def constraint_sql(self, expression: exp.Constraint) -> str: 3619 this = self.sql(expression, "this") 3620 expressions = self.expressions(expression, flat=True) 3621 return f"CONSTRAINT {this} {expressions}" 3622 3623 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3624 order = expression.args.get("order") 3625 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3626 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3627 3628 def extract_sql(self, expression: exp.Extract) -> str: 3629 import sqlglot.dialects.dialect 3630 3631 this = ( 3632 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3633 if self.NORMALIZE_EXTRACT_DATE_PARTS 3634 else expression.this 3635 ) 3636 if self.EXTRACT_ALLOWS_QUOTES: 3637 this_sql = self.sql(this) 3638 elif isinstance(this, exp.WeekStart): 3639 this_sql = self.weekstart_name(this) 3640 else: 3641 this_sql = this.name 3642 expression_sql = self.sql(expression, "expression") 3643 3644 return f"EXTRACT({this_sql} FROM {expression_sql})" 3645 3646 def trim_sql(self, expression: exp.Trim) -> str: 3647 trim_type = self.sql(expression, "position") 3648 3649 if trim_type == "LEADING": 3650 func_name = "LTRIM" 3651 elif trim_type == "TRAILING": 3652 func_name = "RTRIM" 3653 else: 3654 func_name = "TRIM" 3655 3656 return self.func(func_name, expression.this, expression.expression) 3657 3658 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3659 args = expression.expressions 3660 if isinstance(expression, exp.ConcatWs): 3661 args = args[1:] # Skip the delimiter 3662 3663 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3664 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3665 3666 concat_coalesce = ( 3667 self.dialect.CONCAT_WS_COALESCE 3668 if isinstance(expression, exp.ConcatWs) 3669 else self.dialect.CONCAT_COALESCE 3670 ) 3671 3672 if not concat_coalesce and expression.args.get("coalesce"): 3673 3674 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3675 if not e.type: 3676 import sqlglot.optimizer.annotate_types 3677 3678 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3679 3680 if e.is_string or e.is_type(exp.DType.ARRAY): 3681 return e 3682 3683 return exp.func("coalesce", e, exp.Literal.string("")) 3684 3685 args = [_wrap_with_coalesce(e) for e in args] 3686 3687 return args 3688 3689 def concat_sql(self, expression: exp.Concat) -> str: 3690 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3691 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3692 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3693 # instead of coalescing them to empty string. 3694 import sqlglot.dialects.dialect 3695 3696 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3697 3698 expressions = self.convert_concat_args(expression) 3699 3700 # Some dialects don't allow a single-argument CONCAT call 3701 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3702 return self.sql(expressions[0]) 3703 3704 return self.func("CONCAT", *expressions) 3705 3706 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3707 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3708 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3709 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3710 all_args = expression.expressions 3711 expression.set("coalesce", True) 3712 return self.sql( 3713 exp.case() 3714 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3715 .else_(expression) 3716 ) 3717 3718 return self.func( 3719 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3720 ) 3721 3722 def check_sql(self, expression: exp.Check) -> str: 3723 this = self.sql(expression, key="this") 3724 return f"CHECK ({this})" 3725 3726 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3727 expressions = self.expressions(expression, flat=True) 3728 expressions = f" ({expressions})" if expressions else "" 3729 reference = self.sql(expression, "reference") 3730 reference = f" {reference}" if reference else "" 3731 delete = self.sql(expression, "delete") 3732 delete = f" ON DELETE {delete}" if delete else "" 3733 update = self.sql(expression, "update") 3734 update = f" ON UPDATE {update}" if update else "" 3735 options = self.expressions(expression, key="options", flat=True, sep=" ") 3736 options = f" {options}" if options else "" 3737 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3738 3739 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3740 this = self.sql(expression, "this") 3741 this = f" {this}" if this else "" 3742 expressions = self.expressions(expression, flat=True) 3743 include = self.sql(expression, "include") 3744 options = self.expressions(expression, key="options", flat=True, sep=" ") 3745 options = f" {options}" if options else "" 3746 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3747 3748 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3749 self.unsupported("TIMESERIES primary key columns are not supported") 3750 return self.sql(expression, "this") 3751 3752 def if_sql(self, expression: exp.If) -> str: 3753 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3754 3755 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3756 if self.MATCH_AGAINST_TABLE_PREFIX: 3757 expressions = [] 3758 for expr in expression.expressions: 3759 if isinstance(expr, exp.Table): 3760 expressions.append(f"TABLE {self.sql(expr)}") 3761 else: 3762 expressions.append(expr) 3763 else: 3764 expressions = expression.expressions 3765 3766 modifier = expression.args.get("modifier") 3767 modifier = f" {modifier}" if modifier else "" 3768 return ( 3769 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3770 ) 3771 3772 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3773 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3774 3775 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3776 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3777 3778 if self.QUOTE_JSON_PATH: 3779 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3780 3781 return path 3782 3783 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3784 if isinstance(expression, exp.JSONPathPart): 3785 transform = self.TRANSFORMS.get(expression.__class__) 3786 if not callable(transform): 3787 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3788 return "" 3789 3790 return transform(self, expression) 3791 3792 if isinstance(expression, int): 3793 return str(expression) 3794 3795 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3796 escaped = expression.replace("'", "\\'") 3797 escaped = f"\\'{expression}\\'" 3798 else: 3799 escaped = expression.replace('"', '\\"') 3800 escaped = f'"{escaped}"' 3801 3802 return escaped 3803 3804 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3805 return f"{self.sql(expression, 'this')} FORMAT JSON" 3806 3807 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3808 # Output the Teradata column FORMAT override. 3809 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3810 this = self.sql(expression, "this") 3811 fmt = self.sql(expression, "format") 3812 return f"{this} (FORMAT {fmt})" 3813 3814 def _jsonobject_sql( 3815 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3816 ) -> str: 3817 null_handling = expression.args.get("null_handling") 3818 null_handling = f" {null_handling}" if null_handling else "" 3819 3820 unique_keys = expression.args.get("unique_keys") 3821 if unique_keys is not None: 3822 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3823 else: 3824 unique_keys = "" 3825 3826 return_type = self.sql(expression, "return_type") 3827 return_type = f" RETURNING {return_type}" if return_type else "" 3828 encoding = self.sql(expression, "encoding") 3829 encoding = f" ENCODING {encoding}" if encoding else "" 3830 3831 if not name: 3832 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3833 3834 return self.func( 3835 name, 3836 *expression.expressions, 3837 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3838 ) 3839 3840 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3841 null_handling = expression.args.get("null_handling") 3842 null_handling = f" {null_handling}" if null_handling else "" 3843 return_type = self.sql(expression, "return_type") 3844 return_type = f" RETURNING {return_type}" if return_type else "" 3845 strict = " STRICT" if expression.args.get("strict") else "" 3846 return self.func( 3847 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3848 ) 3849 3850 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3851 this = self.sql(expression, "this") 3852 order = self.sql(expression, "order") 3853 null_handling = expression.args.get("null_handling") 3854 null_handling = f" {null_handling}" if null_handling else "" 3855 return_type = self.sql(expression, "return_type") 3856 return_type = f" RETURNING {return_type}" if return_type else "" 3857 strict = " STRICT" if expression.args.get("strict") else "" 3858 return self.func( 3859 "JSON_ARRAYAGG", 3860 this, 3861 suffix=f"{order}{null_handling}{return_type}{strict})", 3862 ) 3863 3864 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3865 path = self.sql(expression, "path") 3866 path = f" PATH {path}" if path else "" 3867 nested_schema = self.sql(expression, "nested_schema") 3868 3869 if nested_schema: 3870 return f"NESTED{path} {nested_schema}" 3871 3872 this = self.sql(expression, "this") 3873 kind = self.sql(expression, "kind") 3874 kind = f" {kind}" if kind else "" 3875 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3876 3877 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3878 return f"{this}{kind}{format_json}{path}{ordinality}" 3879 3880 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3881 return self.func("COLUMNS", *expression.expressions) 3882 3883 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3884 this = self.sql(expression, "this") 3885 path = self.sql(expression, "path") 3886 path = f", {path}" if path else "" 3887 error_handling = expression.args.get("error_handling") 3888 error_handling = f" {error_handling}" if error_handling else "" 3889 empty_handling = expression.args.get("empty_handling") 3890 empty_handling = f" {empty_handling}" if empty_handling else "" 3891 schema = self.sql(expression, "schema") 3892 return self.func( 3893 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3894 ) 3895 3896 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3897 this = self.sql(expression, "this") 3898 kind = self.sql(expression, "kind") 3899 path = self.sql(expression, "path") 3900 path = f" {path}" if path else "" 3901 as_json = " AS JSON" if expression.args.get("as_json") else "" 3902 return f"{this} {kind}{path}{as_json}" 3903 3904 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3905 this = self.sql(expression, "this") 3906 path = self.sql(expression, "path") 3907 path = f", {path}" if path else "" 3908 expressions = self.expressions(expression) 3909 with_ = ( 3910 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3911 if expressions 3912 else "" 3913 ) 3914 return f"OPENJSON({this}{path}){with_}" 3915 3916 def in_sql(self, expression: exp.In) -> str: 3917 query = expression.args.get("query") 3918 unnest = expression.args.get("unnest") 3919 field = expression.args.get("field") 3920 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3921 3922 if query: 3923 in_sql = self.sql(query) 3924 elif unnest: 3925 in_sql = self.in_unnest_op(unnest) 3926 elif field: 3927 in_sql = self.sql(field) 3928 else: 3929 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3930 3931 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3932 3933 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3934 return f"(SELECT {self.sql(unnest)})" 3935 3936 def interval_sql(self, expression: exp.Interval) -> str: 3937 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 3938 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 3939 exp.AutoRefreshProperty, 3940 ) 3941 interval_keyword = "INTERVAL" if include_keyword else "" 3942 unit_expression = expression.args.get("unit") 3943 unit = self.sql(unit_expression) if unit_expression else "" 3944 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3945 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3946 unit = f" {unit}" if unit else "" 3947 3948 if self.SINGLE_STRING_INTERVAL: 3949 this = expression.this.name if expression.this else "" 3950 if this: 3951 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 3952 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3953 return f"{interval_keyword}'{this}'{unit}" 3954 return f"{interval_keyword}'{this}{unit}'" 3955 return f"{interval_keyword}{unit}" 3956 3957 this = self.sql(expression, "this") 3958 if this: 3959 if not include_keyword and expression.this.is_string: 3960 this = expression.this.name 3961 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 3962 this = f"({this})" 3963 if include_keyword: 3964 this = f" {this}" 3965 3966 return f"{interval_keyword}{this}{unit}" 3967 3968 def return_sql(self, expression: exp.Return) -> str: 3969 return f"RETURN {self.sql(expression, 'this')}" 3970 3971 def reference_sql(self, expression: exp.Reference) -> str: 3972 this = self.sql(expression, "this") 3973 expressions = self.expressions(expression, flat=True) 3974 expressions = f"({expressions})" if expressions else "" 3975 options = self.expressions(expression, key="options", flat=True, sep=" ") 3976 options = f" {options}" if options else "" 3977 return f"REFERENCES {this}{expressions}{options}" 3978 3979 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3980 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3981 parent = expression.parent 3982 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3983 3984 return self.func( 3985 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3986 ) 3987 3988 def paren_sql(self, expression: exp.Paren) -> str: 3989 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3990 return f"({sql}{self.seg(')', sep='')}" 3991 3992 def neg_sql(self, expression: exp.Neg) -> str: 3993 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3994 this_sql = self.sql(expression, "this") 3995 sep = " " if this_sql[0] == "-" else "" 3996 return f"-{sep}{this_sql}" 3997 3998 def not_sql(self, expression: exp.Not) -> str: 3999 return f"NOT {self.sql(expression, 'this')}" 4000 4001 def alias_sql(self, expression: exp.Alias) -> str: 4002 alias = self.sql(expression, "alias") 4003 alias = f" AS {alias}" if alias else "" 4004 return f"{self.sql(expression, 'this')}{alias}" 4005 4006 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4007 alias = expression.args["alias"] 4008 4009 parent = expression.parent 4010 pivot = parent and parent.parent 4011 4012 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4013 identifier_alias = isinstance(alias, exp.Identifier) 4014 literal_alias = isinstance(alias, exp.Literal) 4015 4016 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4017 alias.replace(exp.Literal.string(alias.output_name)) 4018 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4019 alias.replace(exp.to_identifier(alias.output_name)) 4020 4021 return self.alias_sql(expression) 4022 4023 def aliases_sql(self, expression: exp.Aliases) -> str: 4024 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4025 4026 def atindex_sql(self, expression: exp.AtIndex) -> str: 4027 this = self.sql(expression, "this") 4028 index = self.sql(expression, "expression") 4029 return f"{this} AT {index}" 4030 4031 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4032 this = self.sql(expression, "this") 4033 zone = self.sql(expression, "zone") 4034 return f"{this} AT TIME ZONE {zone}" 4035 4036 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4037 this = self.sql(expression, "this") 4038 zone = self.sql(expression, "zone") 4039 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4040 4041 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4042 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4043 4044 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4045 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4046 4047 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4048 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4049 4050 def add_sql(self, expression: exp.Add) -> str: 4051 return self.binary(expression, "+") 4052 4053 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4054 return self.connector_sql(expression, "AND", stack) 4055 4056 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4057 return self.connector_sql(expression, "OR", stack) 4058 4059 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4060 return self.connector_sql(expression, "XOR", stack) 4061 4062 def connector_sql( 4063 self, 4064 expression: exp.Connector, 4065 op: str, 4066 stack: list[str | exp.Expr] | None = None, 4067 ) -> str: 4068 if stack is not None: 4069 stack.append(expression.right) 4070 if expression.comments and self.comments: 4071 op = self.maybe_comment(op, comments=expression.comments) 4072 4073 stack.extend((op, expression.left)) 4074 return op 4075 4076 stack = [expression] 4077 sqls: list[str] = [] 4078 ops = set() 4079 4080 while stack: 4081 node = stack.pop() 4082 if isinstance(node, exp.Connector): 4083 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4084 else: 4085 sql = self.sql(node) 4086 if sqls and sqls[-1] in ops: 4087 sqls[-1] += f" {sql}" 4088 else: 4089 sqls.append(sql) 4090 4091 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4092 return sep.join(sqls) 4093 4094 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4095 return self.binary(expression, "&") 4096 4097 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4098 return self.binary(expression, "<<") 4099 4100 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4101 return f"~{self.sql(expression, 'this')}" 4102 4103 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4104 return self.binary(expression, "|") 4105 4106 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4107 return self.binary(expression, ">>") 4108 4109 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4110 return self.binary(expression, "^") 4111 4112 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4113 format_sql = self.sql(expression, "format") 4114 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4115 to_sql = self.sql(expression, "to") 4116 to_sql = f" {to_sql}" if to_sql else "" 4117 action = self.sql(expression, "action") 4118 action = f" {action}" if action else "" 4119 default = self.sql(expression, "default") 4120 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4121 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4122 4123 # Base implementation that excludes safe, zone, and target_type metadata args 4124 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4125 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4126 4127 # Base implementation that excludes the safe and default_year metadata args 4128 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4129 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4130 4131 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4132 return self.func( 4133 "PARSE_DATETIME", 4134 expression.this, 4135 expression.args.get("format"), 4136 expression.args.get("zone"), 4137 ) 4138 4139 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4140 zone = self.sql(expression, "this") 4141 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4142 4143 def collate_sql(self, expression: exp.Collate) -> str: 4144 if self.COLLATE_IS_FUNC: 4145 return self.function_fallback_sql(expression) 4146 return self.binary(expression, "COLLATE") 4147 4148 def command_sql(self, expression: exp.Command) -> str: 4149 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4150 4151 def comment_sql(self, expression: exp.Comment) -> str: 4152 this = self.sql(expression, "this") 4153 kind = expression.args["kind"] 4154 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4155 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4156 expression_sql = self.sql(expression, "expression") 4157 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4158 4159 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4160 this = self.sql(expression, "this") 4161 delete = " DELETE" if expression.args.get("delete") else "" 4162 recompress = self.sql(expression, "recompress") 4163 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4164 to_disk = self.sql(expression, "to_disk") 4165 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4166 to_volume = self.sql(expression, "to_volume") 4167 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4168 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4169 4170 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4171 where = self.sql(expression, "where") 4172 group = self.sql(expression, "group") 4173 aggregates = self.expressions(expression, key="aggregates") 4174 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4175 4176 if not (where or group or aggregates) and len(expression.expressions) == 1: 4177 return f"TTL {self.expressions(expression, flat=True)}" 4178 4179 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4180 4181 def transaction_sql(self, expression: exp.Transaction) -> str: 4182 modes = self.expressions(expression, key="modes") 4183 modes = f" {modes}" if modes else "" 4184 return f"BEGIN{modes}" 4185 4186 def commit_sql(self, expression: exp.Commit) -> str: 4187 chain = expression.args.get("chain") 4188 if chain is not None: 4189 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4190 4191 return f"COMMIT{chain or ''}" 4192 4193 def rollback_sql(self, expression: exp.Rollback) -> str: 4194 savepoint = expression.args.get("savepoint") 4195 savepoint = f" TO {savepoint}" if savepoint else "" 4196 return f"ROLLBACK{savepoint}" 4197 4198 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4199 this = self.sql(expression, "this") 4200 4201 dtype = self.sql(expression, "dtype") 4202 if dtype: 4203 collate = self.sql(expression, "collate") 4204 collate = f" COLLATE {collate}" if collate else "" 4205 using = self.sql(expression, "using") 4206 using = f" USING {using}" if using else "" 4207 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4208 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 4209 4210 default = self.sql(expression, "default") 4211 if default: 4212 return f"ALTER COLUMN {this} SET DEFAULT {default}" 4213 4214 comment = self.sql(expression, "comment") 4215 if comment: 4216 return f"ALTER COLUMN {this} COMMENT {comment}" 4217 4218 visible = expression.args.get("visible") 4219 if visible: 4220 return f"ALTER COLUMN {this} SET {visible}" 4221 4222 allow_null = expression.args.get("allow_null") 4223 drop = expression.args.get("drop") 4224 4225 if not drop and not allow_null: 4226 self.unsupported("Unsupported ALTER COLUMN syntax") 4227 4228 if allow_null is not None: 4229 keyword = "DROP" if drop else "SET" 4230 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4231 4232 return f"ALTER COLUMN {this} DROP DEFAULT" 4233 4234 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4235 this = self.sql(expression, "this") 4236 rename_from = self.sql(expression, "rename_from") 4237 if rename_from: 4238 if not self.SUPPORTS_CHANGE_COLUMN: 4239 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4240 return f"CHANGE COLUMN {rename_from} {this}" 4241 if not self.SUPPORTS_MODIFY_COLUMN: 4242 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4243 return f"MODIFY COLUMN {this}" 4244 4245 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4246 this = self.sql(expression, "this") 4247 4248 visible = expression.args.get("visible") 4249 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4250 4251 return f"ALTER INDEX {this} {visible_sql}" 4252 4253 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4254 this = self.sql(expression, "this") 4255 if not isinstance(expression.this, exp.Var): 4256 this = f"KEY DISTKEY {this}" 4257 return f"ALTER DISTSTYLE {this}" 4258 4259 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4260 compound = " COMPOUND" if expression.args.get("compound") else "" 4261 this = self.sql(expression, "this") 4262 expressions = self.expressions(expression, flat=True) 4263 expressions = f"({expressions})" if expressions else "" 4264 return f"ALTER{compound} SORTKEY {this or expressions}" 4265 4266 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4267 if not self.RENAME_TABLE_WITH_DB: 4268 # Remove db from tables 4269 expression = expression.transform( 4270 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4271 ).assert_is(exp.AlterRename) 4272 this = self.sql(expression, "this") 4273 to_kw = " TO" if include_to else "" 4274 return f"RENAME{to_kw} {this}" 4275 4276 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4277 exists = " IF EXISTS" if expression.args.get("exists") else "" 4278 old_column = self.sql(expression, "this") 4279 new_column = self.sql(expression, "to") 4280 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4281 4282 def alterset_sql(self, expression: exp.AlterSet) -> str: 4283 exprs = self.expressions(expression, flat=True) 4284 if self.ALTER_SET_WRAPPED: 4285 exprs = f"({exprs})" 4286 4287 return f"SET {exprs}" 4288 4289 def alter_sql(self, expression: exp.Alter) -> str: 4290 actions = expression.args["actions"] 4291 4292 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4293 actions[0], exp.ColumnDef 4294 ): 4295 actions_sql = self.expressions(expression, key="actions", flat=True) 4296 actions_sql = f"ADD {actions_sql}" 4297 else: 4298 actions_list = [] 4299 for action in actions: 4300 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4301 action_sql = self.add_column_sql(action) 4302 else: 4303 action_sql = self.sql(action) 4304 if isinstance(action, exp.Query): 4305 action_sql = f"AS {action_sql}" 4306 4307 actions_list.append(action_sql) 4308 4309 actions_sql = self.format_args(*actions_list).lstrip("\n") 4310 4311 iceberg = ( 4312 "ICEBERG " 4313 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4314 else "" 4315 ) 4316 exists = " IF EXISTS" if expression.args.get("exists") else "" 4317 on_cluster = self.sql(expression, "cluster") 4318 on_cluster = f" {on_cluster}" if on_cluster else "" 4319 only = " ONLY" if expression.args.get("only") else "" 4320 options = self.expressions(expression, key="options") 4321 options = f", {options}" if options else "" 4322 kind = self.sql(expression, "kind") 4323 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4324 check = " WITH CHECK" if expression.args.get("check") else "" 4325 cascade = ( 4326 " CASCADE" 4327 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4328 else "" 4329 ) 4330 this = self.sql(expression, "this") 4331 this = f" {this}" if this else "" 4332 4333 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4334 4335 def altersession_sql(self, expression: exp.AlterSession) -> str: 4336 items_sql = self.expressions(expression, flat=True) 4337 keyword = "UNSET" if expression.args.get("unset") else "SET" 4338 return f"{keyword} {items_sql}" 4339 4340 def add_column_sql(self, expression: exp.Expr) -> str: 4341 sql = self.sql(expression) 4342 if isinstance(expression, exp.Schema): 4343 column_text = " COLUMNS" 4344 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4345 column_text = " COLUMN" 4346 else: 4347 column_text = "" 4348 4349 return f"ADD{column_text} {sql}" 4350 4351 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4352 expressions = self.expressions(expression) 4353 exists = " IF EXISTS " if expression.args.get("exists") else " " 4354 return f"DROP{exists}{expressions}" 4355 4356 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4357 return "DROP PRIMARY KEY" 4358 4359 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4360 return f"ADD {self.expressions(expression, indent=False)}" 4361 4362 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4363 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4364 location = self.sql(expression, "location") 4365 location = f" {location}" if location else "" 4366 return f"ADD {exists}{self.sql(expression.this)}{location}" 4367 4368 def distinct_sql(self, expression: exp.Distinct) -> str: 4369 this = self.expressions(expression, flat=True) 4370 4371 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4372 case = exp.case() 4373 for arg in expression.expressions: 4374 case = case.when(arg.is_(exp.null()), exp.null()) 4375 this = self.sql(case.else_(f"({this})")) 4376 4377 this = f" {this}" if this else "" 4378 4379 on = self.sql(expression, "on") 4380 on = f" ON {on}" if on else "" 4381 return f"DISTINCT{this}{on}" 4382 4383 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4384 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4385 4386 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4387 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4388 4389 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4390 this_sql = self.sql(expression, "this") 4391 expression_sql = self.sql(expression, "expression") 4392 kind = "MAX" if expression.args.get("max") else "MIN" 4393 return f"{this_sql} HAVING {kind} {expression_sql}" 4394 4395 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4396 return self.sql( 4397 exp.Cast( 4398 this=exp.Div(this=expression.this, expression=expression.expression), 4399 to=exp.DataType(this=exp.DType.INT), 4400 ) 4401 ) 4402 4403 def dpipe_sql(self, expression: exp.DPipe) -> str: 4404 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4405 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4406 return self.binary(expression, "||") 4407 4408 def div_sql(self, expression: exp.Div) -> str: 4409 l, r = expression.left, expression.right 4410 4411 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4412 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4413 4414 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4415 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4416 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4417 4418 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4419 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4420 return self.sql( 4421 exp.cast( 4422 l / r, 4423 to=exp.DType.BIGINT, 4424 ) 4425 ) 4426 4427 return self.binary(expression, "/") 4428 4429 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4430 n = exp._wrap(expression.this, exp.Binary) 4431 d = exp._wrap(expression.expression, exp.Binary) 4432 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4433 4434 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4435 return self.binary(expression, "OVERLAPS") 4436 4437 def distance_sql(self, expression: exp.Distance) -> str: 4438 return self.binary(expression, "<->") 4439 4440 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4441 return self.binary(expression, "<<->>") 4442 4443 def dot_sql(self, expression: exp.Dot) -> str: 4444 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4445 4446 def eq_sql(self, expression: exp.EQ) -> str: 4447 return self.binary(expression, "=") 4448 4449 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4450 return self.binary(expression, ":=") 4451 4452 def escape_sql(self, expression: exp.Escape) -> str: 4453 this = expression.this 4454 if ( 4455 isinstance(this, (exp.Like, exp.ILike)) 4456 and isinstance(this.expression, (exp.All, exp.Any)) 4457 and not self.SUPPORTS_LIKE_QUANTIFIERS 4458 ): 4459 return self._like_sql(this, escape=expression) 4460 return self.binary(expression, "ESCAPE") 4461 4462 def glob_sql(self, expression: exp.Glob) -> str: 4463 return self.binary(expression, "GLOB") 4464 4465 def gt_sql(self, expression: exp.GT) -> str: 4466 return self.binary(expression, ">") 4467 4468 def gte_sql(self, expression: exp.GTE) -> str: 4469 return self.binary(expression, ">=") 4470 4471 def is_sql(self, expression: exp.Is) -> str: 4472 negate = expression.args.get("negate") 4473 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4474 positive = bool(expression.expression.this) != bool(negate) 4475 return self.sql(expression.this if positive else exp.not_(expression.this)) 4476 return self.binary(expression, "IS NOT" if negate else "IS") 4477 4478 def _like_sql( 4479 self, 4480 expression: exp.Like | exp.ILike, 4481 escape: exp.Escape | None = None, 4482 ) -> str: 4483 this = expression.this 4484 rhs = expression.expression 4485 4486 if isinstance(expression, exp.Like): 4487 exp_class: type[exp.Like | exp.ILike] = exp.Like 4488 op = "LIKE" 4489 else: 4490 exp_class = exp.ILike 4491 op = "ILIKE" 4492 4493 if expression.args.get("negate"): 4494 op = f"NOT {op}" 4495 4496 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4497 exprs = rhs.this.unnest() 4498 4499 if isinstance(exprs, exp.Tuple): 4500 exprs = exprs.expressions 4501 else: 4502 exprs = [exprs] 4503 4504 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4505 4506 def _make_like(expr: exp.Expression) -> exp.Expression: 4507 like: exp.Expression = exp_class( 4508 this=this, expression=expr, negate=expression.args.get("negate") 4509 ) 4510 if escape: 4511 like = exp.Escape(this=like, expression=escape.expression.copy()) 4512 return like 4513 4514 like_expr: exp.Expr = _make_like(exprs[0]) 4515 for expr in exprs[1:]: 4516 like_expr = connective(like_expr, _make_like(expr), copy=False) 4517 4518 parent = escape.parent if escape else expression.parent 4519 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4520 parent, exp.Condition 4521 ): 4522 like_expr = exp.paren(like_expr, copy=False) 4523 4524 return self.sql(like_expr) 4525 4526 return self.binary(expression, op) 4527 4528 def like_sql(self, expression: exp.Like) -> str: 4529 return self._like_sql(expression) 4530 4531 def ilike_sql(self, expression: exp.ILike) -> str: 4532 return self._like_sql(expression) 4533 4534 def match_sql(self, expression: exp.Match) -> str: 4535 return self.binary(expression, "MATCH") 4536 4537 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4538 return self.binary(expression, "SIMILAR TO") 4539 4540 def lt_sql(self, expression: exp.LT) -> str: 4541 return self.binary(expression, "<") 4542 4543 def lte_sql(self, expression: exp.LTE) -> str: 4544 return self.binary(expression, "<=") 4545 4546 def mod_sql(self, expression: exp.Mod) -> str: 4547 return self.binary(expression, "%") 4548 4549 def mul_sql(self, expression: exp.Mul) -> str: 4550 return self.binary(expression, "*") 4551 4552 def neq_sql(self, expression: exp.NEQ) -> str: 4553 return self.binary(expression, "<>") 4554 4555 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4556 return self.binary(expression, "IS NOT DISTINCT FROM") 4557 4558 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4559 return self.binary(expression, "IS DISTINCT FROM") 4560 4561 def sub_sql(self, expression: exp.Sub) -> str: 4562 return self.binary(expression, "-") 4563 4564 def trycast_sql(self, expression: exp.TryCast) -> str: 4565 return self.cast_sql(expression, safe_prefix="TRY_") 4566 4567 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4568 return self.cast_sql(expression) 4569 4570 def try_sql(self, expression: exp.Try) -> str: 4571 if not self.TRY_SUPPORTED: 4572 self.unsupported("Unsupported TRY function") 4573 return self.sql(expression, "this") 4574 4575 return self.func("TRY", expression.this) 4576 4577 def log_sql(self, expression: exp.Log) -> str: 4578 this = expression.this 4579 expr = expression.expression 4580 4581 if self.dialect.LOG_BASE_FIRST is False: 4582 this, expr = expr, this 4583 elif self.dialect.LOG_BASE_FIRST is None and expr: 4584 if this.name in ("2", "10"): 4585 return self.func(f"LOG{this.name}", expr) 4586 4587 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4588 4589 return self.func("LOG", this, expr) 4590 4591 def use_sql(self, expression: exp.Use) -> str: 4592 kind = self.sql(expression, "kind") 4593 kind = f" {kind}" if kind else "" 4594 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4595 this = f" {this}" if this else "" 4596 return f"USE{kind}{this}" 4597 4598 def binary(self, expression: exp.Binary, op: str) -> str: 4599 sqls: list[str] = [] 4600 stack: list[None | str | exp.Expr] = [expression] 4601 binary_type = type(expression) 4602 4603 while stack: 4604 node = stack.pop() 4605 4606 if type(node) is binary_type: 4607 op_func = node.args.get("operator") 4608 if op_func: 4609 op = f"OPERATOR({self.sql(op_func)})" 4610 4611 stack.append(node.args.get("expression")) 4612 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4613 stack.append(node.args.get("this")) 4614 else: 4615 sqls.append(self.sql(node)) 4616 4617 return "".join(sqls) 4618 4619 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4620 to_clause = self.sql(expression, "to") 4621 if to_clause: 4622 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4623 4624 return self.function_fallback_sql(expression) 4625 4626 def function_fallback_sql(self, expression: exp.Func) -> str: 4627 args = [] 4628 4629 for key in expression.arg_types: 4630 arg_value = expression.args.get(key) 4631 4632 if isinstance(arg_value, list): 4633 for value in arg_value: 4634 args.append(value) 4635 elif arg_value is not None: 4636 args.append(arg_value) 4637 4638 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4639 name = expression.meta_get("name") or expression.sql_name() 4640 else: 4641 name = expression.sql_name() 4642 4643 return self.func(name, *args) 4644 4645 def func( 4646 self, 4647 name: str, 4648 *args: t.Any, 4649 prefix: str = "(", 4650 suffix: str = ")", 4651 normalize: bool = True, 4652 ) -> str: 4653 name = self.normalize_func(name) if normalize else name 4654 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4655 4656 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4657 arg_sqls = tuple( 4658 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4659 ) 4660 if self.pretty and self.too_wide(arg_sqls): 4661 return self.indent( 4662 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4663 ) 4664 return sep.join(arg_sqls) 4665 4666 def too_wide(self, args: t.Iterable) -> bool: 4667 return sum(len(arg) for arg in args) > self.max_text_width 4668 4669 def format_time( 4670 self, 4671 expression: exp.Expr, 4672 inverse_time_mapping: dict[str, str] | None = None, 4673 inverse_time_trie: dict | None = None, 4674 ) -> str | None: 4675 return format_time( 4676 self.sql(expression, "format"), 4677 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4678 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4679 ) 4680 4681 def expressions( 4682 self, 4683 expression: exp.Expr | None = None, 4684 key: str | None = None, 4685 sqls: t.Collection[str | exp.Expr] | None = None, 4686 flat: bool = False, 4687 indent: bool = True, 4688 skip_first: bool = False, 4689 skip_last: bool = False, 4690 sep: str = ", ", 4691 prefix: str = "", 4692 dynamic: bool = False, 4693 new_line: bool = False, 4694 ) -> str: 4695 expressions = expression.args.get(key or "expressions") if expression else sqls 4696 4697 if not expressions: 4698 return "" 4699 4700 if flat: 4701 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4702 4703 num_sqls = len(expressions) 4704 result_sqls = [] 4705 4706 for i, e in enumerate(expressions): 4707 sql = self.sql(e, comment=False) 4708 if not sql: 4709 continue 4710 4711 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4712 4713 if self.pretty: 4714 if self.leading_comma: 4715 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4716 else: 4717 result_sqls.append( 4718 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4719 ) 4720 else: 4721 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4722 4723 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4724 if new_line: 4725 result_sqls.insert(0, "") 4726 result_sqls.append("") 4727 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4728 else: 4729 result_sql = "".join(result_sqls) 4730 4731 return ( 4732 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4733 if indent 4734 else result_sql 4735 ) 4736 4737 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4738 flat = flat or isinstance(expression.parent, exp.Properties) 4739 expressions_sql = self.expressions(expression, flat=flat) 4740 if flat: 4741 return f"{op} {expressions_sql}" 4742 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4743 4744 def naked_property(self, expression: exp.Property) -> str: 4745 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4746 if not property_name: 4747 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4748 return f"{property_name} {self.sql(expression, 'this')}" 4749 4750 def tag_sql(self, expression: exp.Tag) -> str: 4751 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4752 4753 def token_sql(self, token_type: TokenType) -> str: 4754 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4755 4756 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4757 this = self.sql(expression, "this") 4758 expressions = self.no_identify(self.expressions, expression) 4759 expressions = ( 4760 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4761 ) 4762 return f"{this}{expressions}" if expressions.strip() != "" else this 4763 4764 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4765 return self.expressions(expression, flat=True) 4766 4767 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4768 params = self.no_identify(self.expressions, expression, flat=True) 4769 body = self.sql(expression, "this") 4770 prefix = "TABLE " if expression.args.get("is_table") else "" 4771 return f"({params}) AS {prefix}{body}" 4772 4773 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4774 this = self.sql(expression, "this") 4775 expressions = self.expressions(expression, flat=True) 4776 return f"{this}({expressions})" 4777 4778 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4779 return self.binary(expression, "=>") 4780 4781 def when_sql(self, expression: exp.When) -> str: 4782 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4783 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4784 condition = self.sql(expression, "condition") 4785 condition = f" AND {condition}" if condition else "" 4786 4787 then_expression = expression.args.get("then") 4788 if isinstance(then_expression, exp.Insert): 4789 this = self.sql(then_expression, "this") 4790 this = f"INSERT {this}" if this else "INSERT" 4791 then = self.sql(then_expression, "expression") 4792 then = f"{this} VALUES {then}" if then else this 4793 elif isinstance(then_expression, exp.Update): 4794 if isinstance(then_expression.args.get("expressions"), exp.Star): 4795 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4796 else: 4797 expressions_sql = self.expressions(then_expression) 4798 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4799 else: 4800 then = self.sql(then_expression) 4801 4802 if isinstance(then_expression, (exp.Insert, exp.Update)): 4803 where = self.sql(then_expression, "where") 4804 if where and not self.SUPPORTS_MERGE_WHERE: 4805 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4806 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4807 where = "" 4808 then = f"{then}{where}" 4809 return f"WHEN {matched}{source}{condition} THEN {then}" 4810 4811 def whens_sql(self, expression: exp.Whens) -> str: 4812 return self.expressions(expression, sep=" ", indent=False) 4813 4814 def merge_sql(self, expression: exp.Merge) -> str: 4815 table = expression.this 4816 table_alias = "" 4817 4818 hints = table.args.get("hints") 4819 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4820 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4821 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4822 4823 this = self.sql(table) 4824 using = f"USING {self.sql(expression, 'using')}" 4825 whens = self.sql(expression, "whens") 4826 4827 on = self.sql(expression, "on") 4828 on = f"ON {on}" if on else "" 4829 4830 if not on: 4831 on = self.expressions(expression, key="using_cond") 4832 on = f"USING ({on})" if on else "" 4833 4834 returning = self.sql(expression, "returning") 4835 if returning: 4836 whens = f"{whens}{returning}" 4837 4838 sep = self.sep() 4839 4840 return self.prepend_ctes( 4841 expression, 4842 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4843 ) 4844 4845 @unsupported_args("format") 4846 def tochar_sql(self, expression: exp.ToChar) -> str: 4847 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4848 4849 @unsupported_args("default") 4850 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4851 if not self.SUPPORTS_TO_NUMBER: 4852 self.unsupported("Unsupported TO_NUMBER function") 4853 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4854 4855 fmt = expression.args.get("format") 4856 if not fmt: 4857 self.unsupported("Conversion format is required for TO_NUMBER") 4858 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4859 4860 return self.func("TO_NUMBER", expression.this, fmt) 4861 4862 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4863 this = self.sql(expression, "this") 4864 kind = self.sql(expression, "kind") 4865 settings_sql = self.expressions(expression, key="settings", sep=" ") 4866 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4867 return f"{this}({kind}{args})" 4868 4869 def dictrange_sql(self, expression: exp.DictRange) -> str: 4870 this = self.sql(expression, "this") 4871 max = self.sql(expression, "max") 4872 min = self.sql(expression, "min") 4873 return f"{this}(MIN {min} MAX {max})" 4874 4875 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4876 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4877 4878 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4879 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4880 4881 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4882 def uniquekeyproperty_sql( 4883 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4884 ) -> str: 4885 return f"{prefix} ({self.expressions(expression, flat=True)})" 4886 4887 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4888 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4889 expressions = self.expressions(expression, flat=True) 4890 expressions = f" {self.wrap(expressions)}" if expressions else "" 4891 buckets = self.sql(expression, "buckets") 4892 kind = self.sql(expression, "kind") 4893 buckets = f" BUCKETS {buckets}" if buckets else "" 4894 order = self.sql(expression, "order") 4895 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4896 4897 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4898 return "" 4899 4900 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4901 expressions = self.expressions(expression, key="expressions", flat=True) 4902 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4903 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4904 buckets = self.sql(expression, "buckets") 4905 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4906 4907 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4908 this = self.sql(expression, "this") 4909 having = self.sql(expression, "having") 4910 4911 if having: 4912 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4913 4914 return self.func("ANY_VALUE", this) 4915 4916 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4917 transform = self.func("TRANSFORM", *expression.expressions) 4918 row_format_before = self.sql(expression, "row_format_before") 4919 row_format_before = f" {row_format_before}" if row_format_before else "" 4920 record_writer = self.sql(expression, "record_writer") 4921 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4922 using = f" USING {self.sql(expression, 'command_script')}" 4923 schema = self.sql(expression, "schema") 4924 schema = f" AS {schema}" if schema else "" 4925 row_format_after = self.sql(expression, "row_format_after") 4926 row_format_after = f" {row_format_after}" if row_format_after else "" 4927 record_reader = self.sql(expression, "record_reader") 4928 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4929 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4930 4931 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4932 key_block_size = self.sql(expression, "key_block_size") 4933 if key_block_size: 4934 return f"KEY_BLOCK_SIZE = {key_block_size}" 4935 4936 using = self.sql(expression, "using") 4937 if using: 4938 return f"USING {using}" 4939 4940 parser = self.sql(expression, "parser") 4941 if parser: 4942 return f"WITH PARSER {parser}" 4943 4944 comment = self.sql(expression, "comment") 4945 if comment: 4946 return f"COMMENT {comment}" 4947 4948 visible = expression.args.get("visible") 4949 if visible is not None: 4950 return "VISIBLE" if visible else "INVISIBLE" 4951 4952 engine_attr = self.sql(expression, "engine_attr") 4953 if engine_attr: 4954 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4955 4956 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4957 if secondary_engine_attr: 4958 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4959 4960 self.unsupported("Unsupported index constraint option.") 4961 return "" 4962 4963 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4964 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4965 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4966 4967 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4968 kind = self.sql(expression, "kind") 4969 kind = f"{kind} INDEX" if kind else "INDEX" 4970 this = self.sql(expression, "this") 4971 this = f" {this}" if this else "" 4972 index_type = self.sql(expression, "index_type") 4973 index_type = f" USING {index_type}" if index_type else "" 4974 expressions = self.expressions(expression, flat=True) 4975 expressions = f" ({expressions})" if expressions else "" 4976 options = self.expressions(expression, key="options", sep=" ") 4977 options = f" {options}" if options else "" 4978 return f"{kind}{this}{index_type}{expressions}{options}" 4979 4980 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4981 if self.NVL2_SUPPORTED: 4982 return self.function_fallback_sql(expression) 4983 4984 case = exp.Case().when( 4985 expression.this.is_(exp.null()).not_(copy=False), 4986 expression.args["true"], 4987 copy=False, 4988 ) 4989 else_cond = expression.args.get("false") 4990 if else_cond: 4991 case.else_(else_cond, copy=False) 4992 4993 return self.sql(case) 4994 4995 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4996 this = self.sql(expression, "this") 4997 expr = self.sql(expression, "expression") 4998 position = self.sql(expression, "position") 4999 position = f", {position}" if position else "" 5000 iterator = self.sql(expression, "iterator") 5001 condition = self.sql(expression, "condition") 5002 condition = f" IF {condition}" if condition else "" 5003 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5004 5005 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5006 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5007 5008 def opclass_sql(self, expression: exp.Opclass) -> str: 5009 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5010 5011 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5012 model = self.sql(expression, "this") 5013 model = f"MODEL {model}" 5014 expr = expression.expression 5015 if expr: 5016 expr_sql = self.sql(expression, "expression") 5017 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5018 else: 5019 expr_sql = None 5020 5021 parameters = self.sql(expression, "params_struct") or None 5022 5023 return self.func(name, model, expr_sql, parameters) 5024 5025 def predict_sql(self, expression: exp.Predict) -> str: 5026 return self._ml_sql(expression, "PREDICT") 5027 5028 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5029 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5030 return self._ml_sql(expression, name) 5031 5032 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5033 return self._ml_sql(expression, "GENERATE_TEXT") 5034 5035 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5036 return self._ml_sql(expression, "GENERATE_TABLE") 5037 5038 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5039 return self._ml_sql(expression, "GENERATE_BOOL") 5040 5041 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5042 return self._ml_sql(expression, "GENERATE_INT") 5043 5044 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5045 return self._ml_sql(expression, "GENERATE_DOUBLE") 5046 5047 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5048 return self._ml_sql(expression, "TRANSLATE") 5049 5050 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5051 return self._ml_sql(expression, "FORECAST") 5052 5053 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5054 this_sql = self.sql(expression, "this") 5055 if isinstance(expression.this, exp.Table): 5056 this_sql = f"TABLE {this_sql}" 5057 5058 return self.func( 5059 "FORECAST", 5060 this_sql, 5061 expression.args.get("data_col"), 5062 expression.args.get("timestamp_col"), 5063 expression.args.get("model"), 5064 expression.args.get("id_cols"), 5065 expression.args.get("horizon"), 5066 expression.args.get("forecast_end_timestamp"), 5067 expression.args.get("confidence_level"), 5068 expression.args.get("output_historical_time_series"), 5069 expression.args.get("context_window"), 5070 ) 5071 5072 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5073 this_sql = self.sql(expression, "this") 5074 if isinstance(expression.this, exp.Table): 5075 this_sql = f"TABLE {this_sql}" 5076 5077 return self.func( 5078 "FEATURES_AT_TIME", 5079 this_sql, 5080 expression.args.get("time"), 5081 expression.args.get("num_rows"), 5082 expression.args.get("ignore_feature_nulls"), 5083 ) 5084 5085 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5086 this_sql = self.sql(expression, "this") 5087 if isinstance(expression.this, exp.Table): 5088 this_sql = f"TABLE {this_sql}" 5089 5090 query_table = self.sql(expression, "query_table") 5091 if isinstance(expression.args["query_table"], exp.Table): 5092 query_table = f"TABLE {query_table}" 5093 5094 return self.func( 5095 "VECTOR_SEARCH", 5096 this_sql, 5097 expression.args.get("column_to_search"), 5098 query_table, 5099 expression.args.get("query_column_to_search"), 5100 expression.args.get("top_k"), 5101 expression.args.get("distance_type"), 5102 expression.args.get("options"), 5103 ) 5104 5105 def forin_sql(self, expression: exp.ForIn) -> str: 5106 this = self.sql(expression, "this") 5107 expression_sql = self.sql(expression, "expression") 5108 return f"FOR {this} DO {expression_sql}" 5109 5110 def refresh_sql(self, expression: exp.Refresh) -> str: 5111 this = self.sql(expression, "this") 5112 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5113 return f"REFRESH {kind}{this}" 5114 5115 def toarray_sql(self, expression: exp.ToArray) -> str: 5116 arg = expression.this 5117 if not arg.type: 5118 import sqlglot.optimizer.annotate_types 5119 5120 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5121 5122 if arg.is_type(exp.DType.ARRAY): 5123 return self.sql(arg) 5124 5125 cond_for_null = arg.is_(exp.null()) 5126 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5127 5128 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5129 this = expression.this 5130 time_format = self.format_time(expression) 5131 5132 if time_format: 5133 return self.sql( 5134 exp.cast( 5135 exp.StrToTime(this=this, format=expression.args["format"]), 5136 exp.DType.TIME, 5137 ) 5138 ) 5139 5140 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5141 return self.sql(this) 5142 5143 return self.sql(exp.cast(this, exp.DType.TIME)) 5144 5145 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5146 this = expression.this 5147 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5148 return self.sql(this) 5149 5150 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5151 5152 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5153 this = expression.this 5154 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5155 return self.sql(this) 5156 5157 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5158 5159 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5160 this = expression.this 5161 time_format = self.format_time(expression) 5162 safe = expression.args.get("safe") 5163 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5164 return self.sql( 5165 exp.cast( 5166 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5167 exp.DType.DATE, 5168 ) 5169 ) 5170 5171 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5172 return self.sql(this) 5173 5174 if safe: 5175 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5176 5177 return self.sql(exp.cast(this, exp.DType.DATE)) 5178 5179 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5180 return self.sql( 5181 exp.func( 5182 "DATEDIFF", 5183 expression.this, 5184 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5185 "day", 5186 ) 5187 ) 5188 5189 def lastday_sql(self, expression: exp.LastDay) -> str: 5190 if self.LAST_DAY_SUPPORTS_DATE_PART: 5191 return self.function_fallback_sql(expression) 5192 5193 unit = expression.args.get("unit") 5194 if unit and unit.name.upper() != "MONTH": 5195 self.unsupported("Date parts are not supported in LAST_DAY.") 5196 5197 return self.func("LAST_DAY", expression.this) 5198 5199 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5200 import sqlglot.dialects.dialect 5201 5202 return self.func( 5203 "DATE_ADD", 5204 expression.this, 5205 expression.expression, 5206 sqlglot.dialects.dialect.unit_to_str(expression), 5207 ) 5208 5209 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5210 if self.CAN_IMPLEMENT_ARRAY_ANY: 5211 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5212 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5213 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5214 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5215 5216 import sqlglot.dialects.dialect 5217 5218 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5219 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5220 self.unsupported("ARRAY_ANY is unsupported") 5221 5222 return self.function_fallback_sql(expression) 5223 5224 def struct_sql(self, expression: exp.Struct) -> str: 5225 expression.set( 5226 "expressions", 5227 [ 5228 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5229 if isinstance(e, exp.PropertyEQ) 5230 else e 5231 for e in expression.expressions 5232 ], 5233 ) 5234 5235 return self.function_fallback_sql(expression) 5236 5237 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5238 low = self.sql(expression, "this") 5239 high = self.sql(expression, "expression") 5240 5241 return f"{low} TO {high}" 5242 5243 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5244 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5245 tables = f" {self.expressions(expression)}" 5246 5247 exists = " IF EXISTS" if expression.args.get("exists") else "" 5248 5249 on_cluster = self.sql(expression, "cluster") 5250 on_cluster = f" {on_cluster}" if on_cluster else "" 5251 5252 identity = self.sql(expression, "identity") 5253 identity = f" {identity} IDENTITY" if identity else "" 5254 5255 option = self.sql(expression, "option") 5256 option = f" {option}" if option else "" 5257 5258 partition = self.sql(expression, "partition") 5259 partition = f" {partition}" if partition else "" 5260 5261 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5262 5263 # This transpiles T-SQL's CONVERT function 5264 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5265 def convert_sql(self, expression: exp.Convert) -> str: 5266 to = expression.this 5267 value = expression.expression 5268 style = expression.args.get("style") 5269 safe = expression.args.get("safe") 5270 strict = expression.args.get("strict") 5271 5272 if not to or not value: 5273 return "" 5274 5275 # Retrieve length of datatype and override to default if not specified 5276 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5277 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5278 5279 transformed: exp.Expr | None = None 5280 cast = exp.Cast if strict else exp.TryCast 5281 5282 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5283 if isinstance(style, exp.Literal) and style.is_int: 5284 import sqlglot.dialects.tsql 5285 5286 style_value = style.name 5287 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5288 if not converted_style: 5289 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5290 5291 fmt = exp.Literal.string(converted_style) 5292 5293 if to.this == exp.DType.DATE: 5294 transformed = exp.StrToDate(this=value, format=fmt) 5295 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5296 transformed = exp.StrToTime(this=value, format=fmt) 5297 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5298 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5299 elif to.this == exp.DType.TEXT: 5300 transformed = exp.TimeToStr(this=value, format=fmt) 5301 5302 if not transformed: 5303 transformed = cast(this=value, to=to, safe=safe) 5304 5305 return self.sql(transformed) 5306 5307 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5308 this = expression.this 5309 if isinstance(this, exp.JSONPathWildcard): 5310 this = self.json_path_part(this) 5311 return f".{this}" if this else "" 5312 5313 quoted = expression.args.get("quoted") 5314 if not ( 5315 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5316 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5317 return f".{this}" 5318 5319 this = self.json_path_part(this) 5320 5321 if quoted and self.QUOTE_JSON_PATH: 5322 # The whole path is rendered as a single quoted string literal, so the bracketed key 5323 # (which may itself contain backslash-escaped quotes, e.g. ["x \"y\"z"]) must be 5324 # escaped again for the outer string literal (-> ["x \\"y\\"z"]). 5325 this = self.escape_str(this) 5326 5327 return ( 5328 f"[{this}]" 5329 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5330 else f".{this}" 5331 ) 5332 5333 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5334 this = self.json_path_part(expression.this) 5335 return f"[{this}]" if this else "" 5336 5337 def _simplify_unless_literal(self, expression: E) -> E: 5338 if not isinstance(expression, exp.Literal): 5339 import sqlglot.optimizer.simplify 5340 5341 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5342 5343 return expression 5344 5345 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5346 this = expression.this 5347 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5348 self.unsupported( 5349 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5350 ) 5351 return self.sql(this) 5352 5353 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5354 if self.IGNORE_NULLS_BEFORE_ORDER: 5355 # The first modifier here will be the one closest to the AggFunc's arg 5356 mods = sorted( 5357 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5358 key=lambda x: ( 5359 0 5360 if isinstance(x, exp.HavingMax) 5361 else (1 if isinstance(x, exp.Order) else 2) 5362 ), 5363 ) 5364 5365 if mods: 5366 mod = mods[0] 5367 this = expression.__class__(this=mod.this.copy()) 5368 this.meta["inline"] = True 5369 mod.this.replace(this) 5370 return self.sql(expression.this) 5371 5372 agg_func = expression.find(exp.AggFunc) 5373 5374 if agg_func: 5375 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5376 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5377 5378 return f"{self.sql(expression, 'this')} {text}" 5379 5380 def _replace_line_breaks(self, string: str) -> str: 5381 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5382 if self.pretty: 5383 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5384 return string 5385 5386 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5387 option = self.sql(expression, "this") 5388 5389 if expression.expressions: 5390 upper = option.upper() 5391 5392 # Snowflake FILE_FORMAT options are separated by whitespace 5393 sep = " " if upper == "FILE_FORMAT" else ", " 5394 5395 # Databricks copy/format options do not set their list of values with EQ 5396 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5397 values = self.expressions(expression, flat=True, sep=sep) 5398 return f"{option}{op}({values})" 5399 5400 value = self.sql(expression, "expression") 5401 5402 if not value: 5403 return option 5404 5405 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5406 5407 return f"{option}{op}{value}" 5408 5409 def credentials_sql(self, expression: exp.Credentials) -> str: 5410 cred_expr = expression.args.get("credentials") 5411 if isinstance(cred_expr, exp.Literal): 5412 # Redshift case: CREDENTIALS <string> 5413 credentials = self.sql(expression, "credentials") 5414 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5415 else: 5416 # Snowflake case: CREDENTIALS = (...) 5417 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5418 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5419 5420 storage = self.sql(expression, "storage") 5421 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5422 5423 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5424 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5425 5426 iam_role = self.sql(expression, "iam_role") 5427 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5428 5429 region = self.sql(expression, "region") 5430 region = f" REGION {region}" if region else "" 5431 5432 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5433 5434 def copy_sql(self, expression: exp.Copy) -> str: 5435 this = self.sql(expression, "this") 5436 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5437 5438 credentials = self.sql(expression, "credentials") 5439 credentials = self.seg(credentials) if credentials else "" 5440 files = self.expressions(expression, key="files", flat=True) 5441 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5442 5443 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5444 params = self.expressions( 5445 expression, 5446 key="params", 5447 sep=sep, 5448 new_line=True, 5449 skip_last=True, 5450 skip_first=True, 5451 indent=self.COPY_PARAMS_ARE_WRAPPED, 5452 ) 5453 5454 if params: 5455 if self.COPY_PARAMS_ARE_WRAPPED: 5456 params = f" WITH ({params})" 5457 elif not self.pretty and (files or credentials): 5458 params = f" {params}" 5459 5460 return f"COPY{this}{kind} {files}{credentials}{params}" 5461 5462 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5463 return "" 5464 5465 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5466 on_sql = "ON" if expression.args.get("on") else "OFF" 5467 filter_col: str | None = self.sql(expression, "filter_column") 5468 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5469 retention_period: str | None = self.sql(expression, "retention_period") 5470 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5471 5472 if filter_col or retention_period: 5473 on_sql = self.func("ON", filter_col, retention_period) 5474 5475 return f"DATA_DELETION={on_sql}" 5476 5477 def maskingpolicycolumnconstraint_sql( 5478 self, expression: exp.MaskingPolicyColumnConstraint 5479 ) -> str: 5480 this = self.sql(expression, "this") 5481 expressions = self.expressions(expression, flat=True) 5482 expressions = f" USING ({expressions})" if expressions else "" 5483 return f"MASKING POLICY {this}{expressions}" 5484 5485 def gapfill_sql(self, expression: exp.GapFill) -> str: 5486 this = self.sql(expression, "this") 5487 this = f"TABLE {this}" 5488 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5489 5490 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5491 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5492 5493 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5494 this = self.sql(expression, "this") 5495 expr = expression.expression 5496 5497 if isinstance(expr, exp.Func): 5498 # T-SQL's CLR functions are case sensitive 5499 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5500 else: 5501 expr = self.sql(expression, "expression") 5502 5503 return self.scope_resolution(expr, this) 5504 5505 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5506 if self.PARSE_JSON_NAME is None: 5507 return self.sql(expression.this) 5508 5509 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5510 5511 def rand_sql(self, expression: exp.Rand) -> str: 5512 lower = self.sql(expression, "lower") 5513 upper = self.sql(expression, "upper") 5514 5515 if lower and upper: 5516 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5517 return self.func("RAND", expression.this) 5518 5519 def changes_sql(self, expression: exp.Changes) -> str: 5520 information = self.sql(expression, "information") 5521 information = f"INFORMATION => {information}" 5522 at_before = self.sql(expression, "at_before") 5523 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5524 end = self.sql(expression, "end") 5525 end = f"{self.seg('')}{end}" if end else "" 5526 5527 return f"CHANGES ({information}){at_before}{end}" 5528 5529 def pad_sql(self, expression: exp.Pad) -> str: 5530 prefix = "L" if expression.args.get("is_left") else "R" 5531 5532 fill_pattern = self.sql(expression, "fill_pattern") or None 5533 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5534 fill_pattern = "' '" 5535 5536 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5537 5538 def summarize_sql(self, expression: exp.Summarize) -> str: 5539 table = " TABLE" if expression.args.get("table") else "" 5540 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5541 5542 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5543 generate_series = exp.GenerateSeries(**expression.args) 5544 5545 parent = expression.parent 5546 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5547 parent = parent.parent 5548 5549 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5550 return self.sql(exp.Unnest(expressions=[generate_series])) 5551 5552 if isinstance(parent, exp.Select): 5553 self.unsupported("GenerateSeries projection unnesting is not supported.") 5554 5555 return self.sql(generate_series) 5556 5557 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5558 if self.SUPPORTS_CONVERT_TIMEZONE: 5559 return self.function_fallback_sql(expression) 5560 5561 source_tz = expression.args.get("source_tz") 5562 target_tz = expression.args.get("target_tz") 5563 timestamp = expression.args.get("timestamp") 5564 5565 if source_tz and timestamp: 5566 timestamp = exp.AtTimeZone( 5567 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5568 ) 5569 5570 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5571 5572 return self.sql(expr) 5573 5574 def json_sql(self, expression: exp.JSON) -> str: 5575 this = self.sql(expression, "this") 5576 this = f" {this}" if this else "" 5577 5578 _with = expression.args.get("with_") 5579 5580 if _with is None: 5581 with_sql = "" 5582 elif not _with: 5583 with_sql = " WITHOUT" 5584 else: 5585 with_sql = " WITH" 5586 5587 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5588 5589 return f"JSON{this}{with_sql}{unique_sql}" 5590 5591 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5592 path = self.sql(expression, "path") 5593 returning = self.sql(expression, "returning") 5594 returning = f" RETURNING {returning}" if returning else "" 5595 5596 on_condition = self.sql(expression, "on_condition") 5597 on_condition = f" {on_condition}" if on_condition else "" 5598 5599 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5600 5601 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5602 regexp = " REGEXP" if expression.args.get("regexp") else "" 5603 return f"SKIP{regexp} {self.sql(expression.expression)}" 5604 5605 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5606 else_ = "ELSE " if expression.args.get("else_") else "" 5607 condition = self.sql(expression, "expression") 5608 condition = f"WHEN {condition} THEN " if condition else else_ 5609 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5610 return f"{condition}{insert}" 5611 5612 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5613 kind = self.sql(expression, "kind") 5614 expressions = self.seg(self.expressions(expression, sep=" ")) 5615 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5616 return res 5617 5618 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5619 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5620 empty = expression.args.get("empty") 5621 empty = ( 5622 f"DEFAULT {empty} ON EMPTY" 5623 if isinstance(empty, exp.Expr) 5624 else self.sql(expression, "empty") 5625 ) 5626 5627 error = expression.args.get("error") 5628 error = ( 5629 f"DEFAULT {error} ON ERROR" 5630 if isinstance(error, exp.Expr) 5631 else self.sql(expression, "error") 5632 ) 5633 5634 if error and empty: 5635 error = ( 5636 f"{empty} {error}" 5637 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5638 else f"{error} {empty}" 5639 ) 5640 empty = "" 5641 5642 null = self.sql(expression, "null") 5643 5644 return f"{empty}{error}{null}" 5645 5646 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5647 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5648 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5649 5650 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5651 this = self.sql(expression, "this") 5652 path = self.sql(expression, "path") 5653 5654 passing = self.expressions(expression, "passing") 5655 passing = f" PASSING {passing}" if passing else "" 5656 5657 on_condition = self.sql(expression, "on_condition") 5658 on_condition = f" {on_condition}" if on_condition else "" 5659 5660 path = f"{path}{passing}{on_condition}" 5661 5662 return self.func("JSON_EXISTS", this, path) 5663 5664 def _add_arrayagg_null_filter( 5665 self, 5666 array_agg_sql: str, 5667 array_agg_expr: exp.ArrayAgg, 5668 column_expr: exp.Expr, 5669 ) -> str: 5670 """ 5671 Add NULL filter to ARRAY_AGG if dialect requires it. 5672 5673 Args: 5674 array_agg_sql: The generated ARRAY_AGG SQL string 5675 array_agg_expr: The ArrayAgg expression node 5676 column_expr: The column/expression to filter (before ORDER BY wrapping) 5677 5678 Returns: 5679 SQL string with FILTER clause added if needed 5680 """ 5681 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5682 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5683 if not ( 5684 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5685 ): 5686 return array_agg_sql 5687 5688 parent = array_agg_expr.parent 5689 if isinstance(parent, exp.Filter): 5690 parent_cond = parent.expression.this 5691 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5692 elif column_expr.find(exp.Column): 5693 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5694 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5695 this_sql = ( 5696 self.expressions(column_expr) 5697 if isinstance(column_expr, exp.Distinct) 5698 else self.sql(column_expr) 5699 ) 5700 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5701 5702 return array_agg_sql 5703 5704 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5705 array_agg = self.function_fallback_sql(expression) 5706 column_expr = expression.this 5707 if isinstance(column_expr, exp.Order): 5708 column_expr = column_expr.this 5709 5710 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5711 5712 def slice_sql(self, expression: exp.Slice) -> str: 5713 step = self.sql(expression, "step") 5714 end = self.sql(expression.expression) 5715 begin = self.sql(expression.this) 5716 5717 sql = f"{end}:{step}" if step else end 5718 return f"{begin}:{sql}" if sql else f"{begin}:" 5719 5720 def apply_sql(self, expression: exp.Apply) -> str: 5721 this = self.sql(expression, "this") 5722 expr = self.sql(expression, "expression") 5723 5724 return f"{this} APPLY({expr})" 5725 5726 def _grant_or_revoke_sql( 5727 self, 5728 expression: exp.Grant | exp.Revoke, 5729 keyword: str, 5730 preposition: str, 5731 grant_option_prefix: str = "", 5732 grant_option_suffix: str = "", 5733 ) -> str: 5734 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5735 5736 kind = self.sql(expression, "kind") 5737 kind = f" {kind}" if kind else "" 5738 5739 securable = self.sql(expression, "securable") 5740 securable = f" {securable}" if securable else "" 5741 5742 principals = self.expressions(expression, key="principals", flat=True) 5743 5744 if not expression.args.get("grant_option"): 5745 grant_option_prefix = grant_option_suffix = "" 5746 5747 # cascade for revoke only 5748 cascade = self.sql(expression, "cascade") 5749 cascade = f" {cascade}" if cascade else "" 5750 5751 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5752 5753 def grant_sql(self, expression: exp.Grant) -> str: 5754 return self._grant_or_revoke_sql( 5755 expression, 5756 keyword="GRANT", 5757 preposition="TO", 5758 grant_option_suffix=" WITH GRANT OPTION", 5759 ) 5760 5761 def revoke_sql(self, expression: exp.Revoke) -> str: 5762 return self._grant_or_revoke_sql( 5763 expression, 5764 keyword="REVOKE", 5765 preposition="FROM", 5766 grant_option_prefix="GRANT OPTION FOR ", 5767 ) 5768 5769 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5770 this = self.sql(expression, "this") 5771 columns = self.expressions(expression, flat=True) 5772 columns = f"({columns})" if columns else "" 5773 5774 return f"{this}{columns}" 5775 5776 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5777 this = self.sql(expression, "this") 5778 5779 kind = self.sql(expression, "kind") 5780 kind = f"{kind} " if kind else "" 5781 5782 return f"{kind}{this}" 5783 5784 def columns_sql(self, expression: exp.Columns) -> str: 5785 func = self.function_fallback_sql(expression) 5786 if expression.args.get("unpack"): 5787 func = f"*{func}" 5788 5789 return func 5790 5791 def overlay_sql(self, expression: exp.Overlay) -> str: 5792 this = self.sql(expression, "this") 5793 expr = self.sql(expression, "expression") 5794 from_sql = self.sql(expression, "from_") 5795 for_sql = self.sql(expression, "for_") 5796 for_sql = f" FOR {for_sql}" if for_sql else "" 5797 5798 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5799 5800 @unsupported_args("format") 5801 def todouble_sql(self, expression: exp.ToDouble) -> str: 5802 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5803 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5804 5805 def string_sql(self, expression: exp.String) -> str: 5806 this = expression.this 5807 zone = expression.args.get("zone") 5808 5809 if zone: 5810 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5811 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5812 # set for source_tz to transpile the time conversion before the STRING cast 5813 this = exp.ConvertTimezone( 5814 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5815 ) 5816 5817 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5818 5819 def median_sql(self, expression: exp.Median) -> str: 5820 if not self.SUPPORTS_MEDIAN: 5821 return self.sql( 5822 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5823 ) 5824 5825 return self.function_fallback_sql(expression) 5826 5827 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5828 filler = self.sql(expression, "this") 5829 filler = f" {filler}" if filler else "" 5830 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5831 return f"TRUNCATE{filler} {with_count}" 5832 5833 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5834 if self.SUPPORTS_UNIX_SECONDS: 5835 return self.function_fallback_sql(expression) 5836 5837 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5838 5839 return self.sql( 5840 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5841 ) 5842 5843 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5844 dim = expression.expression 5845 5846 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5847 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5848 if not (dim.is_int and dim.name == "1"): 5849 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5850 dim = None 5851 5852 # If dimension is required but not specified, default initialize it 5853 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5854 dim = exp.Literal.number(1) 5855 5856 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5857 5858 def attach_sql(self, expression: exp.Attach) -> str: 5859 this = self.sql(expression, "this") 5860 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5861 expressions = self.expressions(expression) 5862 expressions = f" ({expressions})" if expressions else "" 5863 5864 return f"ATTACH{exists_sql} {this}{expressions}" 5865 5866 def detach_sql(self, expression: exp.Detach) -> str: 5867 kind = self.sql(expression, "kind") 5868 kind = f" {kind}" if kind else "" 5869 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5870 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5871 exists = " IF EXISTS" if expression.args.get("exists") else "" 5872 if exists: 5873 kind = kind or " DATABASE" 5874 5875 this = self.sql(expression, "this") 5876 this = f" {this}" if this else "" 5877 cluster = self.sql(expression, "cluster") 5878 cluster = f" {cluster}" if cluster else "" 5879 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5880 sync = " SYNC" if expression.args.get("sync") else "" 5881 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5882 5883 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5884 this = self.sql(expression, "this") 5885 value = self.sql(expression, "expression") 5886 value = f" {value}" if value else "" 5887 return f"{this}{value}" 5888 5889 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5890 return ( 5891 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5892 ) 5893 5894 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5895 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5896 encode = f"{encode} {self.sql(expression, 'this')}" 5897 5898 properties = expression.args.get("properties") 5899 if properties: 5900 encode = f"{encode} {self.properties(properties)}" 5901 5902 return encode 5903 5904 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5905 this = self.sql(expression, "this") 5906 include = f"INCLUDE {this}" 5907 5908 column_def = self.sql(expression, "column_def") 5909 if column_def: 5910 include = f"{include} {column_def}" 5911 5912 alias = self.sql(expression, "alias") 5913 if alias: 5914 include = f"{include} AS {alias}" 5915 5916 return include 5917 5918 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5919 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5920 name = f"{prefix} {self.sql(expression, 'this')}" 5921 return self.func("XMLELEMENT", name, *expression.expressions) 5922 5923 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5924 this = self.sql(expression, "this") 5925 expr = self.sql(expression, "expression") 5926 expr = f"({expr})" if expr else "" 5927 return f"{this}{expr}" 5928 5929 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5930 partitions = self.expressions(expression, "partition_expressions") 5931 create = self.expressions(expression, "create_expressions") 5932 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5933 5934 def partitionbyrangepropertydynamic_sql( 5935 self, expression: exp.PartitionByRangePropertyDynamic 5936 ) -> str: 5937 start = self.sql(expression, "start") 5938 end = self.sql(expression, "end") 5939 5940 every = expression.args["every"] 5941 if isinstance(every, exp.Interval) and every.this.is_string: 5942 every.this.replace(exp.Literal.number(every.name)) 5943 5944 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5945 5946 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5947 name = self.sql(expression, "this") 5948 values = self.expressions(expression, flat=True) 5949 5950 return f"NAME {name} VALUE {values}" 5951 5952 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5953 kind = self.sql(expression, "kind") 5954 sample = self.sql(expression, "sample") 5955 return f"SAMPLE {sample} {kind}" 5956 5957 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5958 kind = self.sql(expression, "kind") 5959 option = self.sql(expression, "option") 5960 option = f" {option}" if option else "" 5961 this = self.sql(expression, "this") 5962 this = f" {this}" if this else "" 5963 columns = self.expressions(expression) 5964 columns = f" {columns}" if columns else "" 5965 return f"{kind}{option} STATISTICS{this}{columns}" 5966 5967 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5968 this = self.sql(expression, "this") 5969 columns = self.expressions(expression) 5970 inner_expression = self.sql(expression, "expression") 5971 inner_expression = f" {inner_expression}" if inner_expression else "" 5972 update_options = self.sql(expression, "update_options") 5973 update_options = f" {update_options} UPDATE" if update_options else "" 5974 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5975 5976 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5977 kind = self.sql(expression, "kind") 5978 kind = f" {kind}" if kind else "" 5979 return f"DELETE{kind} STATISTICS" 5980 5981 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5982 inner_expression = self.sql(expression, "expression") 5983 return f"LIST CHAINED ROWS{inner_expression}" 5984 5985 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5986 kind = self.sql(expression, "kind") 5987 this = self.sql(expression, "this") 5988 this = f" {this}" if this else "" 5989 inner_expression = self.sql(expression, "expression") 5990 return f"VALIDATE {kind}{this}{inner_expression}" 5991 5992 def analyze_sql(self, expression: exp.Analyze) -> str: 5993 options = self.expressions(expression, key="options", sep=" ") 5994 options = f" {options}" if options else "" 5995 kind = self.sql(expression, "kind") 5996 kind = f" {kind}" if kind else "" 5997 this = self.sql(expression, "this") 5998 this = f" {this}" if this else "" 5999 mode = self.sql(expression, "mode") 6000 mode = f" {mode}" if mode else "" 6001 properties = self.sql(expression, "properties") 6002 properties = f" {properties}" if properties else "" 6003 partition = self.sql(expression, "partition") 6004 partition = f" {partition}" if partition else "" 6005 inner_expression = self.sql(expression, "expression") 6006 inner_expression = f" {inner_expression}" if inner_expression else "" 6007 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 6008 6009 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6010 this = self.sql(expression, "this") 6011 namespaces = self.expressions(expression, key="namespaces") 6012 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6013 passing = self.expressions(expression, key="passing") 6014 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6015 columns = self.expressions(expression, key="columns") 6016 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6017 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6018 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6019 6020 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6021 this = self.sql(expression, "this") 6022 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6023 6024 def export_sql(self, expression: exp.Export) -> str: 6025 this = self.sql(expression, "this") 6026 connection = self.sql(expression, "connection") 6027 connection = f"WITH CONNECTION {connection} " if connection else "" 6028 options = self.sql(expression, "options") 6029 return f"EXPORT DATA {connection}{options} AS {this}" 6030 6031 def declare_sql(self, expression: exp.Declare) -> str: 6032 replace = "OR REPLACE " if expression.args.get("replace") else "" 6033 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6034 6035 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6036 variables = self.expressions(expression, "this") 6037 default = self.sql(expression, "default") 6038 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6039 6040 kind = self.sql(expression, "kind") 6041 if isinstance(expression.args.get("kind"), exp.Schema): 6042 kind = f"TABLE {kind}" 6043 6044 kind = f" {kind}" if kind else "" 6045 6046 return f"{variables}{kind}{default}" 6047 6048 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6049 kind = self.sql(expression, "kind") 6050 this = self.sql(expression, "this") 6051 set = self.sql(expression, "expression") 6052 using = self.sql(expression, "using") 6053 using = f" USING {using}" if using else "" 6054 6055 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6056 6057 return f"{kind_sql} {this} SET {set}{using}" 6058 6059 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6060 params = self.expressions(expression, key="params", flat=True) 6061 return self.func(expression.name, *expression.expressions) + f"({params})" 6062 6063 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6064 return self.func(expression.name, *expression.expressions) 6065 6066 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6067 return self.anonymousaggfunc_sql(expression) 6068 6069 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6070 return self.parameterizedagg_sql(expression) 6071 6072 def show_sql(self, expression: exp.Show) -> str: 6073 self.unsupported("Unsupported SHOW statement") 6074 return "" 6075 6076 def install_sql(self, expression: exp.Install) -> str: 6077 self.unsupported("Unsupported INSTALL statement") 6078 return "" 6079 6080 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6081 # Snowflake GET/PUT statements: 6082 # PUT <file> <internalStage> <properties> 6083 # GET <internalStage> <file> <properties> 6084 props = expression.args.get("properties") 6085 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6086 this = self.sql(expression, "this") 6087 target = self.sql(expression, "target") 6088 6089 if isinstance(expression, exp.Put): 6090 return f"PUT {this} {target}{props_sql}" 6091 else: 6092 return f"GET {target} {this}{props_sql}" 6093 6094 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6095 this = self.sql(expression, "this") 6096 expr = self.sql(expression, "expression") 6097 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6098 return f"TRANSLATE({this} USING {expr}{with_error})" 6099 6100 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6101 if self.SUPPORTS_DECODE_CASE: 6102 return self.func("DECODE", *expression.expressions) 6103 6104 decode_expr, *expressions = expression.expressions 6105 6106 ifs = [] 6107 for search, result in zip(expressions[::2], expressions[1::2]): 6108 if isinstance(search, exp.Literal): 6109 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6110 elif isinstance(search, exp.Null): 6111 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6112 else: 6113 if isinstance(search, exp.Binary): 6114 search = exp.paren(search) 6115 6116 cond = exp.or_( 6117 decode_expr.eq(search), 6118 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6119 copy=False, 6120 ) 6121 ifs.append(exp.If(this=cond, true=result)) 6122 6123 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6124 return self.sql(case) 6125 6126 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6127 this = self.sql(expression, "this") 6128 this = self.seg(this, sep="") 6129 dimensions = self.expressions( 6130 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6131 ) 6132 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6133 metrics = self.expressions( 6134 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6135 ) 6136 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6137 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6138 facts = self.seg(f"FACTS {facts}") if facts else "" 6139 where = self.sql(expression, "where") 6140 where = self.seg(f"WHERE {where}") if where else "" 6141 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6142 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6143 6144 def getextract_sql(self, expression: exp.GetExtract) -> str: 6145 this = expression.this 6146 expr = expression.expression 6147 6148 if not this.type or not expression.type: 6149 import sqlglot.optimizer.annotate_types 6150 6151 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6152 6153 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6154 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6155 6156 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6157 6158 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6159 return self.sql( 6160 exp.DateAdd( 6161 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6162 expression=expression.this, 6163 unit=exp.var("DAY"), 6164 ) 6165 ) 6166 6167 def space_sql(self: Generator, expression: exp.Space) -> str: 6168 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6169 6170 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6171 return f"BUILD {self.sql(expression, 'this')}" 6172 6173 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6174 method = self.sql(expression, "method") 6175 kind = expression.args.get("kind") 6176 if not kind: 6177 return f"REFRESH {method}" 6178 6179 every = self.sql(expression, "every") 6180 unit = self.sql(expression, "unit") 6181 every = f" EVERY {every} {unit}" if every else "" 6182 starts = self.sql(expression, "starts") 6183 starts = f" STARTS {starts}" if starts else "" 6184 6185 return f"REFRESH {method} ON {kind}{every}{starts}" 6186 6187 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6188 self.unsupported("The model!attribute syntax is not supported") 6189 return "" 6190 6191 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6192 return self.func("DIRECTORY", expression.this) 6193 6194 def uuid_sql(self, expression: exp.Uuid) -> str: 6195 is_string = expression.args.get("is_string", False) 6196 uuid_func_sql = self.func("UUID") 6197 6198 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6199 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6200 6201 return uuid_func_sql 6202 6203 def initcap_sql(self, expression: exp.Initcap) -> str: 6204 delimiters = expression.expression 6205 6206 if delimiters: 6207 # do not generate delimiters arg if we are round-tripping from default delimiters 6208 if ( 6209 delimiters.is_string 6210 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6211 ): 6212 delimiters = None 6213 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6214 self.unsupported("INITCAP does not support custom delimiters") 6215 delimiters = None 6216 6217 return self.func("INITCAP", expression.this, delimiters) 6218 6219 def localtime_sql(self, expression: exp.Localtime) -> str: 6220 this = expression.this 6221 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6222 6223 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6224 this = expression.this 6225 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6226 6227 def weekstart_name(self, expression: exp.WeekStart) -> str: 6228 import sqlglot.dialects.dialect 6229 6230 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6231 this = expression.this.name.upper() 6232 6233 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6234 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6235 6236 if dow_from_week_start_day != dow_from_week_offset: 6237 self.unsupported( 6238 f"WEEK({this}) is not supported; falling back to the default week start day" 6239 ) 6240 6241 return "WEEK" 6242 6243 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6244 name = self.weekstart_name(expression) 6245 6246 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6247 if isinstance(expression.parent, exp.DateTrunc): 6248 return self.sql(exp.Literal.string(name)) 6249 6250 return name 6251 6252 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6253 this = self.expressions(expression) 6254 charset = self.sql(expression, "charset") 6255 using = f" USING {charset}" if charset else "" 6256 return self.func(name, this + using) 6257 6258 def block_sql(self, expression: exp.Block) -> str: 6259 expressions = self.expressions(expression, sep="; ", flat=True) 6260 begin = "BEGIN " if expression.args.get("begin") else "" 6261 return f"{begin}{expressions}" if expressions else "" 6262 6263 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6264 self.unsupported("Unsupported Inline UDFs syntax") 6265 return "" 6266 6267 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6268 self.unsupported("Unsupported Stored Procedure syntax") 6269 return "" 6270 6271 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6272 self.unsupported("Unsupported If block syntax") 6273 return "" 6274 6275 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6276 self.unsupported("Unsupported While block syntax") 6277 return "" 6278 6279 def execute_sql(self, expression: exp.Execute) -> str: 6280 self.unsupported("Unsupported Execute syntax") 6281 return "" 6282 6283 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6284 self.unsupported("Unsupported Execute syntax") 6285 return "" 6286 6287 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6288 props = self.expressions(expression, sep=" ") 6289 return f"MODIFY {props}" 6290 6291 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6292 kind = expression.args.get("kind") 6293 return f"USING {kind} {self.sql(expression, 'this')}" 6294 6295 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6296 this = self.sql(expression, "this") 6297 to = self.sql(expression, "to") 6298 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]]:
32def unsupported_args( 33 *args: str | tuple[str, str], 34) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 35 """ 36 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 37 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 38 """ 39 diagnostic_by_arg: dict[str, str | None] = {} 40 for arg in args: 41 if isinstance(arg, str): 42 diagnostic_by_arg[arg] = None 43 else: 44 diagnostic_by_arg[arg[0]] = arg[1] 45 46 def decorator(func: GeneratorMethod) -> GeneratorMethod: 47 @wraps(func) 48 def _func(generator: G, expression: E) -> str: 49 expression_name = expression.__class__.__name__ 50 dialect_name = generator.dialect.__class__.__name__ 51 52 for arg_name, diagnostic in diagnostic_by_arg.items(): 53 if expression.args.get(arg_name): 54 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 55 arg_name, expression_name, dialect_name 56 ) 57 generator.unsupported(diagnostic) 58 59 return func(generator, expression) 60 61 return _func 62 63 return decorator
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:
97class Generator: 98 """ 99 Generator converts a given syntax tree to the corresponding SQL string. 100 101 Args: 102 pretty: Whether to format the produced SQL string. 103 Default: False. 104 identify: Determines when an identifier should be quoted. Possible values are: 105 False (default): Never quote, except in cases where it's mandatory by the dialect. 106 True: Always quote except for specials cases. 107 'safe': Only quote identifiers that are case insensitive. 108 normalize: Whether to normalize identifiers to lowercase. 109 Default: False. 110 pad: The pad size in a formatted string. For example, this affects the indentation of 111 a projection in a query, relative to its nesting level. 112 Default: 2. 113 indent: The indentation size in a formatted string. For example, this affects the 114 indentation of subqueries and filters under a `WHERE` clause. 115 Default: 2. 116 normalize_functions: How to normalize function names. Possible values are: 117 "upper" or True (default): Convert names to uppercase. 118 "lower": Convert names to lowercase. 119 False: Disables function name normalization. 120 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 121 Default ErrorLevel.WARN. 122 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 123 This is only relevant if unsupported_level is ErrorLevel.RAISE. 124 Default: 3 125 leading_comma: Whether the comma is leading or trailing in select expressions. 126 This is only relevant when generating in pretty mode. 127 Default: False 128 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 129 The default is on the smaller end because the length only represents a segment and not the true 130 line length. 131 Default: 80 132 comments: Whether to preserve comments in the output SQL code. 133 Default: True 134 """ 135 136 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 137 **JSON_PATH_PART_TRANSFORMS, 138 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 139 exp.AllowedValuesProperty: lambda self, e: ( 140 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 141 ), 142 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 143 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 144 exp.ArrayContainedBy: lambda self, e: self.binary(e, "<@"), 145 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 146 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 147 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 148 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 149 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 150 exp.CaseSpecificColumnConstraint: lambda _, e: ( 151 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 152 ), 153 exp.CalledOnNullInputProperty: lambda *_: "CALLED ON NULL INPUT", 154 exp.Ceil: lambda self, e: self.ceil_floor(e), 155 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 156 exp.CharacterSetProperty: lambda self, e: ( 157 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 158 ), 159 exp.ClusteredColumnConstraint: lambda self, e: ( 160 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 161 ), 162 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 163 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 164 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 165 exp.ConvertToCharset: lambda self, e: self.func( 166 "CONVERT", e.this, e.args["dest"], e.args.get("source") 167 ), 168 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 169 exp.CredentialsProperty: lambda self, e: ( 170 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 171 ), 172 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 173 exp.SessionUser: lambda *_: "SESSION_USER", 174 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 175 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 176 exp.ApiProperty: lambda *_: "API", 177 exp.ApplicationProperty: lambda *_: "APPLICATION", 178 exp.CatalogProperty: lambda *_: "CATALOG", 179 exp.ComputeProperty: lambda *_: "COMPUTE", 180 exp.DatabaseProperty: lambda *_: "DATABASE", 181 exp.DynamicProperty: lambda *_: "DYNAMIC", 182 exp.EmptyProperty: lambda *_: "EMPTY", 183 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 184 exp.EndStatement: lambda *_: "END", 185 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 186 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 187 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 188 exp.EphemeralColumnConstraint: lambda self, e: ( 189 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 190 ), 191 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 192 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 193 exp.Except: lambda self, e: self.set_operations(e), 194 exp.ExternalProperty: lambda *_: "EXTERNAL", 195 exp.Floor: lambda self, e: self.ceil_floor(e), 196 exp.Get: lambda self, e: self.get_put_sql(e), 197 exp.GlobalProperty: lambda *_: "GLOBAL", 198 exp.HeapProperty: lambda *_: "HEAP", 199 exp.HybridProperty: lambda *_: "HYBRID", 200 exp.IcebergProperty: lambda *_: "ICEBERG", 201 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 202 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 203 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 204 exp.Intersect: lambda self, e: self.set_operations(e), 205 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 206 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 207 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 208 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 209 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 210 exp.JSONBPathExists: lambda self, e: self.binary(e, "@?"), 211 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 212 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 213 exp.LanguageProperty: lambda self, e: self.naked_property(e), 214 exp.LocationProperty: lambda self, e: self.naked_property(e), 215 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 216 exp.MaskingProperty: lambda *_: "MASKING", 217 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 218 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 219 exp.NetworkProperty: lambda *_: "NETWORK", 220 exp.NonClusteredColumnConstraint: lambda self, e: ( 221 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 222 ), 223 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 224 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 225 exp.OnCommitProperty: lambda _, e: ( 226 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 227 ), 228 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 229 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 230 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 231 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 232 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 233 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 234 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 235 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 236 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 237 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 238 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 239 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 240 f"PROJECTION POLICY {self.sql(e, 'this')}" 241 ), 242 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 243 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 244 exp.Put: lambda self, e: self.get_put_sql(e), 245 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 246 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 247 ), 248 exp.ReturnsProperty: lambda self, e: ( 249 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 250 ), 251 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 252 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 253 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 254 exp.SecureProperty: lambda *_: "SECURE", 255 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 256 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 257 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 258 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 259 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 260 exp.SqlReadWriteProperty: lambda _, e: e.name, 261 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 262 exp.StabilityProperty: lambda _, e: e.name, 263 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 264 exp.StreamingTableProperty: lambda *_: "STREAMING", 265 exp.StrictProperty: lambda *_: "STRICT", 266 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 267 exp.TableColumn: lambda self, e: self.sql(e.this), 268 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 269 exp.TemporaryProperty: lambda *_: "TEMPORARY", 270 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 271 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 272 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 273 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 274 exp.TransientProperty: lambda *_: "TRANSIENT", 275 exp.VirtualProperty: lambda *_: "VIRTUAL", 276 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 277 exp.Union: lambda self, e: self.set_operations(e), 278 exp.UnloggedProperty: lambda *_: "UNLOGGED", 279 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 280 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 281 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 282 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 283 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 284 exp.UtcTimestamp: lambda self, e: self.sql( 285 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 286 ), 287 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 288 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 289 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 290 exp.VolatileProperty: lambda *_: "VOLATILE", 291 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 292 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 293 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 294 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 295 exp.ForceProperty: lambda *_: "FORCE", 296 } 297 298 # Whether null ordering is supported in order by 299 # True: Full Support, None: No support, False: No support for certain cases 300 # such as window specifications, aggregate functions etc 301 NULL_ORDERING_SUPPORTED: bool | None = True 302 303 # Window functions that support NULLS FIRST/LAST 304 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 305 306 # Whether ignore nulls is inside the agg or outside. 307 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 308 IGNORE_NULLS_IN_FUNC = False 309 310 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 311 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 312 IGNORE_NULLS_BEFORE_ORDER = True 313 314 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 315 LOCKING_READS_SUPPORTED = False 316 317 # Whether the EXCEPT and INTERSECT operations can return duplicates 318 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 319 320 # Wrap derived values in parens, usually standard but spark doesn't support it 321 WRAP_DERIVED_VALUES = True 322 323 # Whether create function uses an AS before the RETURN 324 CREATE_FUNCTION_RETURN_AS = True 325 326 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 327 MATCHED_BY_SOURCE = True 328 329 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 330 SUPPORTS_MERGE_WHERE = False 331 332 # Whether the INTERVAL expression works only with values like '1 day' 333 SINGLE_STRING_INTERVAL = False 334 335 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 336 INTERVAL_ALLOWS_PLURAL_FORM = True 337 338 # Whether intervals in a REFRESH schedule (AutoRefreshProperty) are generated without the 339 # INTERVAL keyword, e.g. ClickHouse's REFRESH EVERY 30 SECOND 340 AUTO_REFRESH_BARE_INTERVALS = False 341 342 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 343 LIMIT_FETCH = "ALL" 344 345 # Whether limit and fetch allows expresions or just limits 346 LIMIT_ONLY_LITERALS = False 347 348 # Whether a table is allowed to be renamed with a db 349 RENAME_TABLE_WITH_DB = True 350 351 # The separator for grouping sets and rollups 352 GROUPINGS_SEP = "," 353 354 # The string used for creating an index on a table 355 INDEX_ON = "ON" 356 357 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 358 INOUT_SEPARATOR = " " 359 360 # Whether join hints should be generated 361 JOIN_HINTS = True 362 363 # Whether directed joins are supported 364 DIRECTED_JOINS = False 365 366 # Whether table hints should be generated 367 TABLE_HINTS = True 368 369 # Whether query hints should be generated 370 QUERY_HINTS = True 371 372 # What kind of separator to use for query hints 373 QUERY_HINT_SEP = ", " 374 375 # Whether comparing against booleans (e.g. x IS TRUE) is supported 376 IS_BOOL_ALLOWED = True 377 378 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 379 DUPLICATE_KEY_UPDATE_WITH_SET = True 380 381 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 382 LIMIT_IS_TOP = False 383 384 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 385 RETURNING_END = True 386 387 # Whether to generate an unquoted value for EXTRACT's date part argument 388 EXTRACT_ALLOWS_QUOTES = True 389 390 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 391 TZ_TO_WITH_TIME_ZONE = False 392 393 # Whether the NVL2 function is supported 394 NVL2_SUPPORTED = True 395 396 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 397 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 398 399 # Whether VALUES statements can be used as derived tables. 400 # MySQL 5 and Redshift do not allow this, so when False, it will convert 401 # SELECT * VALUES into SELECT UNION 402 VALUES_AS_TABLE = True 403 404 # Whether the word COLUMN is included when adding a column with ALTER TABLE 405 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 406 407 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 408 UNNEST_WITH_ORDINALITY = True 409 410 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 411 SEMI_ANTI_JOIN_WITH_SIDE = True 412 413 # Whether to include the type of a computed column in the CREATE DDL 414 COMPUTED_COLUMN_WITH_TYPE = True 415 416 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 417 SUPPORTS_TABLE_COPY = True 418 419 # Whether parentheses are required around the table sample's expression 420 TABLESAMPLE_REQUIRES_PARENS = True 421 422 # Whether a table sample clause's size needs to be followed by the ROWS keyword 423 TABLESAMPLE_SIZE_IS_ROWS = True 424 425 # The keyword(s) to use when generating a sample clause 426 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 427 428 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 429 TABLESAMPLE_WITH_METHOD = True 430 431 # The keyword to use when specifying the seed of a sample clause 432 TABLESAMPLE_SEED_KEYWORD = "SEED" 433 434 # Whether the historical data clause (AT ... / BEFORE ...) is generated after the table alias 435 HISTORICAL_DATA_POST_ALIAS = False 436 437 # Whether COLLATE is a function instead of a binary operator 438 COLLATE_IS_FUNC = False 439 440 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 441 DATA_TYPE_SPECIFIERS_ALLOWED = False 442 443 # Whether conditions require booleans WHERE x = 0 vs WHERE x 444 ENSURE_BOOLS = False 445 446 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 447 CTE_RECURSIVE_KEYWORD_REQUIRED = True 448 449 # Whether CONCAT requires >1 arguments 450 SUPPORTS_SINGLE_ARG_CONCAT = True 451 452 # Whether LAST_DAY function supports a date part argument 453 LAST_DAY_SUPPORTS_DATE_PART = True 454 455 # Whether named columns are allowed in table aliases 456 SUPPORTS_TABLE_ALIAS_COLUMNS = True 457 458 # Whether named columns are allowed in CTE definitions 459 SUPPORTS_NAMED_CTE_COLUMNS = True 460 461 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 462 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 463 464 # Whether a (UN)PIVOT's alias is introduced with AS (Oracle rejects it, ORA-03048) 465 PIVOT_ALIAS_WITH_AS = True 466 467 # What delimiter to use for separating JSON key/value pairs 468 JSON_KEY_VALUE_PAIR_SEP = ":" 469 470 # INSERT OVERWRITE TABLE x override 471 INSERT_OVERWRITE = " OVERWRITE TABLE" 472 473 # Whether the SELECT .. INTO syntax is used instead of CTAS 474 SUPPORTS_SELECT_INTO = False 475 476 # Whether UNLOGGED tables can be created 477 SUPPORTS_UNLOGGED_TABLES = False 478 479 # Whether the CREATE TABLE LIKE statement is supported 480 SUPPORTS_CREATE_TABLE_LIKE = True 481 482 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 483 SUPPORTS_MODIFY_COLUMN = False 484 485 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 486 SUPPORTS_CHANGE_COLUMN = False 487 488 # Whether the LikeProperty needs to be specified inside of the schema clause 489 LIKE_PROPERTY_INSIDE_SCHEMA = False 490 491 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 492 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 493 MULTI_ARG_DISTINCT = True 494 495 # Whether the JSON extraction operators expect a value of type JSON 496 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 497 498 # Whether bracketed keys like ["foo"] are supported in JSON paths 499 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 500 501 # Whether to escape keys using single quotes in JSON paths 502 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 503 504 # Whether a quoted JSON path key (e.g. from a quoted identifier or ['key'] bracket) must be 505 # rendered in bracket form to preserve its case-sensitivity, even if it would otherwise match 506 # SAFE_JSON_PATH_KEY_RE and render as a bare dotted key. Needed for dialects like Databricks 507 # where a bare colon key is case-insensitive but a bracketed key is case-sensitive. 508 JSON_PATH_KEY_QUOTED_FORCES_BRACKETS = False 509 510 # The JSONPathPart expressions supported by this dialect 511 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 512 513 # Whether any(f(x) for x in array) can be implemented by this dialect 514 CAN_IMPLEMENT_ARRAY_ANY = False 515 516 # Whether the function TO_NUMBER is supported 517 SUPPORTS_TO_NUMBER = True 518 519 # Whether EXCLUDE in window specification is supported 520 SUPPORTS_WINDOW_EXCLUDE = False 521 522 # Whether or not set op modifiers apply to the outer set op or select. 523 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 524 # True means limit 1 happens after the set op, False means it it happens on y. 525 SET_OP_MODIFIERS = True 526 527 # Whether parameters from COPY statement are wrapped in parentheses 528 COPY_PARAMS_ARE_WRAPPED = True 529 530 # Whether values of params are set with "=" token or empty space 531 COPY_PARAMS_EQ_REQUIRED = False 532 533 # Whether COPY statement has INTO keyword 534 COPY_HAS_INTO_KEYWORD = True 535 536 # Whether the conditional TRY(expression) function is supported 537 TRY_SUPPORTED = True 538 539 # Whether the UESCAPE syntax in unicode strings is supported 540 SUPPORTS_UESCAPE = True 541 542 # Function used to replace escaped unicode codes in unicode strings 543 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 544 545 # The keyword to use when generating a star projection with excluded columns 546 STAR_EXCEPT = "EXCEPT" 547 548 # The HEX function name 549 HEX_FUNC = "HEX" 550 551 # The keywords to use when prefixing & separating WITH based properties 552 WITH_PROPERTIES_PREFIX = "WITH" 553 554 # Whether to quote the generated expression of exp.JsonPath 555 QUOTE_JSON_PATH = True 556 557 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 558 PAD_FILL_PATTERN_IS_REQUIRED = False 559 560 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 561 SUPPORTS_EXPLODING_PROJECTIONS = True 562 563 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 564 ARRAY_CONCAT_IS_VAR_LEN = True 565 566 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 567 SUPPORTS_CONVERT_TIMEZONE = False 568 569 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 570 SUPPORTS_MEDIAN = True 571 572 # Whether UNIX_SECONDS(timestamp) is supported 573 SUPPORTS_UNIX_SECONDS = False 574 575 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 576 ALTER_SET_WRAPPED = False 577 578 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 579 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 580 # TODO: The normalization should be done by default once we've tested it across all dialects. 581 NORMALIZE_EXTRACT_DATE_PARTS = False 582 583 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 584 PARSE_JSON_NAME: str | None = "PARSE_JSON" 585 586 # The function name of the exp.ArraySize expression 587 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 588 589 # The syntax to use when altering the type of a column 590 ALTER_SET_TYPE = "SET DATA TYPE" 591 592 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 593 # None -> Doesn't support it at all 594 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 595 # True (Postgres) -> Explicitly requires it 596 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 597 598 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 599 SUPPORTS_DECODE_CASE = True 600 601 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 602 SUPPORTS_BETWEEN_FLAGS = False 603 604 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 605 SUPPORTS_LIKE_QUANTIFIERS = True 606 607 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 608 MATCH_AGAINST_TABLE_PREFIX: str | None = None 609 610 # Whether to include the VARIABLE keyword for SET assignments 611 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 612 613 # The keyword to use for default value assignment in DECLARE statements 614 DECLARE_DEFAULT_ASSIGNMENT = "=" 615 616 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 617 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 618 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 619 UPDATE_STATEMENT_SUPPORTS_FROM = True 620 621 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 622 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 623 624 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 625 # - Snowflake: DROP ICEBERG TABLE a.b; 626 # - DuckDB: DROP TABLE a.b; 627 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 628 629 TYPE_MAPPING: t.ClassVar = { 630 exp.DType.DATETIME2: "TIMESTAMP", 631 exp.DType.NCHAR: "CHAR", 632 exp.DType.NVARCHAR: "VARCHAR", 633 exp.DType.MEDIUMTEXT: "TEXT", 634 exp.DType.LONGTEXT: "TEXT", 635 exp.DType.TINYTEXT: "TEXT", 636 exp.DType.BLOB: "VARBINARY", 637 exp.DType.MEDIUMBLOB: "BLOB", 638 exp.DType.LONGBLOB: "BLOB", 639 exp.DType.TINYBLOB: "BLOB", 640 exp.DType.INET: "INET", 641 exp.DType.ROWVERSION: "VARBINARY", 642 exp.DType.SMALLDATETIME: "TIMESTAMP", 643 } 644 645 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 646 647 # mapping of DType to its default parameters, bounds 648 TYPE_PARAM_SETTINGS: t.ClassVar[ 649 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 650 ] = {} 651 652 TIME_PART_SINGULARS: t.ClassVar = { 653 "MICROSECONDS": "MICROSECOND", 654 "SECONDS": "SECOND", 655 "MINUTES": "MINUTE", 656 "HOURS": "HOUR", 657 "DAYS": "DAY", 658 "WEEKS": "WEEK", 659 "MONTHS": "MONTH", 660 "QUARTERS": "QUARTER", 661 "YEARS": "YEAR", 662 } 663 664 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 665 "cluster": lambda self, e: self.sql(e, "cluster"), 666 "distribute": lambda self, e: self.sql(e, "distribute"), 667 "sort": lambda self, e: self.sql(e, "sort"), 668 **AFTER_HAVING_MODIFIER_TRANSFORMS, 669 } 670 671 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 672 673 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 674 675 PARAMETER_TOKEN = "@" 676 NAMED_PLACEHOLDER_TOKEN = ":" 677 678 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 679 680 PROPERTIES_LOCATION: t.ClassVar = { 681 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 682 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 683 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 684 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 685 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 686 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 687 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 688 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 689 exp.CalledOnNullInputProperty: exp.Properties.Location.POST_SCHEMA, 690 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 691 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 692 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 693 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 694 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 695 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 696 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 697 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 698 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 699 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 700 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 701 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 702 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 703 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 704 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 705 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 706 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 707 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 708 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 709 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 710 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 711 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 712 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 713 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 714 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 715 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 717 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 718 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 719 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 720 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 721 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 722 exp.HeapProperty: exp.Properties.Location.POST_WITH, 723 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 724 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 725 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 726 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 729 exp.JournalProperty: exp.Properties.Location.POST_NAME, 730 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 731 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 735 exp.LogProperty: exp.Properties.Location.POST_NAME, 736 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 737 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 738 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 739 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 741 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 742 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 744 exp.Order: exp.Properties.Location.POST_SCHEMA, 745 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 746 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 747 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 748 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 749 exp.Property: exp.Properties.Location.POST_WITH, 750 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 751 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 754 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 755 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 759 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 760 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 761 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 762 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 763 exp.Set: exp.Properties.Location.POST_SCHEMA, 764 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 765 exp.SetProperty: exp.Properties.Location.POST_CREATE, 766 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 767 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 768 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 769 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 770 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 771 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 772 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 773 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 775 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 776 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 777 exp.Tags: exp.Properties.Location.POST_WITH, 778 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 779 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 780 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 781 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 782 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 783 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 784 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 785 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 786 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 787 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 788 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 789 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 790 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 791 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 792 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 793 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 794 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 795 } 796 797 # Keywords that can't be used as unquoted identifier names 798 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 799 800 # Exprs whose comments are separated from them for better formatting 801 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 802 exp.Command, 803 exp.Create, 804 exp.Describe, 805 exp.Delete, 806 exp.Drop, 807 exp.From, 808 exp.Insert, 809 exp.Join, 810 exp.MultitableInserts, 811 exp.Order, 812 exp.Group, 813 exp.Having, 814 exp.Select, 815 exp.SetOperation, 816 exp.Update, 817 exp.Where, 818 exp.With, 819 ) 820 821 # Exprs that should not have their comments generated in maybe_comment 822 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 823 exp.Binary, 824 exp.SetOperation, 825 ) 826 827 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 828 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 829 exp.Column, 830 exp.Literal, 831 exp.Neg, 832 exp.Paren, 833 ) 834 835 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 836 exp.DType.NVARCHAR, 837 exp.DType.VARCHAR, 838 exp.DType.CHAR, 839 exp.DType.NCHAR, 840 } 841 842 # Exprs that need to have all CTEs under them bubbled up to them 843 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 844 845 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 846 847 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 848 849 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 850 851 __slots__ = ( 852 "pretty", 853 "identify", 854 "normalize", 855 "pad", 856 "_indent", 857 "normalize_functions", 858 "unsupported_level", 859 "max_unsupported", 860 "leading_comma", 861 "max_text_width", 862 "comments", 863 "dialect", 864 "unsupported_messages", 865 "_escaped_quote_end", 866 "_escaped_byte_quote_end", 867 "_escaped_identifier_end", 868 "_next_name", 869 "_identifier_start", 870 "_identifier_end", 871 "_quote_json_path_key_using_brackets", 872 "_dispatch", 873 ) 874 875 def __init__( 876 self, 877 pretty: bool | int | None = None, 878 identify: str | bool = False, 879 normalize: bool = False, 880 pad: int = 2, 881 indent: int = 2, 882 normalize_functions: str | bool | None = None, 883 unsupported_level: ErrorLevel = ErrorLevel.WARN, 884 max_unsupported: int = 3, 885 leading_comma: bool = False, 886 max_text_width: int = 80, 887 comments: bool = True, 888 dialect: DialectType = None, 889 ): 890 import sqlglot 891 import sqlglot.dialects.dialect 892 893 self.pretty = pretty if pretty is not None else sqlglot.pretty 894 self.identify = identify 895 self.normalize = normalize 896 self.pad = pad 897 self._indent = indent 898 self.unsupported_level = unsupported_level 899 self.max_unsupported = max_unsupported 900 self.leading_comma = leading_comma 901 self.max_text_width = max_text_width 902 self.comments = comments 903 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 904 905 # This is both a Dialect property and a Generator argument, so we prioritize the latter 906 self.normalize_functions = ( 907 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 908 ) 909 910 self.unsupported_messages: list[str] = [] 911 self._escaped_quote_end: str = ( 912 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 913 ) 914 self._escaped_byte_quote_end: str = ( 915 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 916 if self.dialect.BYTE_END 917 else "" 918 ) 919 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 920 921 self._next_name = name_sequence("_t") 922 923 self._identifier_start = self.dialect.IDENTIFIER_START 924 self._identifier_end = self.dialect.IDENTIFIER_END 925 926 self._quote_json_path_key_using_brackets = True 927 928 cls = type(self) 929 dispatch = _DISPATCH_CACHE.get(cls) 930 if dispatch is None: 931 dispatch = _build_dispatch(cls) 932 _DISPATCH_CACHE[cls] = dispatch 933 self._dispatch = dispatch 934 935 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 936 """ 937 Generates the SQL string corresponding to the given syntax tree. 938 939 Args: 940 expression: The syntax tree. 941 copy: Whether to copy the expression. The generator performs mutations so 942 it is safer to copy. 943 944 Returns: 945 The SQL string corresponding to `expression`. 946 """ 947 if copy: 948 expression = expression.copy() 949 950 expression = self.preprocess(expression) 951 952 self.unsupported_messages = [] 953 sql = self.sql(expression).strip() 954 955 if self.pretty: 956 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 957 958 if self.unsupported_level == ErrorLevel.IGNORE: 959 return sql 960 961 if self.unsupported_level == ErrorLevel.WARN: 962 for msg in self.unsupported_messages: 963 logger.warning(msg) 964 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 965 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 966 967 return sql 968 969 def preprocess(self, expression: exp.Expr) -> exp.Expr: 970 """Apply generic preprocessing transformations to a given expression.""" 971 expression = self._move_ctes_to_top_level(expression) 972 973 if self.ENSURE_BOOLS: 974 import sqlglot.transforms 975 976 expression = sqlglot.transforms.ensure_bools(expression) 977 978 return expression 979 980 def _move_ctes_to_top_level(self, expression: E) -> E: 981 if ( 982 not expression.parent 983 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 984 and any(node.parent is not expression for node in expression.find_all(exp.With)) 985 ): 986 import sqlglot.transforms 987 988 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 989 return expression 990 991 def unsupported(self, message: str) -> None: 992 if self.unsupported_level == ErrorLevel.IMMEDIATE: 993 raise UnsupportedError(message) 994 self.unsupported_messages.append(message) 995 996 def sep(self, sep: str = " ") -> str: 997 return f"{sep.strip()}\n" if self.pretty else sep 998 999 def seg(self, sql: str, sep: str = " ") -> str: 1000 return f"{self.sep(sep)}{sql}" 1001 1002 def sanitize_comment(self, comment: str) -> str: 1003 comment = " " + comment if comment[0].strip() else comment 1004 comment = comment + " " if comment[-1].strip() else comment 1005 1006 # Escape block comment markers to prevent premature closure or unintended nesting. 1007 # This is necessary because single-line comments (--) are converted to block comments 1008 # (/* */) on output, and any */ in the original text would close the comment early. 1009 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1010 1011 return comment 1012 1013 def maybe_comment( 1014 self, 1015 sql: str, 1016 expression: exp.Expr | None = None, 1017 comments: list[str] | None = None, 1018 separated: bool = False, 1019 ) -> str: 1020 comments = ( 1021 ((expression and expression.comments) if comments is None else comments) # type: ignore 1022 if self.comments 1023 else None 1024 ) 1025 1026 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1027 return sql 1028 1029 comments_list = [ 1030 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1031 for comment in comments 1032 if comment 1033 ] 1034 1035 if not comments_list: 1036 return sql 1037 1038 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1039 comments_sql = self.sep().join(comments_list) 1040 return ( 1041 f"{self.sep()}{comments_sql}{sql}" 1042 if not sql or sql[0].isspace() 1043 else f"{comments_sql}{self.sep()}{sql}" 1044 ) 1045 1046 return f"{sql} {' '.join(comments_list)}" 1047 1048 def wrap(self, expression: exp.Expr | str) -> str: 1049 this_sql = ( 1050 self.sql(expression) 1051 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1052 else self.sql(expression, "this") 1053 ) 1054 if not this_sql: 1055 return "()" 1056 1057 this_sql = self.indent(this_sql, level=1, pad=0) 1058 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1059 1060 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1061 original = self.identify 1062 self.identify = False 1063 result = func(*args, **kwargs) 1064 self.identify = original 1065 return result 1066 1067 def normalize_func(self, name: str) -> str: 1068 if self.normalize_functions == "upper" or self.normalize_functions is True: 1069 return name.upper() 1070 if self.normalize_functions == "lower": 1071 return name.lower() 1072 return name 1073 1074 def indent( 1075 self, 1076 sql: str, 1077 level: int = 0, 1078 pad: int | None = None, 1079 skip_first: bool = False, 1080 skip_last: bool = False, 1081 ) -> str: 1082 if not self.pretty or not sql: 1083 return sql 1084 1085 pad = self.pad if pad is None else pad 1086 lines = sql.split("\n") 1087 1088 return "\n".join( 1089 ( 1090 line 1091 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1092 else f"{' ' * (level * self._indent + pad)}{line}" 1093 ) 1094 for i, line in enumerate(lines) 1095 ) 1096 1097 def sql( 1098 self, 1099 expression: str | exp.Expr | None, 1100 key: str | None = None, 1101 comment: bool = True, 1102 ) -> str: 1103 if not expression: 1104 return "" 1105 1106 if isinstance(expression, str): 1107 return expression 1108 1109 if key: 1110 value = expression.args.get(key) 1111 if value: 1112 return self.sql(value) 1113 return "" 1114 1115 handler = self._dispatch.get(expression.__class__) 1116 1117 if handler: 1118 sql = handler(self, expression) 1119 elif isinstance(expression, exp.Func): 1120 sql = self.function_fallback_sql(expression) 1121 elif isinstance(expression, exp.Property): 1122 sql = self.property_sql(expression) 1123 else: 1124 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1125 1126 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1127 1128 def uncache_sql(self, expression: exp.Uncache) -> str: 1129 table = self.sql(expression, "this") 1130 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1131 return f"UNCACHE TABLE{exists_sql} {table}" 1132 1133 def cache_sql(self, expression: exp.Cache) -> str: 1134 lazy = " LAZY" if expression.args.get("lazy") else "" 1135 table = self.sql(expression, "this") 1136 options = expression.args.get("options") 1137 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1138 sql = self.sql(expression, "expression") 1139 sql = f" AS{self.sep()}{sql}" if sql else "" 1140 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1141 return self.prepend_ctes(expression, sql) 1142 1143 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1144 default = "DEFAULT " if expression.args.get("default") else "" 1145 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1146 1147 def column_parts(self, expression: exp.Column) -> str: 1148 return ".".join( 1149 self.sql(part) 1150 for part in ( 1151 expression.args.get("catalog"), 1152 expression.args.get("db"), 1153 expression.args.get("table"), 1154 expression.args.get("this"), 1155 ) 1156 if part 1157 ) 1158 1159 def column_sql(self, expression: exp.Column) -> str: 1160 join_mark = " (+)" if expression.args.get("join_mark") else "" 1161 1162 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1163 join_mark = "" 1164 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1165 1166 return f"{self.column_parts(expression)}{join_mark}" 1167 1168 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1169 return self.column_sql(expression) 1170 1171 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1172 this = self.sql(expression, "this") 1173 this = f" {this}" if this else "" 1174 position = self.sql(expression, "position") 1175 return f"{position}{this}" 1176 1177 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1178 column = self.sql(expression, "this") 1179 kind = self.sql(expression, "kind") 1180 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1181 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1182 kind = f"{sep}{kind}" if kind else "" 1183 constraints = f" {constraints}" if constraints else "" 1184 position = self.sql(expression, "position") 1185 position = f" {position}" if position else "" 1186 1187 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1188 kind = "" 1189 1190 return f"{exists}{column}{kind}{constraints}{position}" 1191 1192 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1193 this = self.sql(expression, "this") 1194 kind_sql = self.sql(expression, "kind").strip() 1195 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1196 1197 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1198 this = self.sql(expression, "this") 1199 if expression.args.get("not_null"): 1200 persisted = " PERSISTED NOT NULL" 1201 elif expression.args.get("persisted"): 1202 persisted = " PERSISTED" 1203 else: 1204 persisted = "" 1205 1206 return f"AS {this}{persisted}" 1207 1208 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1209 return self.token_sql(TokenType.AUTO_INCREMENT) 1210 1211 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1212 if isinstance(expression.this, list): 1213 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1214 else: 1215 this = self.sql(expression, "this") 1216 1217 return f"COMPRESS {this}" 1218 1219 def generatedasidentitycolumnconstraint_sql( 1220 self, expression: exp.GeneratedAsIdentityColumnConstraint 1221 ) -> str: 1222 this = "" 1223 if expression.this is not None: 1224 on_null = " ON NULL" if expression.args.get("on_null") else "" 1225 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1226 1227 start = expression.args.get("start") 1228 start = f"START WITH {start}" if start else "" 1229 increment = expression.args.get("increment") 1230 increment = f" INCREMENT BY {increment}" if increment else "" 1231 minvalue = expression.args.get("minvalue") 1232 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1233 maxvalue = expression.args.get("maxvalue") 1234 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1235 cycle = expression.args.get("cycle") 1236 cycle_sql = "" 1237 1238 if cycle is not None: 1239 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1240 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1241 1242 sequence_opts = "" 1243 if start or increment or cycle_sql: 1244 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1245 sequence_opts = f" ({sequence_opts.strip()})" 1246 1247 expr = self.sql(expression, "expression") 1248 expr = f"({expr})" if expr else "IDENTITY" 1249 1250 return f"GENERATED{this} AS {expr}{sequence_opts}" 1251 1252 def generatedasrowcolumnconstraint_sql( 1253 self, expression: exp.GeneratedAsRowColumnConstraint 1254 ) -> str: 1255 start = "START" if expression.args.get("start") else "END" 1256 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1257 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1258 1259 def periodforsystemtimeconstraint_sql( 1260 self, expression: exp.PeriodForSystemTimeConstraint 1261 ) -> str: 1262 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1263 1264 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1265 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1266 1267 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1268 desc = expression.args.get("desc") 1269 if desc is not None: 1270 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1271 options = self.expressions(expression, key="options", flat=True, sep=" ") 1272 options = f" {options}" if options else "" 1273 return f"PRIMARY KEY{options}" 1274 1275 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1276 this = self.sql(expression, "this") 1277 this = f" {this}" if this else "" 1278 index_type = expression.args.get("index_type") 1279 index_type = f" USING {index_type}" if index_type else "" 1280 on_conflict = self.sql(expression, "on_conflict") 1281 on_conflict = f" {on_conflict}" if on_conflict else "" 1282 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1283 options = self.expressions(expression, key="options", flat=True, sep=" ") 1284 options = f" {options}" if options else "" 1285 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1286 1287 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1288 input_ = expression.args.get("input_") 1289 output = expression.args.get("output") 1290 variadic = expression.args.get("variadic") 1291 1292 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1293 if variadic: 1294 return "VARIADIC" 1295 1296 if input_ and output: 1297 return f"IN{self.INOUT_SEPARATOR}OUT" 1298 if input_: 1299 return "IN" 1300 if output: 1301 return "OUT" 1302 1303 return "" 1304 1305 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1306 return self.sql(expression, "this") 1307 1308 def create_sql(self, expression: exp.Create) -> str: 1309 kind = self.sql(expression, "kind") 1310 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1311 1312 properties = expression.args.get("properties") 1313 1314 if ( 1315 kind == "TRIGGER" 1316 and properties 1317 and properties.expressions 1318 and isinstance(properties.expressions[0], exp.TriggerProperties) 1319 and properties.expressions[0].args.get("constraint") 1320 ): 1321 kind = f"CONSTRAINT {kind}" 1322 1323 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1324 1325 this = self.createable_sql(expression, properties_locs) 1326 1327 properties_sql = "" 1328 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1329 exp.Properties.Location.POST_WITH 1330 ): 1331 props_ast = exp.Properties( 1332 expressions=[ 1333 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1334 *properties_locs[exp.Properties.Location.POST_WITH], 1335 ] 1336 ) 1337 props_ast.parent = expression 1338 properties_sql = self.sql(props_ast) 1339 1340 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1341 properties_sql = self.sep() + properties_sql 1342 elif not self.pretty: 1343 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1344 properties_sql = f" {properties_sql}" 1345 1346 begin = " BEGIN" if expression.args.get("begin") else "" 1347 1348 expression_sql = self.sql(expression, "expression") 1349 if expression_sql: 1350 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1351 1352 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1353 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1354 ): 1355 postalias_props_sql = "" 1356 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1357 postalias_props_sql = self.properties( 1358 exp.Properties( 1359 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1360 ), 1361 wrapped=False, 1362 ) 1363 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1364 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1365 1366 postindex_props_sql = "" 1367 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1368 postindex_props_sql = self.properties( 1369 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1370 wrapped=False, 1371 prefix=" ", 1372 ) 1373 1374 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1375 indexes = f" {indexes}" if indexes else "" 1376 index_sql = indexes + postindex_props_sql 1377 1378 replace = " OR REPLACE" if expression.args.get("replace") else "" 1379 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1380 unique = " UNIQUE" if expression.args.get("unique") else "" 1381 1382 clustered = expression.args.get("clustered") 1383 if clustered is None: 1384 clustered_sql = "" 1385 elif clustered: 1386 clustered_sql = " CLUSTERED COLUMNSTORE" 1387 else: 1388 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1389 1390 postcreate_props_sql = "" 1391 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1392 postcreate_props_sql = self.properties( 1393 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1394 sep=" ", 1395 prefix=" ", 1396 wrapped=False, 1397 ) 1398 1399 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1400 1401 postexpression_props_sql = "" 1402 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1403 postexpression_props_sql = self.properties( 1404 exp.Properties( 1405 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1406 ), 1407 sep=" ", 1408 prefix=" ", 1409 wrapped=False, 1410 ) 1411 1412 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1413 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1414 no_schema_binding = ( 1415 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1416 ) 1417 1418 clone = self.sql(expression, "clone") 1419 clone = f" {clone}" if clone else "" 1420 1421 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1422 properties_expression = f"{expression_sql}{properties_sql}" 1423 else: 1424 properties_expression = f"{properties_sql}{expression_sql}" 1425 1426 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1427 return self.prepend_ctes(expression, expression_sql) 1428 1429 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1430 start = self.sql(expression, "start") 1431 start = f"START WITH {start}" if start else "" 1432 increment = self.sql(expression, "increment") 1433 increment = f" INCREMENT BY {increment}" if increment else "" 1434 minvalue = self.sql(expression, "minvalue") 1435 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1436 maxvalue = self.sql(expression, "maxvalue") 1437 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1438 owned = self.sql(expression, "owned") 1439 owned = f" OWNED BY {owned}" if owned else "" 1440 1441 cache = expression.args.get("cache") 1442 if cache is None: 1443 cache_str = "" 1444 elif cache is True: 1445 cache_str = " CACHE" 1446 else: 1447 cache_str = f" CACHE {cache}" 1448 1449 options = self.expressions(expression, key="options", flat=True, sep=" ") 1450 options = f" {options}" if options else "" 1451 1452 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1453 1454 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1455 timing = expression.args.get("timing", "") 1456 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1457 timing_events = f"{timing} {events}".strip() if timing or events else "" 1458 1459 parts = [timing_events, "ON", self.sql(expression, "table")] 1460 1461 if referenced_table := expression.args.get("referenced_table"): 1462 parts.extend(["FROM", self.sql(referenced_table)]) 1463 1464 if deferrable := expression.args.get("deferrable"): 1465 parts.append(deferrable) 1466 1467 if initially := expression.args.get("initially"): 1468 parts.append(f"INITIALLY {initially}") 1469 1470 if referencing := expression.args.get("referencing"): 1471 parts.append(self.sql(referencing)) 1472 1473 if for_each := expression.args.get("for_each"): 1474 parts.append(f"FOR EACH {for_each}") 1475 1476 if when := expression.args.get("when"): 1477 parts.append(f"WHEN ({self.sql(when)})") 1478 1479 parts.append(self.sql(expression, "execute")) 1480 1481 return self.sep().join(parts) 1482 1483 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1484 parts = [] 1485 1486 if old_alias := expression.args.get("old"): 1487 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1488 1489 if new_alias := expression.args.get("new"): 1490 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1491 1492 return f"REFERENCING {' '.join(parts)}" 1493 1494 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1495 columns = expression.args.get("columns") 1496 if columns: 1497 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1498 1499 return self.sql(expression, "this") 1500 1501 def clone_sql(self, expression: exp.Clone) -> str: 1502 this = self.sql(expression, "this") 1503 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1504 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1505 return f"{shallow}{keyword} {this}" 1506 1507 def describe_sql(self, expression: exp.Describe) -> str: 1508 style = expression.args.get("style") 1509 style = f" {style}" if style else "" 1510 partition = self.sql(expression, "partition") 1511 partition = f" {partition}" if partition else "" 1512 format = self.sql(expression, "format") 1513 format = f" {format}" if format else "" 1514 as_json = " AS JSON" if expression.args.get("as_json") else "" 1515 1516 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1517 1518 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1519 tag = self.sql(expression, "tag") 1520 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1521 1522 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1523 with_ = self.sql(expression, "with_") 1524 if with_: 1525 sql = f"{with_}{self.sep()}{sql}" 1526 return sql 1527 1528 def with_sql(self, expression: exp.With) -> str: 1529 udfs = self.expressions(expression, key="udfs", flat=True) 1530 udfs = f"WITH {udfs}" if udfs else "" 1531 1532 sql = self.expressions(expression, flat=True) 1533 1534 recursive = ( 1535 "RECURSIVE " 1536 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1537 else "" 1538 ) 1539 search = self.sql(expression, "search") 1540 search = f" {search}" if search else "" 1541 1542 sql = f"WITH {recursive}{sql}{search}" if sql else "" 1543 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}" 1544 1545 def cte_sql(self, expression: exp.CTE) -> str: 1546 alias = expression.args.get("alias") 1547 if alias: 1548 alias.add_comments(expression.pop_comments()) 1549 1550 alias_sql = self.sql(expression, "alias") 1551 1552 materialized = expression.args.get("materialized") 1553 if materialized is False: 1554 materialized = "NOT MATERIALIZED " 1555 elif materialized: 1556 materialized = "MATERIALIZED " 1557 1558 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1559 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1560 1561 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1562 1563 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1564 alias = self.sql(expression, "this") 1565 columns = self.expressions(expression, key="columns", flat=True) 1566 columns = f"({columns})" if columns else "" 1567 1568 if ( 1569 columns 1570 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1571 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1572 ): 1573 columns = "" 1574 self.unsupported("Named columns are not supported in table alias.") 1575 1576 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1577 alias = self._next_name() 1578 1579 return f"{alias}{columns}" 1580 1581 def bitstring_sql(self, expression: exp.BitString) -> str: 1582 this = self.sql(expression, "this") 1583 if self.dialect.BIT_START: 1584 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1585 return f"{int(this, 2)}" 1586 1587 def hexstring_sql( 1588 self, expression: exp.HexString, binary_function_repr: str | None = None 1589 ) -> str: 1590 this = self.sql(expression, "this") 1591 is_integer_type = expression.args.get("is_integer") 1592 1593 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1594 not self.dialect.HEX_START and not binary_function_repr 1595 ): 1596 # Integer representation will be returned if: 1597 # - The read dialect treats the hex value as integer literal but not the write 1598 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1599 return f"{int(this, 16)}" 1600 1601 if not is_integer_type: 1602 # Read dialect treats the hex value as BINARY/BLOB 1603 if binary_function_repr: 1604 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1605 return self.func(binary_function_repr, exp.Literal.string(this)) 1606 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1607 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1608 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1609 1610 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1611 1612 def bytestring_sql(self, expression: exp.ByteString) -> str: 1613 this = self.sql(expression, "this") 1614 if self.dialect.BYTE_START: 1615 escaped_byte_string = self.escape_str( 1616 this, 1617 escape_backslash=False, 1618 delimiter=self.dialect.BYTE_END, 1619 escaped_delimiter=self._escaped_byte_quote_end, 1620 is_byte_string=True, 1621 ) 1622 is_bytes = expression.args.get("is_bytes", False) 1623 delimited_byte_string = ( 1624 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1625 ) 1626 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1627 return self.sql( 1628 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1629 ) 1630 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1631 return self.sql( 1632 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1633 ) 1634 1635 return delimited_byte_string 1636 1637 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1638 return self.sql(exp.Literal.string(this)) 1639 1640 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1641 return "" 1642 1643 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1644 this = self.sql(expression, "this") 1645 escape = expression.args.get("escape") 1646 1647 if self.dialect.UNICODE_START: 1648 escape_substitute = r"\\\1" 1649 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1650 else: 1651 escape_substitute = r"\\u\1" 1652 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1653 1654 if escape: 1655 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1656 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1657 else: 1658 escape_pattern = ESCAPED_UNICODE_RE 1659 escape_sql = "" 1660 1661 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1662 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1663 1664 return f"{left_quote}{this}{right_quote}{escape_sql}" 1665 1666 def rawstring_sql(self, expression: exp.RawString) -> str: 1667 string = expression.this 1668 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1669 string = string.replace("\\", "\\\\") 1670 1671 string = self.escape_str(string, escape_backslash=False) 1672 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1673 1674 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1675 this = self.sql(expression, "this") 1676 specifier = self.sql(expression, "expression") 1677 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1678 return f"{this}{specifier}" 1679 1680 def datatype_param_bound_limiter( 1681 self, 1682 expression: exp.DataType, 1683 type_value: exp.DType, 1684 defaults: tuple[int, ...], 1685 bounds: tuple[int | None, ...], 1686 ) -> exp.DataType: 1687 params = expression.expressions 1688 1689 if not params: 1690 if defaults: 1691 expression.set( 1692 "expressions", 1693 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1694 ) 1695 return expression 1696 1697 if not bounds: 1698 return expression 1699 1700 for i, param in enumerate(params): 1701 bound = bounds[i] if i < len(bounds) else None 1702 if bound is None: 1703 continue 1704 1705 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1706 if ( 1707 isinstance(param_value, exp.Literal) 1708 and param_value.is_number 1709 and int(param_value.to_py()) > bound 1710 ): 1711 self.unsupported( 1712 f"{type_value.value} parameter {param_value.name} exceeds " 1713 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1714 ) 1715 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1716 1717 return expression 1718 1719 def datatype_sql(self, expression: exp.DataType) -> str: 1720 nested = "" 1721 values = "" 1722 1723 expr_nested = expression.args.get("nested") 1724 type_value = expression.this 1725 1726 if ( 1727 not expr_nested 1728 and isinstance(type_value, exp.DType) 1729 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1730 ): 1731 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1732 1733 interior = ( 1734 self.expressions( 1735 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1736 ) 1737 if expr_nested and self.pretty 1738 else self.expressions(expression, flat=True) 1739 ) 1740 1741 if type_value in self.UNSUPPORTED_TYPES: 1742 self.unsupported( 1743 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1744 ) 1745 1746 type_sql: t.Any = "" 1747 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1748 type_sql = self.sql(expression, "kind") 1749 elif type_value == exp.DType.CHARACTER_SET: 1750 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1751 else: 1752 type_sql = ( 1753 self.TYPE_MAPPING.get(type_value, type_value.value) 1754 if isinstance(type_value, exp.DType) 1755 else type_value 1756 ) 1757 1758 if interior: 1759 if expr_nested: 1760 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1761 if expression.args.get("values") is not None: 1762 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1763 values = self.expressions(expression, key="values", flat=True) 1764 values = f"{delimiters[0]}{values}{delimiters[1]}" 1765 elif type_value == exp.DType.INTERVAL: 1766 nested = f" {interior}" 1767 else: 1768 nested = f"({interior})" 1769 1770 type_sql = f"{type_sql}{nested}{values}" 1771 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1772 exp.DType.TIMETZ, 1773 exp.DType.TIMESTAMPTZ, 1774 ): 1775 type_sql = f"{type_sql} WITH TIME ZONE" 1776 1777 collate = self.sql(expression, "collate") 1778 if collate: 1779 type_sql = f"{type_sql} COLLATE {collate}" 1780 1781 return type_sql 1782 1783 def directory_sql(self, expression: exp.Directory) -> str: 1784 local = "LOCAL " if expression.args.get("local") else "" 1785 row_format = self.sql(expression, "row_format") 1786 row_format = f" {row_format}" if row_format else "" 1787 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1788 1789 def delete_sql(self, expression: exp.Delete) -> str: 1790 hint = self.sql(expression, "hint") 1791 this = self.sql(expression, "this") 1792 this = f" FROM {this}" if this else "" 1793 using = self.expressions(expression, key="using") 1794 using = f" USING {using}" if using else "" 1795 cluster = self.sql(expression, "cluster") 1796 cluster = f" {cluster}" if cluster else "" 1797 where = self.sql(expression, "where") 1798 returning = self.sql(expression, "returning") 1799 order = self.sql(expression, "order") 1800 limit = self.sql(expression, "limit") 1801 tables = self.expressions(expression, key="tables") 1802 tables = f" {tables}" if tables else "" 1803 if self.RETURNING_END: 1804 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1805 else: 1806 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1807 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1808 1809 def drop_sql(self, expression: exp.Drop) -> str: 1810 this = self.sql(expression, "this") 1811 expressions = self.expressions(expression, flat=True) 1812 expressions = f" ({expressions})" if expressions else "" 1813 kind = expression.args["kind"] 1814 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1815 iceberg = ( 1816 " ICEBERG" 1817 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1818 else "" 1819 ) 1820 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1821 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1822 on_cluster = self.sql(expression, "cluster") 1823 on_cluster = f" {on_cluster}" if on_cluster else "" 1824 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1825 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1826 cascade = " CASCADE" if expression.args.get("cascade") else "" 1827 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1828 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1829 purge = " PURGE" if expression.args.get("purge") else "" 1830 sync = " SYNC" if expression.args.get("sync") else "" 1831 force = " FORCE" if expression.args.get("force") else "" 1832 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}" 1833 1834 def set_operation(self, expression: exp.SetOperation) -> str: 1835 op_type = type(expression) 1836 op_name = op_type.key.upper() 1837 1838 distinct = expression.args.get("distinct") 1839 if ( 1840 distinct is False 1841 and op_type in (exp.Except, exp.Intersect) 1842 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1843 ): 1844 self.unsupported(f"{op_name} ALL is not supported") 1845 1846 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1847 1848 if distinct is None: 1849 distinct = default_distinct 1850 if distinct is None: 1851 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1852 1853 if distinct is default_distinct: 1854 distinct_or_all = "" 1855 else: 1856 distinct_or_all = " DISTINCT" if distinct else " ALL" 1857 1858 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1859 side_kind = f"{side_kind} " if side_kind else "" 1860 1861 by_name = " BY NAME" if expression.args.get("by_name") else "" 1862 on = self.expressions(expression, key="on", flat=True) 1863 on = f" ON ({on})" if on else "" 1864 1865 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1866 1867 def set_operations(self, expression: exp.SetOperation) -> str: 1868 if not self.SET_OP_MODIFIERS: 1869 limit = expression.args.get("limit") 1870 order = expression.args.get("order") 1871 1872 if limit or order: 1873 select = self._move_ctes_to_top_level( 1874 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1875 ) 1876 1877 if limit: 1878 select = select.limit(limit.pop(), copy=False) 1879 if order: 1880 select = select.order_by(order.pop(), copy=False) 1881 return self.sql(select) 1882 1883 sqls: list[str] = [] 1884 stack: list[str | exp.Expr] = [expression] 1885 1886 while stack: 1887 node = stack.pop() 1888 1889 if isinstance(node, exp.SetOperation): 1890 stack.append(node.expression) 1891 stack.append( 1892 self.maybe_comment( 1893 self.set_operation(node), comments=node.comments, separated=True 1894 ) 1895 ) 1896 stack.append(node.this) 1897 else: 1898 sqls.append(self.sql(node)) 1899 1900 this = self.sep().join(sqls) 1901 this = self.query_modifiers(expression, this) 1902 return self.prepend_ctes(expression, this) 1903 1904 def fetch_sql(self, expression: exp.Fetch) -> str: 1905 direction = expression.args.get("direction") 1906 direction = f" {direction}" if direction else "" 1907 count = self.sql(expression, "count") 1908 count = f" {count}" if count else "" 1909 limit_options = self.sql(expression, "limit_options") 1910 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1911 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1912 1913 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1914 percent = " PERCENT" if expression.args.get("percent") else "" 1915 rows = " ROWS" if expression.args.get("rows") else "" 1916 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1917 if not with_ties and rows: 1918 with_ties = " ONLY" 1919 return f"{percent}{rows}{with_ties}" 1920 1921 def filter_sql(self, expression: exp.Filter) -> str: 1922 this = self.sql(expression, "this") 1923 where = self.sql(expression, "expression").strip() 1924 return f"{this} FILTER({where})" 1925 1926 def hint_sql(self, expression: exp.Hint) -> str: 1927 if not self.QUERY_HINTS: 1928 self.unsupported("Hints are not supported") 1929 return "" 1930 1931 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1932 1933 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1934 using = self.sql(expression, "using") 1935 using = f" USING {using}" if using else "" 1936 columns = self.expressions(expression, key="columns", flat=True) 1937 columns = f"({columns})" if columns else "" 1938 partition_by = self.expressions(expression, key="partition_by", flat=True) 1939 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1940 where = self.sql(expression, "where") 1941 include = self.expressions(expression, key="include", flat=True) 1942 if include: 1943 include = f" INCLUDE ({include})" 1944 with_storage = self.expressions(expression, key="with_storage", flat=True) 1945 with_storage = f" WITH ({with_storage})" if with_storage else "" 1946 tablespace = self.sql(expression, "tablespace") 1947 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1948 on = self.sql(expression, "on") 1949 on = f" ON {on}" if on else "" 1950 1951 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1952 1953 def index_sql(self, expression: exp.Index) -> str: 1954 unique = "UNIQUE " if expression.args.get("unique") else "" 1955 primary = "PRIMARY " if expression.args.get("primary") else "" 1956 amp = "AMP " if expression.args.get("amp") else "" 1957 name = self.sql(expression, "this") 1958 name = f"{name} " if name else "" 1959 table = self.sql(expression, "table") 1960 table = f"{self.INDEX_ON} {table}" if table else "" 1961 1962 index = "INDEX " if not table else "" 1963 1964 params = self.sql(expression, "params") 1965 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1966 1967 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 1968 this = expression.this 1969 if this and this.is_string: 1970 resolved = maybe_parse(this.name).sql(self.dialect) 1971 if "expressions" in expression.args: 1972 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 1973 # We can't safely emit the call to other dialects since name/arg semantics may differ 1974 self.unsupported( 1975 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 1976 ) 1977 return resolved 1978 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 1979 return self.func("IDENTIFIER", this) 1980 1981 def identifier_sql(self, expression: exp.Identifier) -> str: 1982 text = expression.name 1983 lower = text.lower() 1984 quoted = expression.quoted 1985 text = lower if self.normalize and not quoted else text 1986 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1987 if ( 1988 quoted 1989 or self.dialect.can_quote(expression, self.identify) 1990 or lower in self.RESERVED_KEYWORDS 1991 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1992 ): 1993 text = ( 1994 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1995 ) 1996 return text 1997 1998 def hex_sql(self, expression: exp.Hex) -> str: 1999 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2000 if self.dialect.HEX_LOWERCASE: 2001 text = self.func("LOWER", text) 2002 2003 return text 2004 2005 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 2006 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 2007 if not self.dialect.HEX_LOWERCASE: 2008 text = self.func("LOWER", text) 2009 return text 2010 2011 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2012 input_format = self.sql(expression, "input_format") 2013 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2014 output_format = self.sql(expression, "output_format") 2015 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2016 return self.sep().join((input_format, output_format)) 2017 2018 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 2019 string = self.sql(exp.Literal.string(expression.name)) 2020 return f"{prefix}{string}" 2021 2022 def partition_sql(self, expression: exp.Partition) -> str: 2023 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 2024 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 2025 2026 def properties_sql(self, expression: exp.Properties) -> str: 2027 root_properties = [] 2028 with_properties = [] 2029 2030 for p in expression.expressions: 2031 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2032 if p_loc == exp.Properties.Location.POST_WITH: 2033 with_properties.append(p) 2034 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2035 root_properties.append(p) 2036 2037 root_props_ast = exp.Properties(expressions=root_properties) 2038 root_props_ast.parent = expression.parent 2039 2040 with_props_ast = exp.Properties(expressions=with_properties) 2041 with_props_ast.parent = expression.parent 2042 2043 root_props = self.root_properties(root_props_ast) 2044 with_props = self.with_properties(with_props_ast) 2045 2046 if root_props and with_props and not self.pretty: 2047 with_props = " " + with_props 2048 2049 return root_props + with_props 2050 2051 def root_properties(self, properties: exp.Properties) -> str: 2052 if properties.expressions: 2053 return self.expressions(properties, indent=False, sep=" ") 2054 return "" 2055 2056 def properties( 2057 self, 2058 properties: exp.Properties, 2059 prefix: str = "", 2060 sep: str = ", ", 2061 suffix: str = "", 2062 wrapped: bool = True, 2063 ) -> str: 2064 if properties.expressions: 2065 expressions = self.expressions(properties, sep=sep, indent=False) 2066 if expressions: 2067 expressions = self.wrap(expressions) if wrapped else expressions 2068 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2069 return "" 2070 2071 def with_properties(self, properties: exp.Properties) -> str: 2072 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2073 2074 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2075 properties_locs = defaultdict(list) 2076 for p in properties.expressions: 2077 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2078 if p_loc != exp.Properties.Location.UNSUPPORTED: 2079 properties_locs[p_loc].append(p) 2080 else: 2081 self.unsupported(f"Unsupported property {p.key}") 2082 2083 return properties_locs 2084 2085 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2086 if isinstance(expression.this, exp.Dot): 2087 return self.sql(expression, "this") 2088 return f"'{expression.name}'" if string_key else expression.name 2089 2090 def property_sql(self, expression: exp.Property) -> str: 2091 property_cls = expression.__class__ 2092 if property_cls == exp.Property: 2093 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2094 2095 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2096 if not property_name: 2097 self.unsupported(f"Unsupported property {expression.key}") 2098 2099 return f"{property_name}={self.sql(expression, 'this')}" 2100 2101 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2102 return f"UUID {self.sql(expression, 'this')}" 2103 2104 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2105 if self.SUPPORTS_CREATE_TABLE_LIKE: 2106 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2107 options = f" {options}" if options else "" 2108 2109 like = f"LIKE {self.sql(expression, 'this')}{options}" 2110 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2111 like = f"({like})" 2112 2113 return like 2114 2115 if expression.expressions: 2116 self.unsupported("Transpilation of LIKE property options is unsupported") 2117 2118 select = exp.select("*").from_(expression.this).limit(0) 2119 return f"AS {self.sql(select)}" 2120 2121 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2122 no = "NO " if expression.args.get("no") else "" 2123 protection = " PROTECTION" if expression.args.get("protection") else "" 2124 return f"{no}FALLBACK{protection}" 2125 2126 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2127 no = "NO " if expression.args.get("no") else "" 2128 local = expression.args.get("local") 2129 local = f"{local} " if local else "" 2130 dual = "DUAL " if expression.args.get("dual") else "" 2131 before = "BEFORE " if expression.args.get("before") else "" 2132 after = "AFTER " if expression.args.get("after") else "" 2133 return f"{no}{local}{dual}{before}{after}JOURNAL" 2134 2135 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2136 freespace = self.sql(expression, "this") 2137 percent = " PERCENT" if expression.args.get("percent") else "" 2138 return f"FREESPACE={freespace}{percent}" 2139 2140 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2141 if expression.args.get("default"): 2142 property = "DEFAULT" 2143 elif expression.args.get("on"): 2144 property = "ON" 2145 else: 2146 property = "OFF" 2147 return f"CHECKSUM={property}" 2148 2149 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2150 if expression.args.get("no"): 2151 return "NO MERGEBLOCKRATIO" 2152 if expression.args.get("default"): 2153 return "DEFAULT MERGEBLOCKRATIO" 2154 2155 percent = " PERCENT" if expression.args.get("percent") else "" 2156 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2157 2158 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2159 expressions = self.expressions(expression, flat=True) 2160 expressions = f"({expressions})" if expressions else "" 2161 return f"USING {self.sql(expression, 'this')}{expressions}" 2162 2163 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2164 default = expression.args.get("default") 2165 minimum = expression.args.get("minimum") 2166 maximum = expression.args.get("maximum") 2167 if default or minimum or maximum: 2168 if default: 2169 prop = "DEFAULT" 2170 elif minimum: 2171 prop = "MINIMUM" 2172 else: 2173 prop = "MAXIMUM" 2174 return f"{prop} DATABLOCKSIZE" 2175 units = expression.args.get("units") 2176 units = f" {units}" if units else "" 2177 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2178 2179 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2180 autotemp = expression.args.get("autotemp") 2181 always = expression.args.get("always") 2182 default = expression.args.get("default") 2183 manual = expression.args.get("manual") 2184 never = expression.args.get("never") 2185 2186 if autotemp is not None: 2187 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2188 elif always: 2189 prop = "ALWAYS" 2190 elif default: 2191 prop = "DEFAULT" 2192 elif manual: 2193 prop = "MANUAL" 2194 elif never: 2195 prop = "NEVER" 2196 return f"BLOCKCOMPRESSION={prop}" 2197 2198 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2199 no = expression.args.get("no") 2200 no = " NO" if no else "" 2201 concurrent = expression.args.get("concurrent") 2202 concurrent = " CONCURRENT" if concurrent else "" 2203 target = self.sql(expression, "target") 2204 target = f" {target}" if target else "" 2205 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2206 2207 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2208 if isinstance(expression.this, list): 2209 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2210 if expression.this: 2211 modulus = self.sql(expression, "this") 2212 remainder = self.sql(expression, "expression") 2213 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2214 2215 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2216 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2217 return f"FROM ({from_expressions}) TO ({to_expressions})" 2218 2219 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2220 this = self.sql(expression, "this") 2221 2222 for_values_or_default = expression.expression 2223 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2224 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2225 else: 2226 for_values_or_default = " DEFAULT" 2227 2228 return f"PARTITION OF {this}{for_values_or_default}" 2229 2230 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2231 kind = expression.args.get("kind") 2232 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2233 for_or_in = expression.args.get("for_or_in") 2234 for_or_in = f" {for_or_in}" if for_or_in else "" 2235 lock_type = expression.args.get("lock_type") 2236 override = " OVERRIDE" if expression.args.get("override") else "" 2237 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2238 2239 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2240 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2241 statistics = expression.args.get("statistics") 2242 statistics_sql = "" 2243 if statistics is not None: 2244 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2245 return f"{data_sql}{statistics_sql}" 2246 2247 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2248 this = self.sql(expression, "this") 2249 this = f"HISTORY_TABLE={this}" if this else "" 2250 data_consistency: str | None = self.sql(expression, "data_consistency") 2251 data_consistency = ( 2252 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2253 ) 2254 retention_period: str | None = self.sql(expression, "retention_period") 2255 retention_period = ( 2256 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2257 ) 2258 2259 if this: 2260 on_sql = self.func("ON", this, data_consistency, retention_period) 2261 else: 2262 on_sql = "ON" if expression.args.get("on") else "OFF" 2263 2264 sql = f"SYSTEM_VERSIONING={on_sql}" 2265 2266 return f"WITH({sql})" if expression.args.get("with_") else sql 2267 2268 def insert_sql(self, expression: exp.Insert) -> str: 2269 hint = self.sql(expression, "hint") 2270 overwrite = expression.args.get("overwrite") 2271 2272 if isinstance(expression.this, exp.Directory): 2273 this = " OVERWRITE" if overwrite else " INTO" 2274 else: 2275 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2276 2277 stored = self.sql(expression, "stored") 2278 stored = f" {stored}" if stored else "" 2279 alternative = expression.args.get("alternative") 2280 alternative = f" OR {alternative}" if alternative else "" 2281 ignore = " IGNORE" if expression.args.get("ignore") else "" 2282 is_function = expression.args.get("is_function") 2283 if is_function: 2284 this = f"{this} FUNCTION" 2285 this = f"{this} {self.sql(expression, 'this')}" 2286 2287 exists = " IF EXISTS" if expression.args.get("exists") else "" 2288 where = self.sql(expression, "where") 2289 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2290 using = self.expressions(expression, key="using", flat=True) 2291 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2292 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2293 on_conflict = self.sql(expression, "conflict") 2294 on_conflict = f" {on_conflict}" if on_conflict else "" 2295 by_name = " BY NAME" if expression.args.get("by_name") else "" 2296 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2297 returning = self.sql(expression, "returning") 2298 2299 if self.RETURNING_END: 2300 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2301 else: 2302 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2303 2304 partition_by = self.sql(expression, "partition") 2305 partition_by = f" {partition_by}" if partition_by else "" 2306 settings = self.sql(expression, "settings") 2307 settings = f" {settings}" if settings else "" 2308 2309 source = self.sql(expression, "source") 2310 source = f"TABLE {source}" if source else "" 2311 2312 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2313 return self.prepend_ctes(expression, sql) 2314 2315 def introducer_sql(self, expression: exp.Introducer) -> str: 2316 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2317 2318 def kill_sql(self, expression: exp.Kill) -> str: 2319 kind = self.sql(expression, "kind") 2320 kind = f" {kind}" if kind else "" 2321 this = self.sql(expression, "this") 2322 this = f" {this}" if this else "" 2323 return f"KILL{kind}{this}" 2324 2325 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2326 return expression.name 2327 2328 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2329 return expression.name 2330 2331 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2332 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2333 2334 constraint = self.sql(expression, "constraint") 2335 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2336 2337 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2338 if conflict_keys: 2339 conflict_keys = f"({conflict_keys})" 2340 2341 index_predicate = self.sql(expression, "index_predicate") 2342 conflict_keys = f"{conflict_keys}{index_predicate} " 2343 2344 action = self.sql(expression, "action") 2345 2346 expressions = self.expressions(expression, flat=True) 2347 if expressions: 2348 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2349 expressions = f" {set_keyword}{expressions}" 2350 2351 where = self.sql(expression, "where") 2352 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2353 2354 def returning_sql(self, expression: exp.Returning) -> str: 2355 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2356 2357 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2358 fields = self.sql(expression, "fields") 2359 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2360 escaped = self.sql(expression, "escaped") 2361 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2362 items = self.sql(expression, "collection_items") 2363 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2364 keys = self.sql(expression, "map_keys") 2365 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2366 lines = self.sql(expression, "lines") 2367 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2368 null = self.sql(expression, "null") 2369 null = f" NULL DEFINED AS {null}" if null else "" 2370 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2371 2372 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2373 return f"WITH ({self.expressions(expression, flat=True)})" 2374 2375 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2376 this = f"{self.sql(expression, 'this')} INDEX" 2377 target = self.sql(expression, "target") 2378 target = f" FOR {target}" if target else "" 2379 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2380 2381 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2382 this = self.sql(expression, "this") 2383 kind = self.sql(expression, "kind") 2384 expr = self.sql(expression, "expression") 2385 return f"{this} ({kind} => {expr})" 2386 2387 def table_parts(self, expression: exp.Table) -> str: 2388 return ".".join( 2389 self.sql(part) 2390 for part in ( 2391 expression.args.get("catalog"), 2392 expression.args.get("db"), 2393 expression.args.get("this"), 2394 ) 2395 if part is not None 2396 ) 2397 2398 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2399 table = self.table_parts(expression) 2400 only = "ONLY " if expression.args.get("only") else "" 2401 partition = self.sql(expression, "partition") 2402 partition = f" {partition}" if partition else "" 2403 version = self.sql(expression, "version") 2404 version = f" {version}" if version else "" 2405 alias = self.sql(expression, "alias") 2406 alias = f"{sep}{alias}" if alias else "" 2407 2408 sample = self.sql(expression, "sample") 2409 post_alias = "" 2410 pre_alias = "" 2411 2412 if self.dialect.ALIAS_POST_TABLESAMPLE: 2413 pre_alias = sample 2414 else: 2415 post_alias = sample 2416 2417 if self.dialect.ALIAS_POST_VERSION: 2418 pre_alias = f"{pre_alias}{version}" 2419 else: 2420 post_alias = f"{post_alias}{version}" 2421 2422 hints = self.expressions(expression, key="hints", sep=" ") 2423 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2424 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2425 joins = self.indent( 2426 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2427 ) 2428 laterals = self.expressions(expression, key="laterals", sep="") 2429 2430 file_format = self.sql(expression, "format") 2431 pattern = self.sql(expression, "pattern") 2432 if file_format: 2433 pattern = f", PATTERN => {pattern}" if pattern else "" 2434 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2435 elif pattern: 2436 file_format = f" (PATTERN => {pattern})" 2437 2438 ordinality = expression.args.get("ordinality") or "" 2439 if ordinality: 2440 ordinality = f" WITH ORDINALITY{alias}" 2441 alias = "" 2442 2443 when = self.sql(expression, "when") 2444 if when: 2445 if self.HISTORICAL_DATA_POST_ALIAS: 2446 alias = f"{alias} {when}" 2447 else: 2448 table = f"{table} {when}" 2449 2450 changes = self.sql(expression, "changes") 2451 changes = f" {changes}" if changes else "" 2452 2453 rows_from = self.expressions(expression, key="rows_from") 2454 if rows_from: 2455 table = f"ROWS FROM {self.wrap(rows_from)}" 2456 2457 indexed = expression.args.get("indexed") 2458 if indexed is not None: 2459 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2460 else: 2461 indexed = "" 2462 2463 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2464 2465 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2466 table = self.func("TABLE", expression.this) 2467 alias = self.sql(expression, "alias") 2468 alias = f" AS {alias}" if alias else "" 2469 sample = self.sql(expression, "sample") 2470 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2471 joins = self.indent( 2472 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2473 ) 2474 return f"{table}{alias}{pivots}{sample}{joins}" 2475 2476 def tablesample_sql( 2477 self, 2478 expression: exp.TableSample, 2479 tablesample_keyword: str | None = None, 2480 ) -> str: 2481 method = self.sql(expression, "method") 2482 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2483 numerator = self.sql(expression, "bucket_numerator") 2484 denominator = self.sql(expression, "bucket_denominator") 2485 field = self.sql(expression, "bucket_field") 2486 field = f" ON {field}" if field else "" 2487 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2488 seed = self.sql(expression, "seed") 2489 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2490 2491 size = self.sql(expression, "size") 2492 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2493 size = f"{size} ROWS" 2494 2495 percent = self.sql(expression, "percent") 2496 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2497 percent = f"{percent} PERCENT" 2498 2499 expr = f"{bucket}{percent}{size}" 2500 if self.TABLESAMPLE_REQUIRES_PARENS: 2501 expr = f"({expr})" 2502 2503 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2504 2505 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2506 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2507 # the stored column name differs from the target dialect's natural output. 2508 columns = expression.args.get("columns") 2509 if not columns or len(expression.fields) != 1: 2510 return None 2511 2512 args = expression.args 2513 parser_cls = self.dialect.parser_class 2514 2515 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2516 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2517 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2518 2519 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2520 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2521 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2522 2523 if ( 2524 src_identify_pivot_strings == tgt_identify_pivot_strings 2525 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2526 and src_pivot_column_naming == tgt_pivot_column_naming 2527 ): 2528 return None 2529 2530 in_exprs = expression.fields[0].expressions 2531 step = len(columns) // len(in_exprs) 2532 2533 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2534 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2535 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2536 first_stored = columns[0].name 2537 2538 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2539 if not first_stored.startswith(first_base): 2540 return None 2541 2542 suffix = first_stored[len(first_base) :] 2543 2544 # Whether the target dialect would append an agg-name suffix for this pivot. 2545 # Spark single-agg uniquely drops the agg alias entirely. 2546 target_has_suffix = ( 2547 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2548 ) and any(a.alias for a in expression.expressions) 2549 source_has_suffix = suffix != "" 2550 2551 new_exprs: list[exp.Expression] = [] 2552 modified = False 2553 for val_idx, e in enumerate(in_exprs): 2554 if isinstance(e, exp.PivotAlias): 2555 new_exprs.append(e) 2556 continue 2557 2558 i = val_idx * step 2559 stored_full = columns[i].name 2560 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2561 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2562 2563 # Source had a suffix, but target won't apply one 2564 if source_has_suffix and not target_has_suffix: 2565 new_exprs.append( 2566 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2567 ) 2568 modified = True 2569 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2570 elif stored_value != target_value: 2571 new_exprs.append( 2572 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2573 ) 2574 modified = True 2575 else: 2576 new_exprs.append(e) 2577 2578 return new_exprs if modified else None 2579 2580 def pivot_sql(self, expression: exp.Pivot) -> str: 2581 expressions = self.expressions(expression, flat=True) 2582 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2583 2584 group = self.sql(expression, "group") 2585 2586 if expression.this: 2587 this = self.sql(expression, "this") 2588 if not expressions: 2589 sql = f"UNPIVOT {this}" 2590 else: 2591 on = f"{self.seg('ON')} {expressions}" 2592 into = self.sql(expression, "into") 2593 into = f"{self.seg('INTO')} {into}" if into else "" 2594 using = self.expressions(expression, key="using", flat=True) 2595 using = f"{self.seg('USING')} {using}" if using else "" 2596 sql = f"{direction} {this}{on}{into}{using}{group}" 2597 return self.prepend_ctes(expression, sql) 2598 2599 if not expression.unpivot: 2600 # Wrap IN-list values with explicit aliases where the target dialect would differ 2601 new_field_exprs = self._pivot_in_value_aliases(expression) 2602 if new_field_exprs is not None: 2603 expression.fields[0].set("expressions", new_field_exprs) 2604 2605 alias = self.sql(expression, "alias") 2606 if alias: 2607 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2608 2609 fields = self.expressions( 2610 expression, 2611 "fields", 2612 sep=" ", 2613 dynamic=True, 2614 new_line=True, 2615 skip_first=True, 2616 skip_last=True, 2617 ) 2618 2619 include_nulls = expression.args.get("include_nulls") 2620 if include_nulls is not None: 2621 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2622 else: 2623 nulls = "" 2624 2625 default_on_null = self.sql(expression, "default_on_null") 2626 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2627 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2628 return self.prepend_ctes(expression, sql) 2629 2630 def version_sql(self, expression: exp.Version) -> str: 2631 this = f"FOR {expression.name}" 2632 kind = expression.text("kind") 2633 expr = self.sql(expression, "expression") 2634 return f"{this} {kind} {expr}" 2635 2636 def tuple_sql(self, expression: exp.Tuple) -> str: 2637 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2638 2639 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2640 """ 2641 Returns (join_sql, from_sql) for UPDATE statements. 2642 - join_sql: placed after UPDATE table, before SET 2643 - from_sql: placed after SET clause (standard position) 2644 Dialects like MySQL need to convert FROM to JOIN syntax. 2645 """ 2646 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2647 return ("", self.sql(expression, "from_")) 2648 2649 # Qualify unqualified columns in SET clause with the target table 2650 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2651 target_table = expression.this 2652 if isinstance(target_table, exp.Table): 2653 target_name = exp.to_identifier(target_table.alias_or_name) 2654 for eq in expression.expressions: 2655 col = eq.this 2656 if isinstance(col, exp.Column) and not col.table: 2657 col.set("table", target_name) 2658 2659 table = from_expr.this 2660 if nested_joins := table.args.get("joins", []): 2661 table.set("joins", None) 2662 2663 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2664 for nested in nested_joins: 2665 if not nested.args.get("on") and not nested.args.get("using"): 2666 nested.set("on", exp.true()) 2667 join_sql += self.sql(nested) 2668 2669 return (join_sql, "") 2670 2671 def update_sql(self, expression: exp.Update) -> str: 2672 hint = self.sql(expression, "hint") 2673 this = self.sql(expression, "this") 2674 join_sql, from_sql = self._update_from_joins_sql(expression) 2675 set_sql = self.expressions(expression, flat=True) 2676 where_sql = self.sql(expression, "where") 2677 returning = self.sql(expression, "returning") 2678 order = self.sql(expression, "order") 2679 limit = self.sql(expression, "limit") 2680 if self.RETURNING_END: 2681 expression_sql = f"{from_sql}{where_sql}{returning}" 2682 else: 2683 expression_sql = f"{returning}{from_sql}{where_sql}" 2684 options = self.expressions(expression, key="options") 2685 options = f" OPTION({options})" if options else "" 2686 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2687 return self.prepend_ctes(expression, sql) 2688 2689 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2690 values_as_table = values_as_table and self.VALUES_AS_TABLE 2691 2692 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2693 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2694 args = self.expressions(expression) 2695 alias = self.sql(expression, "alias") 2696 values = f"VALUES{self.seg('')}{args}" 2697 values = ( 2698 f"({values})" 2699 if self.WRAP_DERIVED_VALUES 2700 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2701 else values 2702 ) 2703 values = self.query_modifiers(expression, values) 2704 return f"{values} AS {alias}" if alias else values 2705 2706 # Converts `VALUES...` expression into a series of select unions. 2707 alias_node = expression.args.get("alias") 2708 column_names = alias_node and alias_node.columns 2709 2710 selects: list[exp.Query] = [] 2711 2712 for i, tup in enumerate(expression.expressions): 2713 row = tup.expressions 2714 2715 if i == 0 and column_names: 2716 row = [ 2717 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2718 ] 2719 2720 selects.append(exp.Select(expressions=row)) 2721 2722 if self.pretty: 2723 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2724 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2725 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2726 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2727 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2728 2729 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2730 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2731 return f"({unions}){alias}" 2732 2733 def var_sql(self, expression: exp.Var) -> str: 2734 return self.sql(expression, "this") 2735 2736 @unsupported_args("expressions") 2737 def into_sql(self, expression: exp.Into) -> str: 2738 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2739 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2740 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2741 2742 def from_sql(self, expression: exp.From) -> str: 2743 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2744 2745 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2746 grouping_sets = self.expressions(expression, indent=False) 2747 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2748 2749 def rollup_sql(self, expression: exp.Rollup) -> str: 2750 expressions = self.expressions(expression, indent=False) 2751 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2752 2753 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2754 this = self.sql(expression, "this") 2755 2756 columns = self.expressions(expression, flat=True) 2757 2758 from_sql = self.sql(expression, "from_index") 2759 from_sql = f" FROM {from_sql}" if from_sql else "" 2760 2761 properties = expression.args.get("properties") 2762 properties_sql = ( 2763 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2764 ) 2765 2766 return f"{this}({columns}){from_sql}{properties_sql}" 2767 2768 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2769 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2770 2771 def cube_sql(self, expression: exp.Cube) -> str: 2772 expressions = self.expressions(expression, indent=False) 2773 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2774 2775 def group_sql(self, expression: exp.Group) -> str: 2776 group_by_all = expression.args.get("all") 2777 if group_by_all is True: 2778 modifier = " ALL" 2779 elif group_by_all is False: 2780 modifier = " DISTINCT" 2781 else: 2782 modifier = "" 2783 2784 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2785 2786 grouping_sets = self.expressions(expression, key="grouping_sets") 2787 cube = self.expressions(expression, key="cube") 2788 rollup = self.expressions(expression, key="rollup") 2789 2790 groupings = csv( 2791 self.seg(grouping_sets) if grouping_sets else "", 2792 self.seg(cube) if cube else "", 2793 self.seg(rollup) if rollup else "", 2794 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2795 sep=self.GROUPINGS_SEP, 2796 ) 2797 2798 if ( 2799 expression.expressions 2800 and groupings 2801 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2802 ): 2803 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2804 2805 return f"{group_by}{groupings}" 2806 2807 def having_sql(self, expression: exp.Having) -> str: 2808 this = self.indent(self.sql(expression, "this")) 2809 return f"{self.seg('HAVING')}{self.sep()}{this}" 2810 2811 def connect_sql(self, expression: exp.Connect) -> str: 2812 start = self.sql(expression, "start") 2813 start = self.seg(f"START WITH {start}") if start else "" 2814 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2815 connect = self.sql(expression, "connect") 2816 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2817 return start + connect 2818 2819 def prior_sql(self, expression: exp.Prior) -> str: 2820 return f"PRIOR {self.sql(expression, 'this')}" 2821 2822 def join_sql(self, expression: exp.Join) -> str: 2823 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2824 side = None 2825 else: 2826 side = expression.side 2827 2828 op_sql = " ".join( 2829 op 2830 for op in ( 2831 expression.method, 2832 "GLOBAL" if expression.args.get("global_") else None, 2833 side, 2834 expression.kind, 2835 expression.hint if self.JOIN_HINTS else None, 2836 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2837 ) 2838 if op 2839 ) 2840 match_cond = self.sql(expression, "match_condition") 2841 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2842 on_sql = self.sql(expression, "on") 2843 using = expression.args.get("using") 2844 2845 if not on_sql and using: 2846 on_sql = csv(*(self.sql(column) for column in using)) 2847 2848 this = expression.this 2849 this_sql = self.sql(this) 2850 2851 exprs = self.expressions(expression) 2852 if exprs: 2853 this_sql = f"{this_sql},{self.seg(exprs)}" 2854 2855 if on_sql: 2856 on_sql = self.indent(on_sql, skip_first=True) 2857 space = self.seg(" " * self.pad) if self.pretty else " " 2858 if using: 2859 on_sql = f"{space}USING ({on_sql})" 2860 else: 2861 on_sql = f"{space}ON {on_sql}" 2862 elif not op_sql: 2863 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2864 return f" {this_sql}" 2865 2866 return f", {this_sql}" 2867 2868 if op_sql != "STRAIGHT_JOIN": 2869 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2870 2871 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2872 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2873 2874 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2875 args = self.expressions(expression, flat=True) 2876 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2877 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2878 2879 def lateral_op(self, expression: exp.Lateral) -> str: 2880 cross_apply = expression.args.get("cross_apply") 2881 2882 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2883 if cross_apply is True: 2884 op = "INNER JOIN " 2885 elif cross_apply is False: 2886 op = "LEFT JOIN " 2887 else: 2888 op = "" 2889 2890 return f"{op}LATERAL" 2891 2892 def lateral_sql(self, expression: exp.Lateral) -> str: 2893 this = self.sql(expression, "this") 2894 2895 if expression.args.get("view"): 2896 alias = expression.args["alias"] 2897 columns = self.expressions(alias, key="columns", flat=True) 2898 table = f" {alias.name}" if alias.name else "" 2899 columns = f" AS {columns}" if columns else "" 2900 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2901 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2902 2903 alias = self.sql(expression, "alias") 2904 alias = f" AS {alias}" if alias else "" 2905 2906 ordinality = expression.args.get("ordinality") or "" 2907 if ordinality: 2908 ordinality = f" WITH ORDINALITY{alias}" 2909 alias = "" 2910 2911 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2912 2913 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2914 this = self.sql(expression, "this") 2915 2916 args = [ 2917 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2918 for e in (expression.args.get(k) for k in ("offset", "expression")) 2919 if e 2920 ] 2921 2922 args_sql = ", ".join(self.sql(e) for e in args) 2923 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2924 expressions = self.expressions(expression, flat=True) 2925 limit_options = self.sql(expression, "limit_options") 2926 expressions = f" BY {expressions}" if expressions else "" 2927 2928 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2929 2930 def offset_sql(self, expression: exp.Offset) -> str: 2931 this = self.sql(expression, "this") 2932 value = expression.expression 2933 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2934 expressions = self.expressions(expression, flat=True) 2935 expressions = f" BY {expressions}" if expressions else "" 2936 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2937 2938 def setitem_sql(self, expression: exp.SetItem) -> str: 2939 kind = self.sql(expression, "kind") 2940 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2941 kind = "" 2942 else: 2943 kind = f"{kind} " if kind else "" 2944 this = self.sql(expression, "this") 2945 expressions = self.expressions(expression) 2946 collate = self.sql(expression, "collate") 2947 collate = f" COLLATE {collate}" if collate else "" 2948 global_ = "GLOBAL " if expression.args.get("global_") else "" 2949 return f"{global_}{kind}{this}{expressions}{collate}" 2950 2951 def set_sql(self, expression: exp.Set) -> str: 2952 expressions = f" {self.expressions(expression, flat=True)}" 2953 tag = " TAG" if expression.args.get("tag") else "" 2954 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2955 2956 def queryband_sql(self, expression: exp.QueryBand) -> str: 2957 this = self.sql(expression, "this") 2958 update = " UPDATE" if expression.args.get("update") else "" 2959 scope = self.sql(expression, "scope") 2960 scope = f" FOR {scope}" if scope else "" 2961 2962 return f"QUERY_BAND = {this}{update}{scope}" 2963 2964 def pragma_sql(self, expression: exp.Pragma) -> str: 2965 return f"PRAGMA {self.sql(expression, 'this')}" 2966 2967 def lock_sql(self, expression: exp.Lock) -> str: 2968 if not self.LOCKING_READS_SUPPORTED: 2969 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2970 return "" 2971 2972 update = expression.args["update"] 2973 key = expression.args.get("key") 2974 if update: 2975 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2976 else: 2977 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2978 expressions = self.expressions(expression, flat=True) 2979 expressions = f" OF {expressions}" if expressions else "" 2980 wait = expression.args.get("wait") 2981 2982 if wait is not None: 2983 if isinstance(wait, exp.Literal): 2984 wait = f" WAIT {self.sql(wait)}" 2985 else: 2986 wait = " NOWAIT" if wait else " SKIP LOCKED" 2987 2988 return f"{lock_type}{expressions}{wait or ''}" 2989 2990 def literal_sql(self, expression: exp.Literal) -> str: 2991 text = expression.this or "" 2992 if expression.is_string: 2993 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2994 return text 2995 2996 def escape_str( 2997 self, 2998 text: str, 2999 escape_backslash: bool = True, 3000 delimiter: str | None = None, 3001 escaped_delimiter: str | None = None, 3002 is_byte_string: bool = False, 3003 ) -> str: 3004 if is_byte_string: 3005 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3006 else: 3007 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3008 3009 if supports_escape_sequences: 3010 text = "".join( 3011 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3012 for ch in text 3013 ) 3014 3015 delimiter = delimiter or self.dialect.QUOTE_END 3016 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3017 3018 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 3019 3020 def loaddata_sql(self, expression: exp.LoadData) -> str: 3021 is_overwrite = expression.args.get("overwrite") 3022 overwrite = " OVERWRITE" if is_overwrite else "" 3023 this = self.sql(expression, "this") 3024 3025 files = expression.args.get("files") 3026 if files: 3027 files_sql = self.expressions(files, flat=True) 3028 files_sql = f"FILES{self.wrap(files_sql)}" 3029 if is_overwrite: 3030 this = f" {this}" 3031 elif expression.args.get("temp"): 3032 this = f" INTO TEMP TABLE {this}" 3033 else: 3034 this = f" INTO TABLE {this}" 3035 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3036 3037 local = " LOCAL" if expression.args.get("local") else "" 3038 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3039 this = f" INTO TABLE {this}" 3040 partition = self.sql(expression, "partition") 3041 partition = f" {partition}" if partition else "" 3042 input_format = self.sql(expression, "input_format") 3043 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3044 serde = self.sql(expression, "serde") 3045 serde = f" SERDE {serde}" if serde else "" 3046 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3047 3048 def null_sql(self, *_) -> str: 3049 return "NULL" 3050 3051 def boolean_sql(self, expression: exp.Boolean) -> str: 3052 return "TRUE" if expression.this else "FALSE" 3053 3054 def booland_sql(self, expression: exp.Booland) -> str: 3055 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3056 3057 def boolor_sql(self, expression: exp.Boolor) -> str: 3058 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3059 3060 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3061 this = self.sql(expression, "this") 3062 this = f"{this} " if this else this 3063 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3064 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3065 3066 def withfill_sql(self, expression: exp.WithFill) -> str: 3067 from_sql = self.sql(expression, "from_") 3068 from_sql = f" FROM {from_sql}" if from_sql else "" 3069 to_sql = self.sql(expression, "to") 3070 to_sql = f" TO {to_sql}" if to_sql else "" 3071 step_sql = self.sql(expression, "step") 3072 step_sql = f" STEP {step_sql}" if step_sql else "" 3073 interpolated_values = [ 3074 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3075 if isinstance(e, exp.Alias) 3076 else self.sql(e, "this") 3077 for e in expression.args.get("interpolate") or [] 3078 ] 3079 interpolate = ( 3080 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3081 ) 3082 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3083 3084 def cluster_sql(self, expression: exp.Cluster) -> str: 3085 return self.op_expressions("CLUSTER BY", expression) 3086 3087 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3088 if expression.this: 3089 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3090 return "" 3091 expressions = self.expressions(expression, flat=True) 3092 return f"CLUSTER BY ({expressions})" 3093 3094 def distribute_sql(self, expression: exp.Distribute) -> str: 3095 return self.op_expressions("DISTRIBUTE BY", expression) 3096 3097 def sort_sql(self, expression: exp.Sort) -> str: 3098 return self.op_expressions("SORT BY", expression) 3099 3100 def _resolve_ordered_for_null_ordering_simulation( 3101 self, expression: exp.Ordered 3102 ) -> exp.Expr | None: 3103 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3104 3105 Returns the underlying expression of the uniquely-matching projection 3106 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3107 simulation, since the CASE is evaluated in FROM-clause scope rather 3108 than alias scope (MySQL error 1052). Returns None if no safe 3109 substitution applies, leaving the original behaviour unchanged. 3110 """ 3111 this = expression.this 3112 if not (isinstance(this, exp.Column) and not this.table): 3113 return None 3114 3115 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3116 if not isinstance(ancestor, exp.Select): 3117 return None 3118 3119 column_name = this.name 3120 matched: list[exp.Expr] = [ 3121 p.this if isinstance(p, exp.Alias) else p 3122 for p in ancestor.selects 3123 if p.output_name == column_name 3124 ] 3125 match = matched[0] if len(matched) == 1 else None 3126 3127 # Skip the substitution when it would be identical to the existing 3128 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3129 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3130 return None 3131 3132 return match 3133 3134 def ordered_sql(self, expression: exp.Ordered) -> str: 3135 desc = expression.args.get("desc") 3136 asc = not desc 3137 3138 nulls_first = expression.args.get("nulls_first") 3139 nulls_last = not nulls_first 3140 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3141 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3142 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3143 3144 this = self.sql(expression, "this") 3145 3146 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3147 nulls_sort_change = "" 3148 if nulls_first and ( 3149 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3150 ): 3151 nulls_sort_change = " NULLS FIRST" 3152 elif ( 3153 nulls_last 3154 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3155 and not nulls_are_last 3156 ): 3157 nulls_sort_change = " NULLS LAST" 3158 3159 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3160 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3161 window = expression.find_ancestor(exp.Window, exp.Select) 3162 3163 if isinstance(window, exp.Window): 3164 window_this = window.this 3165 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3166 window_this = window_this.this 3167 spec = window.args.get("spec") 3168 else: 3169 window_this = None 3170 spec = None 3171 3172 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3173 # without a spec or with a ROWS spec, but not with RANGE 3174 if not ( 3175 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3176 and (not spec or spec.text("kind").upper() == "ROWS") 3177 ): 3178 if window_this and spec: 3179 self.unsupported( 3180 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3181 ) 3182 nulls_sort_change = "" 3183 elif self.NULL_ORDERING_SUPPORTED is False and ( 3184 (asc and nulls_sort_change == " NULLS LAST") 3185 or (desc and nulls_sort_change == " NULLS FIRST") 3186 ): 3187 # BigQuery does not allow these ordering/nulls combinations when used under 3188 # an aggregation func or under a window containing one 3189 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3190 3191 if isinstance(ancestor, exp.Window): 3192 ancestor = ancestor.this 3193 if isinstance(ancestor, exp.AggFunc): 3194 self.unsupported( 3195 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3196 ) 3197 nulls_sort_change = "" 3198 elif self.NULL_ORDERING_SUPPORTED is None: 3199 if expression.this.is_int: 3200 self.unsupported( 3201 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3202 ) 3203 elif not isinstance(expression.this, exp.Rand): 3204 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3205 target = self.sql(resolved) if resolved is not None else this 3206 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3207 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3208 nulls_sort_change = "" 3209 3210 with_fill = self.sql(expression, "with_fill") 3211 with_fill = f" {with_fill}" if with_fill else "" 3212 3213 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3214 3215 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3216 window_frame = self.sql(expression, "window_frame") 3217 window_frame = f"{window_frame} " if window_frame else "" 3218 3219 this = self.sql(expression, "this") 3220 3221 return f"{window_frame}{this}" 3222 3223 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3224 partition = self.partition_by_sql(expression) 3225 order = self.sql(expression, "order") 3226 measures = self.expressions(expression, key="measures") 3227 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3228 rows = self.sql(expression, "rows") 3229 rows = self.seg(rows) if rows else "" 3230 after = self.sql(expression, "after") 3231 after = self.seg(after) if after else "" 3232 pattern = self.sql(expression, "pattern") 3233 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3234 definition_sqls = [ 3235 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3236 for definition in expression.args.get("define", []) 3237 ] 3238 definitions = self.expressions(sqls=definition_sqls) 3239 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3240 body = "".join( 3241 ( 3242 partition, 3243 order, 3244 measures, 3245 rows, 3246 after, 3247 pattern, 3248 define, 3249 ) 3250 ) 3251 alias = self.sql(expression, "alias") 3252 alias = f" {alias}" if alias else "" 3253 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3254 3255 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3256 limit = expression.args.get("limit") 3257 3258 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3259 count = limit.args.get("count") 3260 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3261 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3262 limit = exp.Limit( 3263 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3264 ) 3265 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3266 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3267 3268 return csv( 3269 *sqls, 3270 *[self.sql(join) for join in expression.args.get("joins") or []], 3271 self.sql(expression, "match"), 3272 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3273 self.sql(expression, "prewhere"), 3274 self.sql(expression, "where"), 3275 self.sql(expression, "connect"), 3276 self.sql(expression, "group"), 3277 self.sql(expression, "having"), 3278 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3279 self.sql(expression, "order"), 3280 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3281 *self.after_limit_modifiers(expression), 3282 self.options_modifier(expression), 3283 self.sql(expression, "for_"), 3284 sep="", 3285 ) 3286 3287 def options_modifier(self, expression: exp.Expr) -> str: 3288 options = self.expressions(expression, key="options") 3289 return f" {options}" if options else "" 3290 3291 def forclause_sql(self, expression: exp.ForClause) -> str: 3292 kind = expression.args["kind"] 3293 if kind == "BROWSE": 3294 return f"{self.sep()}FOR BROWSE" 3295 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3296 # the target dialect doesn't support QueryOption, so we drop the clause. 3297 options = self.expressions(expression, key="expressions") 3298 if not options: 3299 return "" 3300 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3301 3302 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3303 self.unsupported("Unsupported query option.") 3304 return "" 3305 3306 def offset_limit_modifiers( 3307 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3308 ) -> list[str]: 3309 return [ 3310 self.sql(expression, "offset") if fetch else self.sql(limit), 3311 self.sql(limit) if fetch else self.sql(expression, "offset"), 3312 ] 3313 3314 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3315 locks = self.expressions(expression, key="locks", sep=" ") 3316 locks = f" {locks}" if locks else "" 3317 return [locks, self.sql(expression, "sample")] 3318 3319 def select_sql(self, expression: exp.Select) -> str: 3320 into = expression.args.get("into") 3321 if not self.SUPPORTS_SELECT_INTO and into: 3322 into.pop() 3323 3324 hint = self.sql(expression, "hint") 3325 distinct = self.sql(expression, "distinct") 3326 distinct = f" {distinct}" if distinct else "" 3327 kind = self.sql(expression, "kind") 3328 3329 limit = expression.args.get("limit") 3330 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3331 top = self.limit_sql(limit, top=True) 3332 limit.pop() 3333 else: 3334 top = "" 3335 3336 expressions = self.expressions(expression) 3337 3338 if kind: 3339 if kind in self.SELECT_KINDS: 3340 kind = f" AS {kind}" 3341 else: 3342 if kind == "STRUCT": 3343 expressions = self.expressions( 3344 sqls=[ 3345 self.sql( 3346 exp.Struct( 3347 expressions=[ 3348 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3349 if isinstance(e, exp.Alias) 3350 else e 3351 for e in expression.expressions 3352 ] 3353 ) 3354 ) 3355 ] 3356 ) 3357 kind = "" 3358 3359 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3360 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3361 3362 exclude = expression.args.get("exclude") 3363 3364 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3365 exclude_sql = self.expressions(sqls=exclude, flat=True) 3366 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3367 3368 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3369 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3370 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3371 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3372 sql = self.query_modifiers( 3373 expression, 3374 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3375 self.sql(expression, "into", comment=False), 3376 self.sql(expression, "from_", comment=False), 3377 ) 3378 3379 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3380 if expression.args.get("with_"): 3381 sql = self.maybe_comment(sql, expression) 3382 expression.pop_comments() 3383 3384 sql = self.prepend_ctes(expression, sql) 3385 3386 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3387 expression.set("exclude", None) 3388 subquery = expression.subquery(copy=False) 3389 star = exp.Star(except_=exclude) 3390 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3391 3392 if not self.SUPPORTS_SELECT_INTO and into: 3393 if into.args.get("temporary"): 3394 table_kind = " TEMPORARY" 3395 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3396 table_kind = " UNLOGGED" 3397 else: 3398 table_kind = "" 3399 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3400 3401 return sql 3402 3403 def schema_sql(self, expression: exp.Schema) -> str: 3404 this = self.sql(expression, "this") 3405 sql = self.schema_columns_sql(expression) 3406 return f"{this} {sql}" if this and sql else this or sql 3407 3408 def schema_columns_sql(self, expression: exp.Expr) -> str: 3409 if expression.expressions: 3410 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3411 return "" 3412 3413 def star_sql(self, expression: exp.Star) -> str: 3414 except_ = self.expressions(expression, key="except_", flat=True) 3415 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3416 replace = self.expressions(expression, key="replace", flat=True) 3417 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3418 rename = self.expressions(expression, key="rename", flat=True) 3419 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3420 ilike = self.sql(expression, "ilike") 3421 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3422 return f"*{ilike}{except_}{replace}{rename}" 3423 3424 def parameter_sql(self, expression: exp.Parameter) -> str: 3425 this = self.sql(expression, "this") 3426 return f"{self.PARAMETER_TOKEN}{this}" 3427 3428 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3429 this = self.sql(expression, "this") 3430 kind = expression.text("kind") 3431 if kind: 3432 kind = f"{kind}." 3433 return f"@@{kind}{this}" 3434 3435 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3436 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3437 3438 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3439 alias = self.sql(expression, "alias") 3440 alias = f"{sep}{alias}" if alias else "" 3441 sample = self.sql(expression, "sample") 3442 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3443 alias = f"{sample}{alias}" 3444 3445 # Set to None so it's not generated again by self.query_modifiers() 3446 expression.set("sample", None) 3447 3448 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3449 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3450 return self.prepend_ctes(expression, sql) 3451 3452 def qualify_sql(self, expression: exp.Qualify) -> str: 3453 this = self.indent(self.sql(expression, "this")) 3454 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3455 3456 def unnest_sql(self, expression: exp.Unnest) -> str: 3457 args = self.expressions(expression, flat=True) 3458 3459 alias = expression.args.get("alias") 3460 offset = expression.args.get("offset") 3461 3462 if self.UNNEST_WITH_ORDINALITY: 3463 if alias and isinstance(offset, exp.Expr): 3464 alias.append("columns", offset) 3465 expression.set("offset", None) 3466 3467 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3468 columns = alias.columns 3469 alias = self.sql(columns[0]) if columns else "" 3470 else: 3471 alias = self.sql(alias) 3472 3473 alias = f" AS {alias}" if alias else alias 3474 if self.UNNEST_WITH_ORDINALITY: 3475 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3476 else: 3477 if isinstance(offset, exp.Expr): 3478 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3479 elif offset: 3480 suffix = f"{alias} WITH OFFSET" 3481 else: 3482 suffix = alias 3483 3484 return f"UNNEST({args}){suffix}" 3485 3486 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3487 return "" 3488 3489 def where_sql(self, expression: exp.Where) -> str: 3490 this = self.indent(self.sql(expression, "this")) 3491 return f"{self.seg('WHERE')}{self.sep()}{this}" 3492 3493 def window_sql(self, expression: exp.Window) -> str: 3494 this = self.sql(expression, "this") 3495 partition = self.partition_by_sql(expression) 3496 order = expression.args.get("order") 3497 order = self.order_sql(order, flat=True) if order else "" 3498 spec = self.sql(expression, "spec") 3499 alias = self.sql(expression, "alias") 3500 over = self.sql(expression, "over") or "OVER" 3501 3502 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3503 3504 first = expression.args.get("first") 3505 if first is None: 3506 first = "" 3507 else: 3508 first = "FIRST" if first else "LAST" 3509 3510 if not partition and not order and not spec and alias: 3511 return f"{this} {alias}" 3512 3513 args = self.format_args( 3514 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3515 ) 3516 return f"{this} ({args})" 3517 3518 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3519 partition = self.expressions(expression, key="partition_by", flat=True) 3520 return f"PARTITION BY {partition}" if partition else "" 3521 3522 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3523 kind = self.sql(expression, "kind") 3524 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3525 end = ( 3526 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3527 or "CURRENT ROW" 3528 ) 3529 3530 window_spec = f"{kind} BETWEEN {start} AND {end}" 3531 3532 exclude = self.sql(expression, "exclude") 3533 if exclude: 3534 if self.SUPPORTS_WINDOW_EXCLUDE: 3535 window_spec += f" EXCLUDE {exclude}" 3536 else: 3537 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3538 3539 return window_spec 3540 3541 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3542 this = self.sql(expression, "this") 3543 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3544 return f"{this} WITHIN GROUP ({expression_sql})" 3545 3546 def between_sql(self, expression: exp.Between) -> str: 3547 this = self.sql(expression, "this") 3548 low = self.sql(expression, "low") 3549 high = self.sql(expression, "high") 3550 symmetric = expression.args.get("symmetric") 3551 3552 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3553 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3554 3555 flag = ( 3556 " SYMMETRIC" 3557 if symmetric 3558 else " ASYMMETRIC" 3559 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3560 else "" # silently drop ASYMMETRIC – semantics identical 3561 ) 3562 return f"{this} BETWEEN{flag} {low} AND {high}" 3563 3564 def bracket_offset_expressions( 3565 self, expression: exp.Bracket, index_offset: int | None = None 3566 ) -> list[exp.Expr]: 3567 if expression.args.get("json_access"): 3568 return expression.expressions 3569 3570 return apply_index_offset( 3571 expression.this, 3572 expression.expressions, 3573 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3574 dialect=self.dialect, 3575 ) 3576 3577 def bracket_sql(self, expression: exp.Bracket) -> str: 3578 expressions = self.bracket_offset_expressions(expression) 3579 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3580 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3581 3582 def all_sql(self, expression: exp.All) -> str: 3583 this = self.sql(expression, "this") 3584 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3585 this = self.wrap(this) 3586 return f"ALL {this}" 3587 3588 def any_sql(self, expression: exp.Any) -> str: 3589 this = self.sql(expression, "this") 3590 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3591 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3592 this = self.wrap(this) 3593 return f"ANY{this}" 3594 return f"ANY {this}" 3595 3596 def exists_sql(self, expression: exp.Exists) -> str: 3597 return f"EXISTS{self.wrap(expression)}" 3598 3599 def case_sql(self, expression: exp.Case) -> str: 3600 this = self.sql(expression, "this") 3601 statements = [f"CASE {this}" if this else "CASE"] 3602 3603 for e in expression.args["ifs"]: 3604 statements.append(f"WHEN {self.sql(e, 'this')}") 3605 statements.append(f"THEN {self.sql(e, 'true')}") 3606 3607 default = self.sql(expression, "default") 3608 3609 if default: 3610 statements.append(f"ELSE {default}") 3611 3612 statements.append("END") 3613 3614 if self.pretty and self.too_wide(statements): 3615 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3616 3617 return " ".join(statements) 3618 3619 def constraint_sql(self, expression: exp.Constraint) -> str: 3620 this = self.sql(expression, "this") 3621 expressions = self.expressions(expression, flat=True) 3622 return f"CONSTRAINT {this} {expressions}" 3623 3624 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3625 order = expression.args.get("order") 3626 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3627 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3628 3629 def extract_sql(self, expression: exp.Extract) -> str: 3630 import sqlglot.dialects.dialect 3631 3632 this = ( 3633 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3634 if self.NORMALIZE_EXTRACT_DATE_PARTS 3635 else expression.this 3636 ) 3637 if self.EXTRACT_ALLOWS_QUOTES: 3638 this_sql = self.sql(this) 3639 elif isinstance(this, exp.WeekStart): 3640 this_sql = self.weekstart_name(this) 3641 else: 3642 this_sql = this.name 3643 expression_sql = self.sql(expression, "expression") 3644 3645 return f"EXTRACT({this_sql} FROM {expression_sql})" 3646 3647 def trim_sql(self, expression: exp.Trim) -> str: 3648 trim_type = self.sql(expression, "position") 3649 3650 if trim_type == "LEADING": 3651 func_name = "LTRIM" 3652 elif trim_type == "TRAILING": 3653 func_name = "RTRIM" 3654 else: 3655 func_name = "TRIM" 3656 3657 return self.func(func_name, expression.this, expression.expression) 3658 3659 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3660 args = expression.expressions 3661 if isinstance(expression, exp.ConcatWs): 3662 args = args[1:] # Skip the delimiter 3663 3664 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3665 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3666 3667 concat_coalesce = ( 3668 self.dialect.CONCAT_WS_COALESCE 3669 if isinstance(expression, exp.ConcatWs) 3670 else self.dialect.CONCAT_COALESCE 3671 ) 3672 3673 if not concat_coalesce and expression.args.get("coalesce"): 3674 3675 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3676 if not e.type: 3677 import sqlglot.optimizer.annotate_types 3678 3679 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3680 3681 if e.is_string or e.is_type(exp.DType.ARRAY): 3682 return e 3683 3684 return exp.func("coalesce", e, exp.Literal.string("")) 3685 3686 args = [_wrap_with_coalesce(e) for e in args] 3687 3688 return args 3689 3690 def concat_sql(self, expression: exp.Concat) -> str: 3691 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3692 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3693 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3694 # instead of coalescing them to empty string. 3695 import sqlglot.dialects.dialect 3696 3697 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3698 3699 expressions = self.convert_concat_args(expression) 3700 3701 # Some dialects don't allow a single-argument CONCAT call 3702 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3703 return self.sql(expressions[0]) 3704 3705 return self.func("CONCAT", *expressions) 3706 3707 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3708 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3709 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3710 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3711 all_args = expression.expressions 3712 expression.set("coalesce", True) 3713 return self.sql( 3714 exp.case() 3715 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3716 .else_(expression) 3717 ) 3718 3719 return self.func( 3720 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3721 ) 3722 3723 def check_sql(self, expression: exp.Check) -> str: 3724 this = self.sql(expression, key="this") 3725 return f"CHECK ({this})" 3726 3727 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3728 expressions = self.expressions(expression, flat=True) 3729 expressions = f" ({expressions})" if expressions else "" 3730 reference = self.sql(expression, "reference") 3731 reference = f" {reference}" if reference else "" 3732 delete = self.sql(expression, "delete") 3733 delete = f" ON DELETE {delete}" if delete else "" 3734 update = self.sql(expression, "update") 3735 update = f" ON UPDATE {update}" if update else "" 3736 options = self.expressions(expression, key="options", flat=True, sep=" ") 3737 options = f" {options}" if options else "" 3738 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3739 3740 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3741 this = self.sql(expression, "this") 3742 this = f" {this}" if this else "" 3743 expressions = self.expressions(expression, flat=True) 3744 include = self.sql(expression, "include") 3745 options = self.expressions(expression, key="options", flat=True, sep=" ") 3746 options = f" {options}" if options else "" 3747 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3748 3749 def timeserieskey_sql(self, expression: exp.TimeseriesKey) -> str: 3750 self.unsupported("TIMESERIES primary key columns are not supported") 3751 return self.sql(expression, "this") 3752 3753 def if_sql(self, expression: exp.If) -> str: 3754 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3755 3756 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3757 if self.MATCH_AGAINST_TABLE_PREFIX: 3758 expressions = [] 3759 for expr in expression.expressions: 3760 if isinstance(expr, exp.Table): 3761 expressions.append(f"TABLE {self.sql(expr)}") 3762 else: 3763 expressions.append(expr) 3764 else: 3765 expressions = expression.expressions 3766 3767 modifier = expression.args.get("modifier") 3768 modifier = f" {modifier}" if modifier else "" 3769 return ( 3770 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3771 ) 3772 3773 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3774 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3775 3776 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3777 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3778 3779 if self.QUOTE_JSON_PATH: 3780 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3781 3782 return path 3783 3784 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3785 if isinstance(expression, exp.JSONPathPart): 3786 transform = self.TRANSFORMS.get(expression.__class__) 3787 if not callable(transform): 3788 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3789 return "" 3790 3791 return transform(self, expression) 3792 3793 if isinstance(expression, int): 3794 return str(expression) 3795 3796 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3797 escaped = expression.replace("'", "\\'") 3798 escaped = f"\\'{expression}\\'" 3799 else: 3800 escaped = expression.replace('"', '\\"') 3801 escaped = f'"{escaped}"' 3802 3803 return escaped 3804 3805 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3806 return f"{self.sql(expression, 'this')} FORMAT JSON" 3807 3808 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3809 # Output the Teradata column FORMAT override. 3810 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3811 this = self.sql(expression, "this") 3812 fmt = self.sql(expression, "format") 3813 return f"{this} (FORMAT {fmt})" 3814 3815 def _jsonobject_sql( 3816 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3817 ) -> str: 3818 null_handling = expression.args.get("null_handling") 3819 null_handling = f" {null_handling}" if null_handling else "" 3820 3821 unique_keys = expression.args.get("unique_keys") 3822 if unique_keys is not None: 3823 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3824 else: 3825 unique_keys = "" 3826 3827 return_type = self.sql(expression, "return_type") 3828 return_type = f" RETURNING {return_type}" if return_type else "" 3829 encoding = self.sql(expression, "encoding") 3830 encoding = f" ENCODING {encoding}" if encoding else "" 3831 3832 if not name: 3833 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3834 3835 return self.func( 3836 name, 3837 *expression.expressions, 3838 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3839 ) 3840 3841 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3842 null_handling = expression.args.get("null_handling") 3843 null_handling = f" {null_handling}" if null_handling else "" 3844 return_type = self.sql(expression, "return_type") 3845 return_type = f" RETURNING {return_type}" if return_type else "" 3846 strict = " STRICT" if expression.args.get("strict") else "" 3847 return self.func( 3848 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3849 ) 3850 3851 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3852 this = self.sql(expression, "this") 3853 order = self.sql(expression, "order") 3854 null_handling = expression.args.get("null_handling") 3855 null_handling = f" {null_handling}" if null_handling else "" 3856 return_type = self.sql(expression, "return_type") 3857 return_type = f" RETURNING {return_type}" if return_type else "" 3858 strict = " STRICT" if expression.args.get("strict") else "" 3859 return self.func( 3860 "JSON_ARRAYAGG", 3861 this, 3862 suffix=f"{order}{null_handling}{return_type}{strict})", 3863 ) 3864 3865 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3866 path = self.sql(expression, "path") 3867 path = f" PATH {path}" if path else "" 3868 nested_schema = self.sql(expression, "nested_schema") 3869 3870 if nested_schema: 3871 return f"NESTED{path} {nested_schema}" 3872 3873 this = self.sql(expression, "this") 3874 kind = self.sql(expression, "kind") 3875 kind = f" {kind}" if kind else "" 3876 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3877 3878 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3879 return f"{this}{kind}{format_json}{path}{ordinality}" 3880 3881 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3882 return self.func("COLUMNS", *expression.expressions) 3883 3884 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3885 this = self.sql(expression, "this") 3886 path = self.sql(expression, "path") 3887 path = f", {path}" if path else "" 3888 error_handling = expression.args.get("error_handling") 3889 error_handling = f" {error_handling}" if error_handling else "" 3890 empty_handling = expression.args.get("empty_handling") 3891 empty_handling = f" {empty_handling}" if empty_handling else "" 3892 schema = self.sql(expression, "schema") 3893 return self.func( 3894 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3895 ) 3896 3897 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3898 this = self.sql(expression, "this") 3899 kind = self.sql(expression, "kind") 3900 path = self.sql(expression, "path") 3901 path = f" {path}" if path else "" 3902 as_json = " AS JSON" if expression.args.get("as_json") else "" 3903 return f"{this} {kind}{path}{as_json}" 3904 3905 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3906 this = self.sql(expression, "this") 3907 path = self.sql(expression, "path") 3908 path = f", {path}" if path else "" 3909 expressions = self.expressions(expression) 3910 with_ = ( 3911 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3912 if expressions 3913 else "" 3914 ) 3915 return f"OPENJSON({this}{path}){with_}" 3916 3917 def in_sql(self, expression: exp.In) -> str: 3918 query = expression.args.get("query") 3919 unnest = expression.args.get("unnest") 3920 field = expression.args.get("field") 3921 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3922 3923 if query: 3924 in_sql = self.sql(query) 3925 elif unnest: 3926 in_sql = self.in_unnest_op(unnest) 3927 elif field: 3928 in_sql = self.sql(field) 3929 else: 3930 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3931 3932 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3933 3934 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3935 return f"(SELECT {self.sql(unnest)})" 3936 3937 def interval_sql(self, expression: exp.Interval) -> str: 3938 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 3939 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 3940 exp.AutoRefreshProperty, 3941 ) 3942 interval_keyword = "INTERVAL" if include_keyword else "" 3943 unit_expression = expression.args.get("unit") 3944 unit = self.sql(unit_expression) if unit_expression else "" 3945 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3946 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3947 unit = f" {unit}" if unit else "" 3948 3949 if self.SINGLE_STRING_INTERVAL: 3950 this = expression.this.name if expression.this else "" 3951 if this: 3952 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 3953 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3954 return f"{interval_keyword}'{this}'{unit}" 3955 return f"{interval_keyword}'{this}{unit}'" 3956 return f"{interval_keyword}{unit}" 3957 3958 this = self.sql(expression, "this") 3959 if this: 3960 if not include_keyword and expression.this.is_string: 3961 this = expression.this.name 3962 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 3963 this = f"({this})" 3964 if include_keyword: 3965 this = f" {this}" 3966 3967 return f"{interval_keyword}{this}{unit}" 3968 3969 def return_sql(self, expression: exp.Return) -> str: 3970 return f"RETURN {self.sql(expression, 'this')}" 3971 3972 def reference_sql(self, expression: exp.Reference) -> str: 3973 this = self.sql(expression, "this") 3974 expressions = self.expressions(expression, flat=True) 3975 expressions = f"({expressions})" if expressions else "" 3976 options = self.expressions(expression, key="options", flat=True, sep=" ") 3977 options = f" {options}" if options else "" 3978 return f"REFERENCES {this}{expressions}{options}" 3979 3980 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3981 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3982 parent = expression.parent 3983 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3984 3985 return self.func( 3986 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3987 ) 3988 3989 def paren_sql(self, expression: exp.Paren) -> str: 3990 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3991 return f"({sql}{self.seg(')', sep='')}" 3992 3993 def neg_sql(self, expression: exp.Neg) -> str: 3994 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3995 this_sql = self.sql(expression, "this") 3996 sep = " " if this_sql[0] == "-" else "" 3997 return f"-{sep}{this_sql}" 3998 3999 def not_sql(self, expression: exp.Not) -> str: 4000 return f"NOT {self.sql(expression, 'this')}" 4001 4002 def alias_sql(self, expression: exp.Alias) -> str: 4003 alias = self.sql(expression, "alias") 4004 alias = f" AS {alias}" if alias else "" 4005 return f"{self.sql(expression, 'this')}{alias}" 4006 4007 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4008 alias = expression.args["alias"] 4009 4010 parent = expression.parent 4011 pivot = parent and parent.parent 4012 4013 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4014 identifier_alias = isinstance(alias, exp.Identifier) 4015 literal_alias = isinstance(alias, exp.Literal) 4016 4017 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4018 alias.replace(exp.Literal.string(alias.output_name)) 4019 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4020 alias.replace(exp.to_identifier(alias.output_name)) 4021 4022 return self.alias_sql(expression) 4023 4024 def aliases_sql(self, expression: exp.Aliases) -> str: 4025 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 4026 4027 def atindex_sql(self, expression: exp.AtIndex) -> str: 4028 this = self.sql(expression, "this") 4029 index = self.sql(expression, "expression") 4030 return f"{this} AT {index}" 4031 4032 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 4033 this = self.sql(expression, "this") 4034 zone = self.sql(expression, "zone") 4035 return f"{this} AT TIME ZONE {zone}" 4036 4037 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 4038 this = self.sql(expression, "this") 4039 zone = self.sql(expression, "zone") 4040 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 4041 4042 def fromiso8601date_sql(self, expression: exp.FromISO8601Date) -> str: 4043 return self.sql(exp.cast(expression.this, exp.DType.DATE)) 4044 4045 def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str: 4046 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4047 4048 def fromiso8601timestampnanos_sql(self, expression: exp.FromISO8601TimestampNanos) -> str: 4049 return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ)) 4050 4051 def add_sql(self, expression: exp.Add) -> str: 4052 return self.binary(expression, "+") 4053 4054 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 4055 return self.connector_sql(expression, "AND", stack) 4056 4057 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 4058 return self.connector_sql(expression, "OR", stack) 4059 4060 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 4061 return self.connector_sql(expression, "XOR", stack) 4062 4063 def connector_sql( 4064 self, 4065 expression: exp.Connector, 4066 op: str, 4067 stack: list[str | exp.Expr] | None = None, 4068 ) -> str: 4069 if stack is not None: 4070 stack.append(expression.right) 4071 if expression.comments and self.comments: 4072 op = self.maybe_comment(op, comments=expression.comments) 4073 4074 stack.extend((op, expression.left)) 4075 return op 4076 4077 stack = [expression] 4078 sqls: list[str] = [] 4079 ops = set() 4080 4081 while stack: 4082 node = stack.pop() 4083 if isinstance(node, exp.Connector): 4084 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4085 else: 4086 sql = self.sql(node) 4087 if sqls and sqls[-1] in ops: 4088 sqls[-1] += f" {sql}" 4089 else: 4090 sqls.append(sql) 4091 4092 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4093 return sep.join(sqls) 4094 4095 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4096 return self.binary(expression, "&") 4097 4098 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4099 return self.binary(expression, "<<") 4100 4101 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4102 return f"~{self.sql(expression, 'this')}" 4103 4104 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4105 return self.binary(expression, "|") 4106 4107 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4108 return self.binary(expression, ">>") 4109 4110 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4111 return self.binary(expression, "^") 4112 4113 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4114 format_sql = self.sql(expression, "format") 4115 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4116 to_sql = self.sql(expression, "to") 4117 to_sql = f" {to_sql}" if to_sql else "" 4118 action = self.sql(expression, "action") 4119 action = f" {action}" if action else "" 4120 default = self.sql(expression, "default") 4121 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4122 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4123 4124 # Base implementation that excludes safe, zone, and target_type metadata args 4125 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4126 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4127 4128 # Base implementation that excludes the safe and default_year metadata args 4129 def strtodate_sql(self, expression: exp.StrToDate) -> str: 4130 return self.func("STR_TO_DATE", expression.this, expression.args.get("format")) 4131 4132 def parsedatetime_sql(self, expression: exp.ParseDatetime) -> str: 4133 return self.func( 4134 "PARSE_DATETIME", 4135 expression.this, 4136 expression.args.get("format"), 4137 expression.args.get("zone"), 4138 ) 4139 4140 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4141 zone = self.sql(expression, "this") 4142 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4143 4144 def collate_sql(self, expression: exp.Collate) -> str: 4145 if self.COLLATE_IS_FUNC: 4146 return self.function_fallback_sql(expression) 4147 return self.binary(expression, "COLLATE") 4148 4149 def command_sql(self, expression: exp.Command) -> str: 4150 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4151 4152 def comment_sql(self, expression: exp.Comment) -> str: 4153 this = self.sql(expression, "this") 4154 kind = expression.args["kind"] 4155 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4156 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4157 expression_sql = self.sql(expression, "expression") 4158 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4159 4160 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4161 this = self.sql(expression, "this") 4162 delete = " DELETE" if expression.args.get("delete") else "" 4163 recompress = self.sql(expression, "recompress") 4164 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4165 to_disk = self.sql(expression, "to_disk") 4166 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4167 to_volume = self.sql(expression, "to_volume") 4168 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4169 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4170 4171 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4172 where = self.sql(expression, "where") 4173 group = self.sql(expression, "group") 4174 aggregates = self.expressions(expression, key="aggregates") 4175 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4176 4177 if not (where or group or aggregates) and len(expression.expressions) == 1: 4178 return f"TTL {self.expressions(expression, flat=True)}" 4179 4180 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4181 4182 def transaction_sql(self, expression: exp.Transaction) -> str: 4183 modes = self.expressions(expression, key="modes") 4184 modes = f" {modes}" if modes else "" 4185 return f"BEGIN{modes}" 4186 4187 def commit_sql(self, expression: exp.Commit) -> str: 4188 chain = expression.args.get("chain") 4189 if chain is not None: 4190 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4191 4192 return f"COMMIT{chain or ''}" 4193 4194 def rollback_sql(self, expression: exp.Rollback) -> str: 4195 savepoint = expression.args.get("savepoint") 4196 savepoint = f" TO {savepoint}" if savepoint else "" 4197 return f"ROLLBACK{savepoint}" 4198 4199 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4200 this = self.sql(expression, "this") 4201 4202 dtype = self.sql(expression, "dtype") 4203 if dtype: 4204 collate = self.sql(expression, "collate") 4205 collate = f" COLLATE {collate}" if collate else "" 4206 using = self.sql(expression, "using") 4207 using = f" USING {using}" if using else "" 4208 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4209 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 4210 4211 default = self.sql(expression, "default") 4212 if default: 4213 return f"ALTER COLUMN {this} SET DEFAULT {default}" 4214 4215 comment = self.sql(expression, "comment") 4216 if comment: 4217 return f"ALTER COLUMN {this} COMMENT {comment}" 4218 4219 visible = expression.args.get("visible") 4220 if visible: 4221 return f"ALTER COLUMN {this} SET {visible}" 4222 4223 allow_null = expression.args.get("allow_null") 4224 drop = expression.args.get("drop") 4225 4226 if not drop and not allow_null: 4227 self.unsupported("Unsupported ALTER COLUMN syntax") 4228 4229 if allow_null is not None: 4230 keyword = "DROP" if drop else "SET" 4231 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4232 4233 return f"ALTER COLUMN {this} DROP DEFAULT" 4234 4235 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4236 this = self.sql(expression, "this") 4237 rename_from = self.sql(expression, "rename_from") 4238 if rename_from: 4239 if not self.SUPPORTS_CHANGE_COLUMN: 4240 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4241 return f"CHANGE COLUMN {rename_from} {this}" 4242 if not self.SUPPORTS_MODIFY_COLUMN: 4243 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4244 return f"MODIFY COLUMN {this}" 4245 4246 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4247 this = self.sql(expression, "this") 4248 4249 visible = expression.args.get("visible") 4250 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4251 4252 return f"ALTER INDEX {this} {visible_sql}" 4253 4254 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4255 this = self.sql(expression, "this") 4256 if not isinstance(expression.this, exp.Var): 4257 this = f"KEY DISTKEY {this}" 4258 return f"ALTER DISTSTYLE {this}" 4259 4260 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4261 compound = " COMPOUND" if expression.args.get("compound") else "" 4262 this = self.sql(expression, "this") 4263 expressions = self.expressions(expression, flat=True) 4264 expressions = f"({expressions})" if expressions else "" 4265 return f"ALTER{compound} SORTKEY {this or expressions}" 4266 4267 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4268 if not self.RENAME_TABLE_WITH_DB: 4269 # Remove db from tables 4270 expression = expression.transform( 4271 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4272 ).assert_is(exp.AlterRename) 4273 this = self.sql(expression, "this") 4274 to_kw = " TO" if include_to else "" 4275 return f"RENAME{to_kw} {this}" 4276 4277 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4278 exists = " IF EXISTS" if expression.args.get("exists") else "" 4279 old_column = self.sql(expression, "this") 4280 new_column = self.sql(expression, "to") 4281 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4282 4283 def alterset_sql(self, expression: exp.AlterSet) -> str: 4284 exprs = self.expressions(expression, flat=True) 4285 if self.ALTER_SET_WRAPPED: 4286 exprs = f"({exprs})" 4287 4288 return f"SET {exprs}" 4289 4290 def alter_sql(self, expression: exp.Alter) -> str: 4291 actions = expression.args["actions"] 4292 4293 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4294 actions[0], exp.ColumnDef 4295 ): 4296 actions_sql = self.expressions(expression, key="actions", flat=True) 4297 actions_sql = f"ADD {actions_sql}" 4298 else: 4299 actions_list = [] 4300 for action in actions: 4301 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4302 action_sql = self.add_column_sql(action) 4303 else: 4304 action_sql = self.sql(action) 4305 if isinstance(action, exp.Query): 4306 action_sql = f"AS {action_sql}" 4307 4308 actions_list.append(action_sql) 4309 4310 actions_sql = self.format_args(*actions_list).lstrip("\n") 4311 4312 iceberg = ( 4313 "ICEBERG " 4314 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4315 else "" 4316 ) 4317 exists = " IF EXISTS" if expression.args.get("exists") else "" 4318 on_cluster = self.sql(expression, "cluster") 4319 on_cluster = f" {on_cluster}" if on_cluster else "" 4320 only = " ONLY" if expression.args.get("only") else "" 4321 options = self.expressions(expression, key="options") 4322 options = f", {options}" if options else "" 4323 kind = self.sql(expression, "kind") 4324 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4325 check = " WITH CHECK" if expression.args.get("check") else "" 4326 cascade = ( 4327 " CASCADE" 4328 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4329 else "" 4330 ) 4331 this = self.sql(expression, "this") 4332 this = f" {this}" if this else "" 4333 4334 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4335 4336 def altersession_sql(self, expression: exp.AlterSession) -> str: 4337 items_sql = self.expressions(expression, flat=True) 4338 keyword = "UNSET" if expression.args.get("unset") else "SET" 4339 return f"{keyword} {items_sql}" 4340 4341 def add_column_sql(self, expression: exp.Expr) -> str: 4342 sql = self.sql(expression) 4343 if isinstance(expression, exp.Schema): 4344 column_text = " COLUMNS" 4345 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4346 column_text = " COLUMN" 4347 else: 4348 column_text = "" 4349 4350 return f"ADD{column_text} {sql}" 4351 4352 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4353 expressions = self.expressions(expression) 4354 exists = " IF EXISTS " if expression.args.get("exists") else " " 4355 return f"DROP{exists}{expressions}" 4356 4357 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4358 return "DROP PRIMARY KEY" 4359 4360 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4361 return f"ADD {self.expressions(expression, indent=False)}" 4362 4363 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4364 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4365 location = self.sql(expression, "location") 4366 location = f" {location}" if location else "" 4367 return f"ADD {exists}{self.sql(expression.this)}{location}" 4368 4369 def distinct_sql(self, expression: exp.Distinct) -> str: 4370 this = self.expressions(expression, flat=True) 4371 4372 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4373 case = exp.case() 4374 for arg in expression.expressions: 4375 case = case.when(arg.is_(exp.null()), exp.null()) 4376 this = self.sql(case.else_(f"({this})")) 4377 4378 this = f" {this}" if this else "" 4379 4380 on = self.sql(expression, "on") 4381 on = f" ON {on}" if on else "" 4382 return f"DISTINCT{this}{on}" 4383 4384 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4385 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4386 4387 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4388 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4389 4390 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4391 this_sql = self.sql(expression, "this") 4392 expression_sql = self.sql(expression, "expression") 4393 kind = "MAX" if expression.args.get("max") else "MIN" 4394 return f"{this_sql} HAVING {kind} {expression_sql}" 4395 4396 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4397 return self.sql( 4398 exp.Cast( 4399 this=exp.Div(this=expression.this, expression=expression.expression), 4400 to=exp.DataType(this=exp.DType.INT), 4401 ) 4402 ) 4403 4404 def dpipe_sql(self, expression: exp.DPipe) -> str: 4405 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4406 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4407 return self.binary(expression, "||") 4408 4409 def div_sql(self, expression: exp.Div) -> str: 4410 l, r = expression.left, expression.right 4411 4412 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4413 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4414 4415 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4416 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4417 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4418 4419 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4420 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4421 return self.sql( 4422 exp.cast( 4423 l / r, 4424 to=exp.DType.BIGINT, 4425 ) 4426 ) 4427 4428 return self.binary(expression, "/") 4429 4430 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4431 n = exp._wrap(expression.this, exp.Binary) 4432 d = exp._wrap(expression.expression, exp.Binary) 4433 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4434 4435 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4436 return self.binary(expression, "OVERLAPS") 4437 4438 def distance_sql(self, expression: exp.Distance) -> str: 4439 return self.binary(expression, "<->") 4440 4441 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4442 return self.binary(expression, "<<->>") 4443 4444 def dot_sql(self, expression: exp.Dot) -> str: 4445 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4446 4447 def eq_sql(self, expression: exp.EQ) -> str: 4448 return self.binary(expression, "=") 4449 4450 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4451 return self.binary(expression, ":=") 4452 4453 def escape_sql(self, expression: exp.Escape) -> str: 4454 this = expression.this 4455 if ( 4456 isinstance(this, (exp.Like, exp.ILike)) 4457 and isinstance(this.expression, (exp.All, exp.Any)) 4458 and not self.SUPPORTS_LIKE_QUANTIFIERS 4459 ): 4460 return self._like_sql(this, escape=expression) 4461 return self.binary(expression, "ESCAPE") 4462 4463 def glob_sql(self, expression: exp.Glob) -> str: 4464 return self.binary(expression, "GLOB") 4465 4466 def gt_sql(self, expression: exp.GT) -> str: 4467 return self.binary(expression, ">") 4468 4469 def gte_sql(self, expression: exp.GTE) -> str: 4470 return self.binary(expression, ">=") 4471 4472 def is_sql(self, expression: exp.Is) -> str: 4473 negate = expression.args.get("negate") 4474 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4475 positive = bool(expression.expression.this) != bool(negate) 4476 return self.sql(expression.this if positive else exp.not_(expression.this)) 4477 return self.binary(expression, "IS NOT" if negate else "IS") 4478 4479 def _like_sql( 4480 self, 4481 expression: exp.Like | exp.ILike, 4482 escape: exp.Escape | None = None, 4483 ) -> str: 4484 this = expression.this 4485 rhs = expression.expression 4486 4487 if isinstance(expression, exp.Like): 4488 exp_class: type[exp.Like | exp.ILike] = exp.Like 4489 op = "LIKE" 4490 else: 4491 exp_class = exp.ILike 4492 op = "ILIKE" 4493 4494 if expression.args.get("negate"): 4495 op = f"NOT {op}" 4496 4497 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4498 exprs = rhs.this.unnest() 4499 4500 if isinstance(exprs, exp.Tuple): 4501 exprs = exprs.expressions 4502 else: 4503 exprs = [exprs] 4504 4505 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4506 4507 def _make_like(expr: exp.Expression) -> exp.Expression: 4508 like: exp.Expression = exp_class( 4509 this=this, expression=expr, negate=expression.args.get("negate") 4510 ) 4511 if escape: 4512 like = exp.Escape(this=like, expression=escape.expression.copy()) 4513 return like 4514 4515 like_expr: exp.Expr = _make_like(exprs[0]) 4516 for expr in exprs[1:]: 4517 like_expr = connective(like_expr, _make_like(expr), copy=False) 4518 4519 parent = escape.parent if escape else expression.parent 4520 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4521 parent, exp.Condition 4522 ): 4523 like_expr = exp.paren(like_expr, copy=False) 4524 4525 return self.sql(like_expr) 4526 4527 return self.binary(expression, op) 4528 4529 def like_sql(self, expression: exp.Like) -> str: 4530 return self._like_sql(expression) 4531 4532 def ilike_sql(self, expression: exp.ILike) -> str: 4533 return self._like_sql(expression) 4534 4535 def match_sql(self, expression: exp.Match) -> str: 4536 return self.binary(expression, "MATCH") 4537 4538 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4539 return self.binary(expression, "SIMILAR TO") 4540 4541 def lt_sql(self, expression: exp.LT) -> str: 4542 return self.binary(expression, "<") 4543 4544 def lte_sql(self, expression: exp.LTE) -> str: 4545 return self.binary(expression, "<=") 4546 4547 def mod_sql(self, expression: exp.Mod) -> str: 4548 return self.binary(expression, "%") 4549 4550 def mul_sql(self, expression: exp.Mul) -> str: 4551 return self.binary(expression, "*") 4552 4553 def neq_sql(self, expression: exp.NEQ) -> str: 4554 return self.binary(expression, "<>") 4555 4556 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4557 return self.binary(expression, "IS NOT DISTINCT FROM") 4558 4559 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4560 return self.binary(expression, "IS DISTINCT FROM") 4561 4562 def sub_sql(self, expression: exp.Sub) -> str: 4563 return self.binary(expression, "-") 4564 4565 def trycast_sql(self, expression: exp.TryCast) -> str: 4566 return self.cast_sql(expression, safe_prefix="TRY_") 4567 4568 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4569 return self.cast_sql(expression) 4570 4571 def try_sql(self, expression: exp.Try) -> str: 4572 if not self.TRY_SUPPORTED: 4573 self.unsupported("Unsupported TRY function") 4574 return self.sql(expression, "this") 4575 4576 return self.func("TRY", expression.this) 4577 4578 def log_sql(self, expression: exp.Log) -> str: 4579 this = expression.this 4580 expr = expression.expression 4581 4582 if self.dialect.LOG_BASE_FIRST is False: 4583 this, expr = expr, this 4584 elif self.dialect.LOG_BASE_FIRST is None and expr: 4585 if this.name in ("2", "10"): 4586 return self.func(f"LOG{this.name}", expr) 4587 4588 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4589 4590 return self.func("LOG", this, expr) 4591 4592 def use_sql(self, expression: exp.Use) -> str: 4593 kind = self.sql(expression, "kind") 4594 kind = f" {kind}" if kind else "" 4595 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4596 this = f" {this}" if this else "" 4597 return f"USE{kind}{this}" 4598 4599 def binary(self, expression: exp.Binary, op: str) -> str: 4600 sqls: list[str] = [] 4601 stack: list[None | str | exp.Expr] = [expression] 4602 binary_type = type(expression) 4603 4604 while stack: 4605 node = stack.pop() 4606 4607 if type(node) is binary_type: 4608 op_func = node.args.get("operator") 4609 if op_func: 4610 op = f"OPERATOR({self.sql(op_func)})" 4611 4612 stack.append(node.args.get("expression")) 4613 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4614 stack.append(node.args.get("this")) 4615 else: 4616 sqls.append(self.sql(node)) 4617 4618 return "".join(sqls) 4619 4620 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4621 to_clause = self.sql(expression, "to") 4622 if to_clause: 4623 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4624 4625 return self.function_fallback_sql(expression) 4626 4627 def function_fallback_sql(self, expression: exp.Func) -> str: 4628 args = [] 4629 4630 for key in expression.arg_types: 4631 arg_value = expression.args.get(key) 4632 4633 if isinstance(arg_value, list): 4634 for value in arg_value: 4635 args.append(value) 4636 elif arg_value is not None: 4637 args.append(arg_value) 4638 4639 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4640 name = expression.meta_get("name") or expression.sql_name() 4641 else: 4642 name = expression.sql_name() 4643 4644 return self.func(name, *args) 4645 4646 def func( 4647 self, 4648 name: str, 4649 *args: t.Any, 4650 prefix: str = "(", 4651 suffix: str = ")", 4652 normalize: bool = True, 4653 ) -> str: 4654 name = self.normalize_func(name) if normalize else name 4655 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4656 4657 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4658 arg_sqls = tuple( 4659 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4660 ) 4661 if self.pretty and self.too_wide(arg_sqls): 4662 return self.indent( 4663 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4664 ) 4665 return sep.join(arg_sqls) 4666 4667 def too_wide(self, args: t.Iterable) -> bool: 4668 return sum(len(arg) for arg in args) > self.max_text_width 4669 4670 def format_time( 4671 self, 4672 expression: exp.Expr, 4673 inverse_time_mapping: dict[str, str] | None = None, 4674 inverse_time_trie: dict | None = None, 4675 ) -> str | None: 4676 return format_time( 4677 self.sql(expression, "format"), 4678 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4679 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4680 ) 4681 4682 def expressions( 4683 self, 4684 expression: exp.Expr | None = None, 4685 key: str | None = None, 4686 sqls: t.Collection[str | exp.Expr] | None = None, 4687 flat: bool = False, 4688 indent: bool = True, 4689 skip_first: bool = False, 4690 skip_last: bool = False, 4691 sep: str = ", ", 4692 prefix: str = "", 4693 dynamic: bool = False, 4694 new_line: bool = False, 4695 ) -> str: 4696 expressions = expression.args.get(key or "expressions") if expression else sqls 4697 4698 if not expressions: 4699 return "" 4700 4701 if flat: 4702 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4703 4704 num_sqls = len(expressions) 4705 result_sqls = [] 4706 4707 for i, e in enumerate(expressions): 4708 sql = self.sql(e, comment=False) 4709 if not sql: 4710 continue 4711 4712 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4713 4714 if self.pretty: 4715 if self.leading_comma: 4716 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4717 else: 4718 result_sqls.append( 4719 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4720 ) 4721 else: 4722 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4723 4724 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4725 if new_line: 4726 result_sqls.insert(0, "") 4727 result_sqls.append("") 4728 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4729 else: 4730 result_sql = "".join(result_sqls) 4731 4732 return ( 4733 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4734 if indent 4735 else result_sql 4736 ) 4737 4738 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4739 flat = flat or isinstance(expression.parent, exp.Properties) 4740 expressions_sql = self.expressions(expression, flat=flat) 4741 if flat: 4742 return f"{op} {expressions_sql}" 4743 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4744 4745 def naked_property(self, expression: exp.Property) -> str: 4746 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4747 if not property_name: 4748 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4749 return f"{property_name} {self.sql(expression, 'this')}" 4750 4751 def tag_sql(self, expression: exp.Tag) -> str: 4752 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4753 4754 def token_sql(self, token_type: TokenType) -> str: 4755 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4756 4757 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4758 this = self.sql(expression, "this") 4759 expressions = self.no_identify(self.expressions, expression) 4760 expressions = ( 4761 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4762 ) 4763 return f"{this}{expressions}" if expressions.strip() != "" else this 4764 4765 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4766 return self.expressions(expression, flat=True) 4767 4768 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4769 params = self.no_identify(self.expressions, expression, flat=True) 4770 body = self.sql(expression, "this") 4771 prefix = "TABLE " if expression.args.get("is_table") else "" 4772 return f"({params}) AS {prefix}{body}" 4773 4774 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4775 this = self.sql(expression, "this") 4776 expressions = self.expressions(expression, flat=True) 4777 return f"{this}({expressions})" 4778 4779 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4780 return self.binary(expression, "=>") 4781 4782 def when_sql(self, expression: exp.When) -> str: 4783 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4784 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4785 condition = self.sql(expression, "condition") 4786 condition = f" AND {condition}" if condition else "" 4787 4788 then_expression = expression.args.get("then") 4789 if isinstance(then_expression, exp.Insert): 4790 this = self.sql(then_expression, "this") 4791 this = f"INSERT {this}" if this else "INSERT" 4792 then = self.sql(then_expression, "expression") 4793 then = f"{this} VALUES {then}" if then else this 4794 elif isinstance(then_expression, exp.Update): 4795 if isinstance(then_expression.args.get("expressions"), exp.Star): 4796 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4797 else: 4798 expressions_sql = self.expressions(then_expression) 4799 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4800 else: 4801 then = self.sql(then_expression) 4802 4803 if isinstance(then_expression, (exp.Insert, exp.Update)): 4804 where = self.sql(then_expression, "where") 4805 if where and not self.SUPPORTS_MERGE_WHERE: 4806 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4807 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4808 where = "" 4809 then = f"{then}{where}" 4810 return f"WHEN {matched}{source}{condition} THEN {then}" 4811 4812 def whens_sql(self, expression: exp.Whens) -> str: 4813 return self.expressions(expression, sep=" ", indent=False) 4814 4815 def merge_sql(self, expression: exp.Merge) -> str: 4816 table = expression.this 4817 table_alias = "" 4818 4819 hints = table.args.get("hints") 4820 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4821 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4822 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4823 4824 this = self.sql(table) 4825 using = f"USING {self.sql(expression, 'using')}" 4826 whens = self.sql(expression, "whens") 4827 4828 on = self.sql(expression, "on") 4829 on = f"ON {on}" if on else "" 4830 4831 if not on: 4832 on = self.expressions(expression, key="using_cond") 4833 on = f"USING ({on})" if on else "" 4834 4835 returning = self.sql(expression, "returning") 4836 if returning: 4837 whens = f"{whens}{returning}" 4838 4839 sep = self.sep() 4840 4841 return self.prepend_ctes( 4842 expression, 4843 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4844 ) 4845 4846 @unsupported_args("format") 4847 def tochar_sql(self, expression: exp.ToChar) -> str: 4848 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4849 4850 @unsupported_args("default") 4851 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4852 if not self.SUPPORTS_TO_NUMBER: 4853 self.unsupported("Unsupported TO_NUMBER function") 4854 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4855 4856 fmt = expression.args.get("format") 4857 if not fmt: 4858 self.unsupported("Conversion format is required for TO_NUMBER") 4859 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4860 4861 return self.func("TO_NUMBER", expression.this, fmt) 4862 4863 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4864 this = self.sql(expression, "this") 4865 kind = self.sql(expression, "kind") 4866 settings_sql = self.expressions(expression, key="settings", sep=" ") 4867 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4868 return f"{this}({kind}{args})" 4869 4870 def dictrange_sql(self, expression: exp.DictRange) -> str: 4871 this = self.sql(expression, "this") 4872 max = self.sql(expression, "max") 4873 min = self.sql(expression, "min") 4874 return f"{this}(MIN {min} MAX {max})" 4875 4876 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4877 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4878 4879 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4880 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4881 4882 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4883 def uniquekeyproperty_sql( 4884 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4885 ) -> str: 4886 return f"{prefix} ({self.expressions(expression, flat=True)})" 4887 4888 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4889 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4890 expressions = self.expressions(expression, flat=True) 4891 expressions = f" {self.wrap(expressions)}" if expressions else "" 4892 buckets = self.sql(expression, "buckets") 4893 kind = self.sql(expression, "kind") 4894 buckets = f" BUCKETS {buckets}" if buckets else "" 4895 order = self.sql(expression, "order") 4896 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4897 4898 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4899 return "" 4900 4901 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4902 expressions = self.expressions(expression, key="expressions", flat=True) 4903 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4904 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4905 buckets = self.sql(expression, "buckets") 4906 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4907 4908 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4909 this = self.sql(expression, "this") 4910 having = self.sql(expression, "having") 4911 4912 if having: 4913 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4914 4915 return self.func("ANY_VALUE", this) 4916 4917 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4918 transform = self.func("TRANSFORM", *expression.expressions) 4919 row_format_before = self.sql(expression, "row_format_before") 4920 row_format_before = f" {row_format_before}" if row_format_before else "" 4921 record_writer = self.sql(expression, "record_writer") 4922 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4923 using = f" USING {self.sql(expression, 'command_script')}" 4924 schema = self.sql(expression, "schema") 4925 schema = f" AS {schema}" if schema else "" 4926 row_format_after = self.sql(expression, "row_format_after") 4927 row_format_after = f" {row_format_after}" if row_format_after else "" 4928 record_reader = self.sql(expression, "record_reader") 4929 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4930 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4931 4932 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4933 key_block_size = self.sql(expression, "key_block_size") 4934 if key_block_size: 4935 return f"KEY_BLOCK_SIZE = {key_block_size}" 4936 4937 using = self.sql(expression, "using") 4938 if using: 4939 return f"USING {using}" 4940 4941 parser = self.sql(expression, "parser") 4942 if parser: 4943 return f"WITH PARSER {parser}" 4944 4945 comment = self.sql(expression, "comment") 4946 if comment: 4947 return f"COMMENT {comment}" 4948 4949 visible = expression.args.get("visible") 4950 if visible is not None: 4951 return "VISIBLE" if visible else "INVISIBLE" 4952 4953 engine_attr = self.sql(expression, "engine_attr") 4954 if engine_attr: 4955 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4956 4957 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4958 if secondary_engine_attr: 4959 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4960 4961 self.unsupported("Unsupported index constraint option.") 4962 return "" 4963 4964 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4965 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4966 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4967 4968 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4969 kind = self.sql(expression, "kind") 4970 kind = f"{kind} INDEX" if kind else "INDEX" 4971 this = self.sql(expression, "this") 4972 this = f" {this}" if this else "" 4973 index_type = self.sql(expression, "index_type") 4974 index_type = f" USING {index_type}" if index_type else "" 4975 expressions = self.expressions(expression, flat=True) 4976 expressions = f" ({expressions})" if expressions else "" 4977 options = self.expressions(expression, key="options", sep=" ") 4978 options = f" {options}" if options else "" 4979 return f"{kind}{this}{index_type}{expressions}{options}" 4980 4981 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4982 if self.NVL2_SUPPORTED: 4983 return self.function_fallback_sql(expression) 4984 4985 case = exp.Case().when( 4986 expression.this.is_(exp.null()).not_(copy=False), 4987 expression.args["true"], 4988 copy=False, 4989 ) 4990 else_cond = expression.args.get("false") 4991 if else_cond: 4992 case.else_(else_cond, copy=False) 4993 4994 return self.sql(case) 4995 4996 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4997 this = self.sql(expression, "this") 4998 expr = self.sql(expression, "expression") 4999 position = self.sql(expression, "position") 5000 position = f", {position}" if position else "" 5001 iterator = self.sql(expression, "iterator") 5002 condition = self.sql(expression, "condition") 5003 condition = f" IF {condition}" if condition else "" 5004 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 5005 5006 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 5007 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 5008 5009 def opclass_sql(self, expression: exp.Opclass) -> str: 5010 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 5011 5012 def _ml_sql(self, expression: exp.Func, name: str) -> str: 5013 model = self.sql(expression, "this") 5014 model = f"MODEL {model}" 5015 expr = expression.expression 5016 if expr: 5017 expr_sql = self.sql(expression, "expression") 5018 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 5019 else: 5020 expr_sql = None 5021 5022 parameters = self.sql(expression, "params_struct") or None 5023 5024 return self.func(name, model, expr_sql, parameters) 5025 5026 def predict_sql(self, expression: exp.Predict) -> str: 5027 return self._ml_sql(expression, "PREDICT") 5028 5029 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 5030 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 5031 return self._ml_sql(expression, name) 5032 5033 def generatetext_sql(self, expression: exp.GenerateText) -> str: 5034 return self._ml_sql(expression, "GENERATE_TEXT") 5035 5036 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 5037 return self._ml_sql(expression, "GENERATE_TABLE") 5038 5039 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 5040 return self._ml_sql(expression, "GENERATE_BOOL") 5041 5042 def generateint_sql(self, expression: exp.GenerateInt) -> str: 5043 return self._ml_sql(expression, "GENERATE_INT") 5044 5045 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 5046 return self._ml_sql(expression, "GENERATE_DOUBLE") 5047 5048 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 5049 return self._ml_sql(expression, "TRANSLATE") 5050 5051 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 5052 return self._ml_sql(expression, "FORECAST") 5053 5054 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5055 this_sql = self.sql(expression, "this") 5056 if isinstance(expression.this, exp.Table): 5057 this_sql = f"TABLE {this_sql}" 5058 5059 return self.func( 5060 "FORECAST", 5061 this_sql, 5062 expression.args.get("data_col"), 5063 expression.args.get("timestamp_col"), 5064 expression.args.get("model"), 5065 expression.args.get("id_cols"), 5066 expression.args.get("horizon"), 5067 expression.args.get("forecast_end_timestamp"), 5068 expression.args.get("confidence_level"), 5069 expression.args.get("output_historical_time_series"), 5070 expression.args.get("context_window"), 5071 ) 5072 5073 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5074 this_sql = self.sql(expression, "this") 5075 if isinstance(expression.this, exp.Table): 5076 this_sql = f"TABLE {this_sql}" 5077 5078 return self.func( 5079 "FEATURES_AT_TIME", 5080 this_sql, 5081 expression.args.get("time"), 5082 expression.args.get("num_rows"), 5083 expression.args.get("ignore_feature_nulls"), 5084 ) 5085 5086 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5087 this_sql = self.sql(expression, "this") 5088 if isinstance(expression.this, exp.Table): 5089 this_sql = f"TABLE {this_sql}" 5090 5091 query_table = self.sql(expression, "query_table") 5092 if isinstance(expression.args["query_table"], exp.Table): 5093 query_table = f"TABLE {query_table}" 5094 5095 return self.func( 5096 "VECTOR_SEARCH", 5097 this_sql, 5098 expression.args.get("column_to_search"), 5099 query_table, 5100 expression.args.get("query_column_to_search"), 5101 expression.args.get("top_k"), 5102 expression.args.get("distance_type"), 5103 expression.args.get("options"), 5104 ) 5105 5106 def forin_sql(self, expression: exp.ForIn) -> str: 5107 this = self.sql(expression, "this") 5108 expression_sql = self.sql(expression, "expression") 5109 return f"FOR {this} DO {expression_sql}" 5110 5111 def refresh_sql(self, expression: exp.Refresh) -> str: 5112 this = self.sql(expression, "this") 5113 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5114 return f"REFRESH {kind}{this}" 5115 5116 def toarray_sql(self, expression: exp.ToArray) -> str: 5117 arg = expression.this 5118 if not arg.type: 5119 import sqlglot.optimizer.annotate_types 5120 5121 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5122 5123 if arg.is_type(exp.DType.ARRAY): 5124 return self.sql(arg) 5125 5126 cond_for_null = arg.is_(exp.null()) 5127 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5128 5129 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5130 this = expression.this 5131 time_format = self.format_time(expression) 5132 5133 if time_format: 5134 return self.sql( 5135 exp.cast( 5136 exp.StrToTime(this=this, format=expression.args["format"]), 5137 exp.DType.TIME, 5138 ) 5139 ) 5140 5141 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5142 return self.sql(this) 5143 5144 return self.sql(exp.cast(this, exp.DType.TIME)) 5145 5146 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5147 this = expression.this 5148 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5149 return self.sql(this) 5150 5151 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5152 5153 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5154 this = expression.this 5155 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5156 return self.sql(this) 5157 5158 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5159 5160 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5161 this = expression.this 5162 time_format = self.format_time(expression) 5163 safe = expression.args.get("safe") 5164 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5165 return self.sql( 5166 exp.cast( 5167 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5168 exp.DType.DATE, 5169 ) 5170 ) 5171 5172 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5173 return self.sql(this) 5174 5175 if safe: 5176 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5177 5178 return self.sql(exp.cast(this, exp.DType.DATE)) 5179 5180 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5181 return self.sql( 5182 exp.func( 5183 "DATEDIFF", 5184 expression.this, 5185 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5186 "day", 5187 ) 5188 ) 5189 5190 def lastday_sql(self, expression: exp.LastDay) -> str: 5191 if self.LAST_DAY_SUPPORTS_DATE_PART: 5192 return self.function_fallback_sql(expression) 5193 5194 unit = expression.args.get("unit") 5195 if unit and unit.name.upper() != "MONTH": 5196 self.unsupported("Date parts are not supported in LAST_DAY.") 5197 5198 return self.func("LAST_DAY", expression.this) 5199 5200 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5201 import sqlglot.dialects.dialect 5202 5203 return self.func( 5204 "DATE_ADD", 5205 expression.this, 5206 expression.expression, 5207 sqlglot.dialects.dialect.unit_to_str(expression), 5208 ) 5209 5210 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5211 if self.CAN_IMPLEMENT_ARRAY_ANY: 5212 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5213 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5214 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5215 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5216 5217 import sqlglot.dialects.dialect 5218 5219 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5220 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5221 self.unsupported("ARRAY_ANY is unsupported") 5222 5223 return self.function_fallback_sql(expression) 5224 5225 def struct_sql(self, expression: exp.Struct) -> str: 5226 expression.set( 5227 "expressions", 5228 [ 5229 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5230 if isinstance(e, exp.PropertyEQ) 5231 else e 5232 for e in expression.expressions 5233 ], 5234 ) 5235 5236 return self.function_fallback_sql(expression) 5237 5238 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5239 low = self.sql(expression, "this") 5240 high = self.sql(expression, "expression") 5241 5242 return f"{low} TO {high}" 5243 5244 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5245 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5246 tables = f" {self.expressions(expression)}" 5247 5248 exists = " IF EXISTS" if expression.args.get("exists") else "" 5249 5250 on_cluster = self.sql(expression, "cluster") 5251 on_cluster = f" {on_cluster}" if on_cluster else "" 5252 5253 identity = self.sql(expression, "identity") 5254 identity = f" {identity} IDENTITY" if identity else "" 5255 5256 option = self.sql(expression, "option") 5257 option = f" {option}" if option else "" 5258 5259 partition = self.sql(expression, "partition") 5260 partition = f" {partition}" if partition else "" 5261 5262 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5263 5264 # This transpiles T-SQL's CONVERT function 5265 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5266 def convert_sql(self, expression: exp.Convert) -> str: 5267 to = expression.this 5268 value = expression.expression 5269 style = expression.args.get("style") 5270 safe = expression.args.get("safe") 5271 strict = expression.args.get("strict") 5272 5273 if not to or not value: 5274 return "" 5275 5276 # Retrieve length of datatype and override to default if not specified 5277 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5278 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5279 5280 transformed: exp.Expr | None = None 5281 cast = exp.Cast if strict else exp.TryCast 5282 5283 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5284 if isinstance(style, exp.Literal) and style.is_int: 5285 import sqlglot.dialects.tsql 5286 5287 style_value = style.name 5288 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5289 if not converted_style: 5290 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5291 5292 fmt = exp.Literal.string(converted_style) 5293 5294 if to.this == exp.DType.DATE: 5295 transformed = exp.StrToDate(this=value, format=fmt) 5296 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5297 transformed = exp.StrToTime(this=value, format=fmt) 5298 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5299 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5300 elif to.this == exp.DType.TEXT: 5301 transformed = exp.TimeToStr(this=value, format=fmt) 5302 5303 if not transformed: 5304 transformed = cast(this=value, to=to, safe=safe) 5305 5306 return self.sql(transformed) 5307 5308 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5309 this = expression.this 5310 if isinstance(this, exp.JSONPathWildcard): 5311 this = self.json_path_part(this) 5312 return f".{this}" if this else "" 5313 5314 quoted = expression.args.get("quoted") 5315 if not ( 5316 quoted and self.JSON_PATH_KEY_QUOTED_FORCES_BRACKETS 5317 ) and self.SAFE_JSON_PATH_KEY_RE.match(this): 5318 return f".{this}" 5319 5320 this = self.json_path_part(this) 5321 5322 if quoted and self.QUOTE_JSON_PATH: 5323 # The whole path is rendered as a single quoted string literal, so the bracketed key 5324 # (which may itself contain backslash-escaped quotes, e.g. ["x \"y\"z"]) must be 5325 # escaped again for the outer string literal (-> ["x \\"y\\"z"]). 5326 this = self.escape_str(this) 5327 5328 return ( 5329 f"[{this}]" 5330 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5331 else f".{this}" 5332 ) 5333 5334 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5335 this = self.json_path_part(expression.this) 5336 return f"[{this}]" if this else "" 5337 5338 def _simplify_unless_literal(self, expression: E) -> E: 5339 if not isinstance(expression, exp.Literal): 5340 import sqlglot.optimizer.simplify 5341 5342 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5343 5344 return expression 5345 5346 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5347 this = expression.this 5348 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5349 self.unsupported( 5350 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5351 ) 5352 return self.sql(this) 5353 5354 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5355 if self.IGNORE_NULLS_BEFORE_ORDER: 5356 # The first modifier here will be the one closest to the AggFunc's arg 5357 mods = sorted( 5358 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5359 key=lambda x: ( 5360 0 5361 if isinstance(x, exp.HavingMax) 5362 else (1 if isinstance(x, exp.Order) else 2) 5363 ), 5364 ) 5365 5366 if mods: 5367 mod = mods[0] 5368 this = expression.__class__(this=mod.this.copy()) 5369 this.meta["inline"] = True 5370 mod.this.replace(this) 5371 return self.sql(expression.this) 5372 5373 agg_func = expression.find(exp.AggFunc) 5374 5375 if agg_func: 5376 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5377 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5378 5379 return f"{self.sql(expression, 'this')} {text}" 5380 5381 def _replace_line_breaks(self, string: str) -> str: 5382 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5383 if self.pretty: 5384 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5385 return string 5386 5387 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5388 option = self.sql(expression, "this") 5389 5390 if expression.expressions: 5391 upper = option.upper() 5392 5393 # Snowflake FILE_FORMAT options are separated by whitespace 5394 sep = " " if upper == "FILE_FORMAT" else ", " 5395 5396 # Databricks copy/format options do not set their list of values with EQ 5397 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5398 values = self.expressions(expression, flat=True, sep=sep) 5399 return f"{option}{op}({values})" 5400 5401 value = self.sql(expression, "expression") 5402 5403 if not value: 5404 return option 5405 5406 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5407 5408 return f"{option}{op}{value}" 5409 5410 def credentials_sql(self, expression: exp.Credentials) -> str: 5411 cred_expr = expression.args.get("credentials") 5412 if isinstance(cred_expr, exp.Literal): 5413 # Redshift case: CREDENTIALS <string> 5414 credentials = self.sql(expression, "credentials") 5415 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5416 else: 5417 # Snowflake case: CREDENTIALS = (...) 5418 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5419 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5420 5421 storage = self.sql(expression, "storage") 5422 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5423 5424 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5425 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5426 5427 iam_role = self.sql(expression, "iam_role") 5428 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5429 5430 region = self.sql(expression, "region") 5431 region = f" REGION {region}" if region else "" 5432 5433 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5434 5435 def copy_sql(self, expression: exp.Copy) -> str: 5436 this = self.sql(expression, "this") 5437 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5438 5439 credentials = self.sql(expression, "credentials") 5440 credentials = self.seg(credentials) if credentials else "" 5441 files = self.expressions(expression, key="files", flat=True) 5442 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5443 5444 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5445 params = self.expressions( 5446 expression, 5447 key="params", 5448 sep=sep, 5449 new_line=True, 5450 skip_last=True, 5451 skip_first=True, 5452 indent=self.COPY_PARAMS_ARE_WRAPPED, 5453 ) 5454 5455 if params: 5456 if self.COPY_PARAMS_ARE_WRAPPED: 5457 params = f" WITH ({params})" 5458 elif not self.pretty and (files or credentials): 5459 params = f" {params}" 5460 5461 return f"COPY{this}{kind} {files}{credentials}{params}" 5462 5463 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5464 return "" 5465 5466 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5467 on_sql = "ON" if expression.args.get("on") else "OFF" 5468 filter_col: str | None = self.sql(expression, "filter_column") 5469 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5470 retention_period: str | None = self.sql(expression, "retention_period") 5471 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5472 5473 if filter_col or retention_period: 5474 on_sql = self.func("ON", filter_col, retention_period) 5475 5476 return f"DATA_DELETION={on_sql}" 5477 5478 def maskingpolicycolumnconstraint_sql( 5479 self, expression: exp.MaskingPolicyColumnConstraint 5480 ) -> str: 5481 this = self.sql(expression, "this") 5482 expressions = self.expressions(expression, flat=True) 5483 expressions = f" USING ({expressions})" if expressions else "" 5484 return f"MASKING POLICY {this}{expressions}" 5485 5486 def gapfill_sql(self, expression: exp.GapFill) -> str: 5487 this = self.sql(expression, "this") 5488 this = f"TABLE {this}" 5489 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5490 5491 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5492 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5493 5494 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5495 this = self.sql(expression, "this") 5496 expr = expression.expression 5497 5498 if isinstance(expr, exp.Func): 5499 # T-SQL's CLR functions are case sensitive 5500 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5501 else: 5502 expr = self.sql(expression, "expression") 5503 5504 return self.scope_resolution(expr, this) 5505 5506 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5507 if self.PARSE_JSON_NAME is None: 5508 return self.sql(expression.this) 5509 5510 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5511 5512 def rand_sql(self, expression: exp.Rand) -> str: 5513 lower = self.sql(expression, "lower") 5514 upper = self.sql(expression, "upper") 5515 5516 if lower and upper: 5517 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5518 return self.func("RAND", expression.this) 5519 5520 def changes_sql(self, expression: exp.Changes) -> str: 5521 information = self.sql(expression, "information") 5522 information = f"INFORMATION => {information}" 5523 at_before = self.sql(expression, "at_before") 5524 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5525 end = self.sql(expression, "end") 5526 end = f"{self.seg('')}{end}" if end else "" 5527 5528 return f"CHANGES ({information}){at_before}{end}" 5529 5530 def pad_sql(self, expression: exp.Pad) -> str: 5531 prefix = "L" if expression.args.get("is_left") else "R" 5532 5533 fill_pattern = self.sql(expression, "fill_pattern") or None 5534 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5535 fill_pattern = "' '" 5536 5537 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5538 5539 def summarize_sql(self, expression: exp.Summarize) -> str: 5540 table = " TABLE" if expression.args.get("table") else "" 5541 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5542 5543 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5544 generate_series = exp.GenerateSeries(**expression.args) 5545 5546 parent = expression.parent 5547 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5548 parent = parent.parent 5549 5550 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5551 return self.sql(exp.Unnest(expressions=[generate_series])) 5552 5553 if isinstance(parent, exp.Select): 5554 self.unsupported("GenerateSeries projection unnesting is not supported.") 5555 5556 return self.sql(generate_series) 5557 5558 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5559 if self.SUPPORTS_CONVERT_TIMEZONE: 5560 return self.function_fallback_sql(expression) 5561 5562 source_tz = expression.args.get("source_tz") 5563 target_tz = expression.args.get("target_tz") 5564 timestamp = expression.args.get("timestamp") 5565 5566 if source_tz and timestamp: 5567 timestamp = exp.AtTimeZone( 5568 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5569 ) 5570 5571 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5572 5573 return self.sql(expr) 5574 5575 def json_sql(self, expression: exp.JSON) -> str: 5576 this = self.sql(expression, "this") 5577 this = f" {this}" if this else "" 5578 5579 _with = expression.args.get("with_") 5580 5581 if _with is None: 5582 with_sql = "" 5583 elif not _with: 5584 with_sql = " WITHOUT" 5585 else: 5586 with_sql = " WITH" 5587 5588 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5589 5590 return f"JSON{this}{with_sql}{unique_sql}" 5591 5592 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5593 path = self.sql(expression, "path") 5594 returning = self.sql(expression, "returning") 5595 returning = f" RETURNING {returning}" if returning else "" 5596 5597 on_condition = self.sql(expression, "on_condition") 5598 on_condition = f" {on_condition}" if on_condition else "" 5599 5600 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5601 5602 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5603 regexp = " REGEXP" if expression.args.get("regexp") else "" 5604 return f"SKIP{regexp} {self.sql(expression.expression)}" 5605 5606 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5607 else_ = "ELSE " if expression.args.get("else_") else "" 5608 condition = self.sql(expression, "expression") 5609 condition = f"WHEN {condition} THEN " if condition else else_ 5610 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5611 return f"{condition}{insert}" 5612 5613 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5614 kind = self.sql(expression, "kind") 5615 expressions = self.seg(self.expressions(expression, sep=" ")) 5616 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5617 return res 5618 5619 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5620 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5621 empty = expression.args.get("empty") 5622 empty = ( 5623 f"DEFAULT {empty} ON EMPTY" 5624 if isinstance(empty, exp.Expr) 5625 else self.sql(expression, "empty") 5626 ) 5627 5628 error = expression.args.get("error") 5629 error = ( 5630 f"DEFAULT {error} ON ERROR" 5631 if isinstance(error, exp.Expr) 5632 else self.sql(expression, "error") 5633 ) 5634 5635 if error and empty: 5636 error = ( 5637 f"{empty} {error}" 5638 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5639 else f"{error} {empty}" 5640 ) 5641 empty = "" 5642 5643 null = self.sql(expression, "null") 5644 5645 return f"{empty}{error}{null}" 5646 5647 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5648 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5649 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5650 5651 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5652 this = self.sql(expression, "this") 5653 path = self.sql(expression, "path") 5654 5655 passing = self.expressions(expression, "passing") 5656 passing = f" PASSING {passing}" if passing else "" 5657 5658 on_condition = self.sql(expression, "on_condition") 5659 on_condition = f" {on_condition}" if on_condition else "" 5660 5661 path = f"{path}{passing}{on_condition}" 5662 5663 return self.func("JSON_EXISTS", this, path) 5664 5665 def _add_arrayagg_null_filter( 5666 self, 5667 array_agg_sql: str, 5668 array_agg_expr: exp.ArrayAgg, 5669 column_expr: exp.Expr, 5670 ) -> str: 5671 """ 5672 Add NULL filter to ARRAY_AGG if dialect requires it. 5673 5674 Args: 5675 array_agg_sql: The generated ARRAY_AGG SQL string 5676 array_agg_expr: The ArrayAgg expression node 5677 column_expr: The column/expression to filter (before ORDER BY wrapping) 5678 5679 Returns: 5680 SQL string with FILTER clause added if needed 5681 """ 5682 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5683 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5684 if not ( 5685 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5686 ): 5687 return array_agg_sql 5688 5689 parent = array_agg_expr.parent 5690 if isinstance(parent, exp.Filter): 5691 parent_cond = parent.expression.this 5692 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5693 elif column_expr.find(exp.Column): 5694 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5695 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5696 this_sql = ( 5697 self.expressions(column_expr) 5698 if isinstance(column_expr, exp.Distinct) 5699 else self.sql(column_expr) 5700 ) 5701 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5702 5703 return array_agg_sql 5704 5705 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5706 array_agg = self.function_fallback_sql(expression) 5707 column_expr = expression.this 5708 if isinstance(column_expr, exp.Order): 5709 column_expr = column_expr.this 5710 5711 return self._add_arrayagg_null_filter(array_agg, expression, column_expr) 5712 5713 def slice_sql(self, expression: exp.Slice) -> str: 5714 step = self.sql(expression, "step") 5715 end = self.sql(expression.expression) 5716 begin = self.sql(expression.this) 5717 5718 sql = f"{end}:{step}" if step else end 5719 return f"{begin}:{sql}" if sql else f"{begin}:" 5720 5721 def apply_sql(self, expression: exp.Apply) -> str: 5722 this = self.sql(expression, "this") 5723 expr = self.sql(expression, "expression") 5724 5725 return f"{this} APPLY({expr})" 5726 5727 def _grant_or_revoke_sql( 5728 self, 5729 expression: exp.Grant | exp.Revoke, 5730 keyword: str, 5731 preposition: str, 5732 grant_option_prefix: str = "", 5733 grant_option_suffix: str = "", 5734 ) -> str: 5735 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5736 5737 kind = self.sql(expression, "kind") 5738 kind = f" {kind}" if kind else "" 5739 5740 securable = self.sql(expression, "securable") 5741 securable = f" {securable}" if securable else "" 5742 5743 principals = self.expressions(expression, key="principals", flat=True) 5744 5745 if not expression.args.get("grant_option"): 5746 grant_option_prefix = grant_option_suffix = "" 5747 5748 # cascade for revoke only 5749 cascade = self.sql(expression, "cascade") 5750 cascade = f" {cascade}" if cascade else "" 5751 5752 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5753 5754 def grant_sql(self, expression: exp.Grant) -> str: 5755 return self._grant_or_revoke_sql( 5756 expression, 5757 keyword="GRANT", 5758 preposition="TO", 5759 grant_option_suffix=" WITH GRANT OPTION", 5760 ) 5761 5762 def revoke_sql(self, expression: exp.Revoke) -> str: 5763 return self._grant_or_revoke_sql( 5764 expression, 5765 keyword="REVOKE", 5766 preposition="FROM", 5767 grant_option_prefix="GRANT OPTION FOR ", 5768 ) 5769 5770 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5771 this = self.sql(expression, "this") 5772 columns = self.expressions(expression, flat=True) 5773 columns = f"({columns})" if columns else "" 5774 5775 return f"{this}{columns}" 5776 5777 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5778 this = self.sql(expression, "this") 5779 5780 kind = self.sql(expression, "kind") 5781 kind = f"{kind} " if kind else "" 5782 5783 return f"{kind}{this}" 5784 5785 def columns_sql(self, expression: exp.Columns) -> str: 5786 func = self.function_fallback_sql(expression) 5787 if expression.args.get("unpack"): 5788 func = f"*{func}" 5789 5790 return func 5791 5792 def overlay_sql(self, expression: exp.Overlay) -> str: 5793 this = self.sql(expression, "this") 5794 expr = self.sql(expression, "expression") 5795 from_sql = self.sql(expression, "from_") 5796 for_sql = self.sql(expression, "for_") 5797 for_sql = f" FOR {for_sql}" if for_sql else "" 5798 5799 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5800 5801 @unsupported_args("format") 5802 def todouble_sql(self, expression: exp.ToDouble) -> str: 5803 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5804 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5805 5806 def string_sql(self, expression: exp.String) -> str: 5807 this = expression.this 5808 zone = expression.args.get("zone") 5809 5810 if zone: 5811 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5812 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5813 # set for source_tz to transpile the time conversion before the STRING cast 5814 this = exp.ConvertTimezone( 5815 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5816 ) 5817 5818 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5819 5820 def median_sql(self, expression: exp.Median) -> str: 5821 if not self.SUPPORTS_MEDIAN: 5822 return self.sql( 5823 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5824 ) 5825 5826 return self.function_fallback_sql(expression) 5827 5828 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5829 filler = self.sql(expression, "this") 5830 filler = f" {filler}" if filler else "" 5831 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5832 return f"TRUNCATE{filler} {with_count}" 5833 5834 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5835 if self.SUPPORTS_UNIX_SECONDS: 5836 return self.function_fallback_sql(expression) 5837 5838 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5839 5840 return self.sql( 5841 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5842 ) 5843 5844 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5845 dim = expression.expression 5846 5847 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5848 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5849 if not (dim.is_int and dim.name == "1"): 5850 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5851 dim = None 5852 5853 # If dimension is required but not specified, default initialize it 5854 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5855 dim = exp.Literal.number(1) 5856 5857 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5858 5859 def attach_sql(self, expression: exp.Attach) -> str: 5860 this = self.sql(expression, "this") 5861 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5862 expressions = self.expressions(expression) 5863 expressions = f" ({expressions})" if expressions else "" 5864 5865 return f"ATTACH{exists_sql} {this}{expressions}" 5866 5867 def detach_sql(self, expression: exp.Detach) -> str: 5868 kind = self.sql(expression, "kind") 5869 kind = f" {kind}" if kind else "" 5870 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5871 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5872 exists = " IF EXISTS" if expression.args.get("exists") else "" 5873 if exists: 5874 kind = kind or " DATABASE" 5875 5876 this = self.sql(expression, "this") 5877 this = f" {this}" if this else "" 5878 cluster = self.sql(expression, "cluster") 5879 cluster = f" {cluster}" if cluster else "" 5880 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5881 sync = " SYNC" if expression.args.get("sync") else "" 5882 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5883 5884 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5885 this = self.sql(expression, "this") 5886 value = self.sql(expression, "expression") 5887 value = f" {value}" if value else "" 5888 return f"{this}{value}" 5889 5890 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5891 return ( 5892 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5893 ) 5894 5895 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5896 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5897 encode = f"{encode} {self.sql(expression, 'this')}" 5898 5899 properties = expression.args.get("properties") 5900 if properties: 5901 encode = f"{encode} {self.properties(properties)}" 5902 5903 return encode 5904 5905 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5906 this = self.sql(expression, "this") 5907 include = f"INCLUDE {this}" 5908 5909 column_def = self.sql(expression, "column_def") 5910 if column_def: 5911 include = f"{include} {column_def}" 5912 5913 alias = self.sql(expression, "alias") 5914 if alias: 5915 include = f"{include} AS {alias}" 5916 5917 return include 5918 5919 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5920 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5921 name = f"{prefix} {self.sql(expression, 'this')}" 5922 return self.func("XMLELEMENT", name, *expression.expressions) 5923 5924 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5925 this = self.sql(expression, "this") 5926 expr = self.sql(expression, "expression") 5927 expr = f"({expr})" if expr else "" 5928 return f"{this}{expr}" 5929 5930 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5931 partitions = self.expressions(expression, "partition_expressions") 5932 create = self.expressions(expression, "create_expressions") 5933 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5934 5935 def partitionbyrangepropertydynamic_sql( 5936 self, expression: exp.PartitionByRangePropertyDynamic 5937 ) -> str: 5938 start = self.sql(expression, "start") 5939 end = self.sql(expression, "end") 5940 5941 every = expression.args["every"] 5942 if isinstance(every, exp.Interval) and every.this.is_string: 5943 every.this.replace(exp.Literal.number(every.name)) 5944 5945 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5946 5947 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5948 name = self.sql(expression, "this") 5949 values = self.expressions(expression, flat=True) 5950 5951 return f"NAME {name} VALUE {values}" 5952 5953 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5954 kind = self.sql(expression, "kind") 5955 sample = self.sql(expression, "sample") 5956 return f"SAMPLE {sample} {kind}" 5957 5958 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5959 kind = self.sql(expression, "kind") 5960 option = self.sql(expression, "option") 5961 option = f" {option}" if option else "" 5962 this = self.sql(expression, "this") 5963 this = f" {this}" if this else "" 5964 columns = self.expressions(expression) 5965 columns = f" {columns}" if columns else "" 5966 return f"{kind}{option} STATISTICS{this}{columns}" 5967 5968 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5969 this = self.sql(expression, "this") 5970 columns = self.expressions(expression) 5971 inner_expression = self.sql(expression, "expression") 5972 inner_expression = f" {inner_expression}" if inner_expression else "" 5973 update_options = self.sql(expression, "update_options") 5974 update_options = f" {update_options} UPDATE" if update_options else "" 5975 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5976 5977 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5978 kind = self.sql(expression, "kind") 5979 kind = f" {kind}" if kind else "" 5980 return f"DELETE{kind} STATISTICS" 5981 5982 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5983 inner_expression = self.sql(expression, "expression") 5984 return f"LIST CHAINED ROWS{inner_expression}" 5985 5986 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5987 kind = self.sql(expression, "kind") 5988 this = self.sql(expression, "this") 5989 this = f" {this}" if this else "" 5990 inner_expression = self.sql(expression, "expression") 5991 return f"VALIDATE {kind}{this}{inner_expression}" 5992 5993 def analyze_sql(self, expression: exp.Analyze) -> str: 5994 options = self.expressions(expression, key="options", sep=" ") 5995 options = f" {options}" if options else "" 5996 kind = self.sql(expression, "kind") 5997 kind = f" {kind}" if kind else "" 5998 this = self.sql(expression, "this") 5999 this = f" {this}" if this else "" 6000 mode = self.sql(expression, "mode") 6001 mode = f" {mode}" if mode else "" 6002 properties = self.sql(expression, "properties") 6003 properties = f" {properties}" if properties else "" 6004 partition = self.sql(expression, "partition") 6005 partition = f" {partition}" if partition else "" 6006 inner_expression = self.sql(expression, "expression") 6007 inner_expression = f" {inner_expression}" if inner_expression else "" 6008 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 6009 6010 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6011 this = self.sql(expression, "this") 6012 namespaces = self.expressions(expression, key="namespaces") 6013 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6014 passing = self.expressions(expression, key="passing") 6015 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6016 columns = self.expressions(expression, key="columns") 6017 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6018 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6019 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 6020 6021 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 6022 this = self.sql(expression, "this") 6023 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 6024 6025 def export_sql(self, expression: exp.Export) -> str: 6026 this = self.sql(expression, "this") 6027 connection = self.sql(expression, "connection") 6028 connection = f"WITH CONNECTION {connection} " if connection else "" 6029 options = self.sql(expression, "options") 6030 return f"EXPORT DATA {connection}{options} AS {this}" 6031 6032 def declare_sql(self, expression: exp.Declare) -> str: 6033 replace = "OR REPLACE " if expression.args.get("replace") else "" 6034 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 6035 6036 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6037 variables = self.expressions(expression, "this") 6038 default = self.sql(expression, "default") 6039 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6040 6041 kind = self.sql(expression, "kind") 6042 if isinstance(expression.args.get("kind"), exp.Schema): 6043 kind = f"TABLE {kind}" 6044 6045 kind = f" {kind}" if kind else "" 6046 6047 return f"{variables}{kind}{default}" 6048 6049 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6050 kind = self.sql(expression, "kind") 6051 this = self.sql(expression, "this") 6052 set = self.sql(expression, "expression") 6053 using = self.sql(expression, "using") 6054 using = f" USING {using}" if using else "" 6055 6056 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6057 6058 return f"{kind_sql} {this} SET {set}{using}" 6059 6060 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 6061 params = self.expressions(expression, key="params", flat=True) 6062 return self.func(expression.name, *expression.expressions) + f"({params})" 6063 6064 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 6065 return self.func(expression.name, *expression.expressions) 6066 6067 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 6068 return self.anonymousaggfunc_sql(expression) 6069 6070 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 6071 return self.parameterizedagg_sql(expression) 6072 6073 def show_sql(self, expression: exp.Show) -> str: 6074 self.unsupported("Unsupported SHOW statement") 6075 return "" 6076 6077 def install_sql(self, expression: exp.Install) -> str: 6078 self.unsupported("Unsupported INSTALL statement") 6079 return "" 6080 6081 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6082 # Snowflake GET/PUT statements: 6083 # PUT <file> <internalStage> <properties> 6084 # GET <internalStage> <file> <properties> 6085 props = expression.args.get("properties") 6086 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6087 this = self.sql(expression, "this") 6088 target = self.sql(expression, "target") 6089 6090 if isinstance(expression, exp.Put): 6091 return f"PUT {this} {target}{props_sql}" 6092 else: 6093 return f"GET {target} {this}{props_sql}" 6094 6095 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6096 this = self.sql(expression, "this") 6097 expr = self.sql(expression, "expression") 6098 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6099 return f"TRANSLATE({this} USING {expr}{with_error})" 6100 6101 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6102 if self.SUPPORTS_DECODE_CASE: 6103 return self.func("DECODE", *expression.expressions) 6104 6105 decode_expr, *expressions = expression.expressions 6106 6107 ifs = [] 6108 for search, result in zip(expressions[::2], expressions[1::2]): 6109 if isinstance(search, exp.Literal): 6110 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6111 elif isinstance(search, exp.Null): 6112 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6113 else: 6114 if isinstance(search, exp.Binary): 6115 search = exp.paren(search) 6116 6117 cond = exp.or_( 6118 decode_expr.eq(search), 6119 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6120 copy=False, 6121 ) 6122 ifs.append(exp.If(this=cond, true=result)) 6123 6124 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6125 return self.sql(case) 6126 6127 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6128 this = self.sql(expression, "this") 6129 this = self.seg(this, sep="") 6130 dimensions = self.expressions( 6131 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6132 ) 6133 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6134 metrics = self.expressions( 6135 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6136 ) 6137 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6138 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6139 facts = self.seg(f"FACTS {facts}") if facts else "" 6140 where = self.sql(expression, "where") 6141 where = self.seg(f"WHERE {where}") if where else "" 6142 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6143 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6144 6145 def getextract_sql(self, expression: exp.GetExtract) -> str: 6146 this = expression.this 6147 expr = expression.expression 6148 6149 if not this.type or not expression.type: 6150 import sqlglot.optimizer.annotate_types 6151 6152 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6153 6154 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6155 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6156 6157 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6158 6159 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6160 return self.sql( 6161 exp.DateAdd( 6162 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6163 expression=expression.this, 6164 unit=exp.var("DAY"), 6165 ) 6166 ) 6167 6168 def space_sql(self: Generator, expression: exp.Space) -> str: 6169 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6170 6171 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6172 return f"BUILD {self.sql(expression, 'this')}" 6173 6174 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6175 method = self.sql(expression, "method") 6176 kind = expression.args.get("kind") 6177 if not kind: 6178 return f"REFRESH {method}" 6179 6180 every = self.sql(expression, "every") 6181 unit = self.sql(expression, "unit") 6182 every = f" EVERY {every} {unit}" if every else "" 6183 starts = self.sql(expression, "starts") 6184 starts = f" STARTS {starts}" if starts else "" 6185 6186 return f"REFRESH {method} ON {kind}{every}{starts}" 6187 6188 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6189 self.unsupported("The model!attribute syntax is not supported") 6190 return "" 6191 6192 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6193 return self.func("DIRECTORY", expression.this) 6194 6195 def uuid_sql(self, expression: exp.Uuid) -> str: 6196 is_string = expression.args.get("is_string", False) 6197 uuid_func_sql = self.func("UUID") 6198 6199 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6200 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6201 6202 return uuid_func_sql 6203 6204 def initcap_sql(self, expression: exp.Initcap) -> str: 6205 delimiters = expression.expression 6206 6207 if delimiters: 6208 # do not generate delimiters arg if we are round-tripping from default delimiters 6209 if ( 6210 delimiters.is_string 6211 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6212 ): 6213 delimiters = None 6214 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6215 self.unsupported("INITCAP does not support custom delimiters") 6216 delimiters = None 6217 6218 return self.func("INITCAP", expression.this, delimiters) 6219 6220 def localtime_sql(self, expression: exp.Localtime) -> str: 6221 this = expression.this 6222 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6223 6224 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6225 this = expression.this 6226 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6227 6228 def weekstart_name(self, expression: exp.WeekStart) -> str: 6229 import sqlglot.dialects.dialect 6230 6231 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6232 this = expression.this.name.upper() 6233 6234 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6235 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6236 6237 if dow_from_week_start_day != dow_from_week_offset: 6238 self.unsupported( 6239 f"WEEK({this}) is not supported; falling back to the default week start day" 6240 ) 6241 6242 return "WEEK" 6243 6244 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6245 name = self.weekstart_name(expression) 6246 6247 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6248 if isinstance(expression.parent, exp.DateTrunc): 6249 return self.sql(exp.Literal.string(name)) 6250 6251 return name 6252 6253 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6254 this = self.expressions(expression) 6255 charset = self.sql(expression, "charset") 6256 using = f" USING {charset}" if charset else "" 6257 return self.func(name, this + using) 6258 6259 def block_sql(self, expression: exp.Block) -> str: 6260 expressions = self.expressions(expression, sep="; ", flat=True) 6261 begin = "BEGIN " if expression.args.get("begin") else "" 6262 return f"{begin}{expressions}" if expressions else "" 6263 6264 def functionspecification_sql(self, expression: exp.FunctionSpecification) -> str: 6265 self.unsupported("Unsupported Inline UDFs syntax") 6266 return "" 6267 6268 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6269 self.unsupported("Unsupported Stored Procedure syntax") 6270 return "" 6271 6272 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6273 self.unsupported("Unsupported If block syntax") 6274 return "" 6275 6276 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6277 self.unsupported("Unsupported While block syntax") 6278 return "" 6279 6280 def execute_sql(self, expression: exp.Execute) -> str: 6281 self.unsupported("Unsupported Execute syntax") 6282 return "" 6283 6284 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6285 self.unsupported("Unsupported Execute syntax") 6286 return "" 6287 6288 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6289 props = self.expressions(expression, sep=" ") 6290 return f"MODIFY {props}" 6291 6292 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6293 kind = expression.args.get("kind") 6294 return f"USING {kind} {self.sql(expression, 'this')}" 6295 6296 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6297 this = self.sql(expression, "this") 6298 to = self.sql(expression, "to") 6299 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)
875 def __init__( 876 self, 877 pretty: bool | int | None = None, 878 identify: str | bool = False, 879 normalize: bool = False, 880 pad: int = 2, 881 indent: int = 2, 882 normalize_functions: str | bool | None = None, 883 unsupported_level: ErrorLevel = ErrorLevel.WARN, 884 max_unsupported: int = 3, 885 leading_comma: bool = False, 886 max_text_width: int = 80, 887 comments: bool = True, 888 dialect: DialectType = None, 889 ): 890 import sqlglot 891 import sqlglot.dialects.dialect 892 893 self.pretty = pretty if pretty is not None else sqlglot.pretty 894 self.identify = identify 895 self.normalize = normalize 896 self.pad = pad 897 self._indent = indent 898 self.unsupported_level = unsupported_level 899 self.max_unsupported = max_unsupported 900 self.leading_comma = leading_comma 901 self.max_text_width = max_text_width 902 self.comments = comments 903 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 904 905 # This is both a Dialect property and a Generator argument, so we prioritize the latter 906 self.normalize_functions = ( 907 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 908 ) 909 910 self.unsupported_messages: list[str] = [] 911 self._escaped_quote_end: str = ( 912 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 913 ) 914 self._escaped_byte_quote_end: str = ( 915 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 916 if self.dialect.BYTE_END 917 else "" 918 ) 919 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 920 921 self._next_name = name_sequence("_t") 922 923 self._identifier_start = self.dialect.IDENTIFIER_START 924 self._identifier_end = self.dialect.IDENTIFIER_END 925 926 self._quote_json_path_key_using_brackets = True 927 928 cls = type(self) 929 dispatch = _DISPATCH_CACHE.get(cls) 930 if dispatch is None: 931 dispatch = _build_dispatch(cls) 932 _DISPATCH_CACHE[cls] = dispatch 933 self._dispatch = dispatch
TRANSFORMS: ClassVar[dict[type[sqlglot.expressions.core.Expr], Callable[..., str]]] =
{<class 'sqlglot.expressions.query.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.query.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.core.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainedBy'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.AssumeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.string.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApiProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.EndStatement'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.math.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.HybridProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.datatypes.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONBPathExists'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObject'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.json.JSONObjectAgg'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.functions.NetFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.InvisibleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.core.SafeFunc'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ddl.TriggerExecute'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.temporal.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.query.Variadic'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.array.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.constraints.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.properties.ForceProperty'>: <function Generator.<lambda>>}
WINDOW_FUNCS_WITH_NULL_ORDERING: ClassVar[tuple[type[sqlglot.expressions.core.Expression], ...]] =
()
SUPPORTED_JSON_PATH_PARTS: ClassVar =
{<class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathWildcard'>, <class 'sqlglot.expressions.query.JSONPathFilter'>, <class 'sqlglot.expressions.query.JSONPathUnion'>, <class 'sqlglot.expressions.query.JSONPathSubscript'>, <class 'sqlglot.expressions.query.JSONPathSelector'>, <class 'sqlglot.expressions.query.JSONPathSlice'>, <class 'sqlglot.expressions.query.JSONPathScript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>, <class 'sqlglot.expressions.query.JSONPathRecursive'>}
TYPE_MAPPING: ClassVar =
{<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TYPE_PARAM_SETTINGS: ClassVar[dict[sqlglot.expressions.datatypes.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]] =
{}
TIME_PART_SINGULARS: ClassVar =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS: ClassVar =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function <lambda>>, 'qualify': <function <lambda>>}
PROPERTIES_LOCATION: ClassVar =
{<class 'sqlglot.expressions.properties.AllowedValuesProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AlgorithmProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApiProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ApplicationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.AutoIncrementProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.AutoRefreshProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BackupProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.BlockCompressionProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CalledOnNullInputProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.CatalogProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CharacterSetProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ChecksumProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.CollateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ComputeProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.CopyGrantsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.query.Cluster'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusteredByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ClusterProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.ddl.Command'>, <class 'sqlglot.expressions.ddl.Create'>, <class 'sqlglot.expressions.ddl.Describe'>, <class 'sqlglot.expressions.dml.Delete'>, <class 'sqlglot.expressions.ddl.Drop'>, <class 'sqlglot.expressions.query.From'>, <class 'sqlglot.expressions.dml.Insert'>, <class 'sqlglot.expressions.query.Join'>, <class 'sqlglot.expressions.query.MultitableInserts'>, <class 'sqlglot.expressions.query.Order'>, <class 'sqlglot.expressions.query.Group'>, <class 'sqlglot.expressions.query.Having'>, <class 'sqlglot.expressions.query.Select'>, <class 'sqlglot.expressions.query.SetOperation'>, <class 'sqlglot.expressions.dml.Update'>, <class 'sqlglot.expressions.query.Where'>, <class 'sqlglot.expressions.query.With'>)
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar =
{<DType.VARCHAR: 'VARCHAR'>, <DType.NVARCHAR: 'NVARCHAR'>, <DType.CHAR: 'CHAR'>, <DType.NCHAR: 'NCHAR'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
()
935 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 936 """ 937 Generates the SQL string corresponding to the given syntax tree. 938 939 Args: 940 expression: The syntax tree. 941 copy: Whether to copy the expression. The generator performs mutations so 942 it is safer to copy. 943 944 Returns: 945 The SQL string corresponding to `expression`. 946 """ 947 if copy: 948 expression = expression.copy() 949 950 expression = self.preprocess(expression) 951 952 self.unsupported_messages = [] 953 sql = self.sql(expression).strip() 954 955 if self.pretty: 956 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 957 958 if self.unsupported_level == ErrorLevel.IGNORE: 959 return sql 960 961 if self.unsupported_level == ErrorLevel.WARN: 962 for msg in self.unsupported_messages: 963 logger.warning(msg) 964 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 965 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 966 967 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.
969 def preprocess(self, expression: exp.Expr) -> exp.Expr: 970 """Apply generic preprocessing transformations to a given expression.""" 971 expression = self._move_ctes_to_top_level(expression) 972 973 if self.ENSURE_BOOLS: 974 import sqlglot.transforms 975 976 expression = sqlglot.transforms.ensure_bools(expression) 977 978 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
1002 def sanitize_comment(self, comment: str) -> str: 1003 comment = " " + comment if comment[0].strip() else comment 1004 comment = comment + " " if comment[-1].strip() else comment 1005 1006 # Escape block comment markers to prevent premature closure or unintended nesting. 1007 # This is necessary because single-line comments (--) are converted to block comments 1008 # (/* */) on output, and any */ in the original text would close the comment early. 1009 comment = comment.replace("*/", "* /").replace("/*", "/ *") 1010 1011 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
1013 def maybe_comment( 1014 self, 1015 sql: str, 1016 expression: exp.Expr | None = None, 1017 comments: list[str] | None = None, 1018 separated: bool = False, 1019 ) -> str: 1020 comments = ( 1021 ((expression and expression.comments) if comments is None else comments) # type: ignore 1022 if self.comments 1023 else None 1024 ) 1025 1026 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1027 return sql 1028 1029 comments_list = [ 1030 f"/*{self._replace_line_breaks(self.sanitize_comment(comment))}*/" 1031 for comment in comments 1032 if comment 1033 ] 1034 1035 if not comments_list: 1036 return sql 1037 1038 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1039 comments_sql = self.sep().join(comments_list) 1040 return ( 1041 f"{self.sep()}{comments_sql}{sql}" 1042 if not sql or sql[0].isspace() 1043 else f"{comments_sql}{self.sep()}{sql}" 1044 ) 1045 1046 return f"{sql} {' '.join(comments_list)}"
1048 def wrap(self, expression: exp.Expr | str) -> str: 1049 this_sql = ( 1050 self.sql(expression) 1051 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1052 else self.sql(expression, "this") 1053 ) 1054 if not this_sql: 1055 return "()" 1056 1057 this_sql = self.indent(this_sql, level=1, pad=0) 1058 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:
1074 def indent( 1075 self, 1076 sql: str, 1077 level: int = 0, 1078 pad: int | None = None, 1079 skip_first: bool = False, 1080 skip_last: bool = False, 1081 ) -> str: 1082 if not self.pretty or not sql: 1083 return sql 1084 1085 pad = self.pad if pad is None else pad 1086 lines = sql.split("\n") 1087 1088 return "\n".join( 1089 ( 1090 line 1091 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1092 else f"{' ' * (level * self._indent + pad)}{line}" 1093 ) 1094 for i, line in enumerate(lines) 1095 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1097 def sql( 1098 self, 1099 expression: str | exp.Expr | None, 1100 key: str | None = None, 1101 comment: bool = True, 1102 ) -> str: 1103 if not expression: 1104 return "" 1105 1106 if isinstance(expression, str): 1107 return expression 1108 1109 if key: 1110 value = expression.args.get(key) 1111 if value: 1112 return self.sql(value) 1113 return "" 1114 1115 handler = self._dispatch.get(expression.__class__) 1116 1117 if handler: 1118 sql = handler(self, expression) 1119 elif isinstance(expression, exp.Func): 1120 sql = self.function_fallback_sql(expression) 1121 elif isinstance(expression, exp.Property): 1122 sql = self.property_sql(expression) 1123 else: 1124 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1125 1126 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1133 def cache_sql(self, expression: exp.Cache) -> str: 1134 lazy = " LAZY" if expression.args.get("lazy") else "" 1135 table = self.sql(expression, "this") 1136 options = expression.args.get("options") 1137 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1138 sql = self.sql(expression, "expression") 1139 sql = f" AS{self.sep()}{sql}" if sql else "" 1140 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1141 return self.prepend_ctes(expression, sql)
1159 def column_sql(self, expression: exp.Column) -> str: 1160 join_mark = " (+)" if expression.args.get("join_mark") else "" 1161 1162 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1163 join_mark = "" 1164 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1165 1166 return f"{self.column_parts(expression)}{join_mark}"
1177 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1178 column = self.sql(expression, "this") 1179 kind = self.sql(expression, "kind") 1180 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1181 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1182 kind = f"{sep}{kind}" if kind else "" 1183 constraints = f" {constraints}" if constraints else "" 1184 position = self.sql(expression, "position") 1185 position = f" {position}" if position else "" 1186 1187 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1188 kind = "" 1189 1190 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:
1197 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1198 this = self.sql(expression, "this") 1199 if expression.args.get("not_null"): 1200 persisted = " PERSISTED NOT NULL" 1201 elif expression.args.get("persisted"): 1202 persisted = " PERSISTED" 1203 else: 1204 persisted = "" 1205 1206 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:
1219 def generatedasidentitycolumnconstraint_sql( 1220 self, expression: exp.GeneratedAsIdentityColumnConstraint 1221 ) -> str: 1222 this = "" 1223 if expression.this is not None: 1224 on_null = " ON NULL" if expression.args.get("on_null") else "" 1225 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1226 1227 start = expression.args.get("start") 1228 start = f"START WITH {start}" if start else "" 1229 increment = expression.args.get("increment") 1230 increment = f" INCREMENT BY {increment}" if increment else "" 1231 minvalue = expression.args.get("minvalue") 1232 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1233 maxvalue = expression.args.get("maxvalue") 1234 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1235 cycle = expression.args.get("cycle") 1236 cycle_sql = "" 1237 1238 if cycle is not None: 1239 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1240 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1241 1242 sequence_opts = "" 1243 if start or increment or cycle_sql: 1244 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1245 sequence_opts = f" ({sequence_opts.strip()})" 1246 1247 expr = self.sql(expression, "expression") 1248 expr = f"({expr})" if expr else "IDENTITY" 1249 1250 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1252 def generatedasrowcolumnconstraint_sql( 1253 self, expression: exp.GeneratedAsRowColumnConstraint 1254 ) -> str: 1255 start = "START" if expression.args.get("start") else "END" 1256 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1257 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:
1267 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1268 desc = expression.args.get("desc") 1269 if desc is not None: 1270 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1271 options = self.expressions(expression, key="options", flat=True, sep=" ") 1272 options = f" {options}" if options else "" 1273 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1275 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1276 this = self.sql(expression, "this") 1277 this = f" {this}" if this else "" 1278 index_type = expression.args.get("index_type") 1279 index_type = f" USING {index_type}" if index_type else "" 1280 on_conflict = self.sql(expression, "on_conflict") 1281 on_conflict = f" {on_conflict}" if on_conflict else "" 1282 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1283 options = self.expressions(expression, key="options", flat=True, sep=" ") 1284 options = f" {options}" if options else "" 1285 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1287 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1288 input_ = expression.args.get("input_") 1289 output = expression.args.get("output") 1290 variadic = expression.args.get("variadic") 1291 1292 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1293 if variadic: 1294 return "VARIADIC" 1295 1296 if input_ and output: 1297 return f"IN{self.INOUT_SEPARATOR}OUT" 1298 if input_: 1299 return "IN" 1300 if output: 1301 return "OUT" 1302 1303 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1308 def create_sql(self, expression: exp.Create) -> str: 1309 kind = self.sql(expression, "kind") 1310 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1311 1312 properties = expression.args.get("properties") 1313 1314 if ( 1315 kind == "TRIGGER" 1316 and properties 1317 and properties.expressions 1318 and isinstance(properties.expressions[0], exp.TriggerProperties) 1319 and properties.expressions[0].args.get("constraint") 1320 ): 1321 kind = f"CONSTRAINT {kind}" 1322 1323 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1324 1325 this = self.createable_sql(expression, properties_locs) 1326 1327 properties_sql = "" 1328 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1329 exp.Properties.Location.POST_WITH 1330 ): 1331 props_ast = exp.Properties( 1332 expressions=[ 1333 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1334 *properties_locs[exp.Properties.Location.POST_WITH], 1335 ] 1336 ) 1337 props_ast.parent = expression 1338 properties_sql = self.sql(props_ast) 1339 1340 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1341 properties_sql = self.sep() + properties_sql 1342 elif not self.pretty: 1343 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1344 properties_sql = f" {properties_sql}" 1345 1346 begin = " BEGIN" if expression.args.get("begin") else "" 1347 1348 expression_sql = self.sql(expression, "expression") 1349 if expression_sql: 1350 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1351 1352 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1353 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1354 ): 1355 postalias_props_sql = "" 1356 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1357 postalias_props_sql = self.properties( 1358 exp.Properties( 1359 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1360 ), 1361 wrapped=False, 1362 ) 1363 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1364 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1365 1366 postindex_props_sql = "" 1367 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1368 postindex_props_sql = self.properties( 1369 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1370 wrapped=False, 1371 prefix=" ", 1372 ) 1373 1374 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1375 indexes = f" {indexes}" if indexes else "" 1376 index_sql = indexes + postindex_props_sql 1377 1378 replace = " OR REPLACE" if expression.args.get("replace") else "" 1379 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1380 unique = " UNIQUE" if expression.args.get("unique") else "" 1381 1382 clustered = expression.args.get("clustered") 1383 if clustered is None: 1384 clustered_sql = "" 1385 elif clustered: 1386 clustered_sql = " CLUSTERED COLUMNSTORE" 1387 else: 1388 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1389 1390 postcreate_props_sql = "" 1391 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1392 postcreate_props_sql = self.properties( 1393 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1394 sep=" ", 1395 prefix=" ", 1396 wrapped=False, 1397 ) 1398 1399 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1400 1401 postexpression_props_sql = "" 1402 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1403 postexpression_props_sql = self.properties( 1404 exp.Properties( 1405 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1406 ), 1407 sep=" ", 1408 prefix=" ", 1409 wrapped=False, 1410 ) 1411 1412 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1413 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1414 no_schema_binding = ( 1415 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1416 ) 1417 1418 clone = self.sql(expression, "clone") 1419 clone = f" {clone}" if clone else "" 1420 1421 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1422 properties_expression = f"{expression_sql}{properties_sql}" 1423 else: 1424 properties_expression = f"{properties_sql}{expression_sql}" 1425 1426 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1427 return self.prepend_ctes(expression, expression_sql)
1429 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1430 start = self.sql(expression, "start") 1431 start = f"START WITH {start}" if start else "" 1432 increment = self.sql(expression, "increment") 1433 increment = f" INCREMENT BY {increment}" if increment else "" 1434 minvalue = self.sql(expression, "minvalue") 1435 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1436 maxvalue = self.sql(expression, "maxvalue") 1437 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1438 owned = self.sql(expression, "owned") 1439 owned = f" OWNED BY {owned}" if owned else "" 1440 1441 cache = expression.args.get("cache") 1442 if cache is None: 1443 cache_str = "" 1444 elif cache is True: 1445 cache_str = " CACHE" 1446 else: 1447 cache_str = f" CACHE {cache}" 1448 1449 options = self.expressions(expression, key="options", flat=True, sep=" ") 1450 options = f" {options}" if options else "" 1451 1452 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1454 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1455 timing = expression.args.get("timing", "") 1456 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1457 timing_events = f"{timing} {events}".strip() if timing or events else "" 1458 1459 parts = [timing_events, "ON", self.sql(expression, "table")] 1460 1461 if referenced_table := expression.args.get("referenced_table"): 1462 parts.extend(["FROM", self.sql(referenced_table)]) 1463 1464 if deferrable := expression.args.get("deferrable"): 1465 parts.append(deferrable) 1466 1467 if initially := expression.args.get("initially"): 1468 parts.append(f"INITIALLY {initially}") 1469 1470 if referencing := expression.args.get("referencing"): 1471 parts.append(self.sql(referencing)) 1472 1473 if for_each := expression.args.get("for_each"): 1474 parts.append(f"FOR EACH {for_each}") 1475 1476 if when := expression.args.get("when"): 1477 parts.append(f"WHEN ({self.sql(when)})") 1478 1479 parts.append(self.sql(expression, "execute")) 1480 1481 return self.sep().join(parts)
1483 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1484 parts = [] 1485 1486 if old_alias := expression.args.get("old"): 1487 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1488 1489 if new_alias := expression.args.get("new"): 1490 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1491 1492 return f"REFERENCING {' '.join(parts)}"
1501 def clone_sql(self, expression: exp.Clone) -> str: 1502 this = self.sql(expression, "this") 1503 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1504 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1505 return f"{shallow}{keyword} {this}"
1507 def describe_sql(self, expression: exp.Describe) -> str: 1508 style = expression.args.get("style") 1509 style = f" {style}" if style else "" 1510 partition = self.sql(expression, "partition") 1511 partition = f" {partition}" if partition else "" 1512 format = self.sql(expression, "format") 1513 format = f" {format}" if format else "" 1514 as_json = " AS JSON" if expression.args.get("as_json") else "" 1515 1516 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1528 def with_sql(self, expression: exp.With) -> str: 1529 udfs = self.expressions(expression, key="udfs", flat=True) 1530 udfs = f"WITH {udfs}" if udfs else "" 1531 1532 sql = self.expressions(expression, flat=True) 1533 1534 recursive = ( 1535 "RECURSIVE " 1536 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1537 else "" 1538 ) 1539 search = self.sql(expression, "search") 1540 search = f" {search}" if search else "" 1541 1542 sql = f"WITH {recursive}{sql}{search}" if sql else "" 1543 return f"{udfs} {sql}" if udfs and sql else f"{udfs}{sql}"
1545 def cte_sql(self, expression: exp.CTE) -> str: 1546 alias = expression.args.get("alias") 1547 if alias: 1548 alias.add_comments(expression.pop_comments()) 1549 1550 alias_sql = self.sql(expression, "alias") 1551 1552 materialized = expression.args.get("materialized") 1553 if materialized is False: 1554 materialized = "NOT MATERIALIZED " 1555 elif materialized: 1556 materialized = "MATERIALIZED " 1557 1558 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1559 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1560 1561 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1563 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1564 alias = self.sql(expression, "this") 1565 columns = self.expressions(expression, key="columns", flat=True) 1566 columns = f"({columns})" if columns else "" 1567 1568 if ( 1569 columns 1570 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1571 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1572 ): 1573 columns = "" 1574 self.unsupported("Named columns are not supported in table alias.") 1575 1576 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1577 alias = self._next_name() 1578 1579 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1587 def hexstring_sql( 1588 self, expression: exp.HexString, binary_function_repr: str | None = None 1589 ) -> str: 1590 this = self.sql(expression, "this") 1591 is_integer_type = expression.args.get("is_integer") 1592 1593 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1594 not self.dialect.HEX_START and not binary_function_repr 1595 ): 1596 # Integer representation will be returned if: 1597 # - The read dialect treats the hex value as integer literal but not the write 1598 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1599 return f"{int(this, 16)}" 1600 1601 if not is_integer_type: 1602 # Read dialect treats the hex value as BINARY/BLOB 1603 if binary_function_repr: 1604 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1605 return self.func(binary_function_repr, exp.Literal.string(this)) 1606 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1607 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1608 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1609 1610 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1612 def bytestring_sql(self, expression: exp.ByteString) -> str: 1613 this = self.sql(expression, "this") 1614 if self.dialect.BYTE_START: 1615 escaped_byte_string = self.escape_str( 1616 this, 1617 escape_backslash=False, 1618 delimiter=self.dialect.BYTE_END, 1619 escaped_delimiter=self._escaped_byte_quote_end, 1620 is_byte_string=True, 1621 ) 1622 is_bytes = expression.args.get("is_bytes", False) 1623 delimited_byte_string = ( 1624 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1625 ) 1626 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1627 return self.sql( 1628 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1629 ) 1630 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1631 return self.sql( 1632 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1633 ) 1634 1635 return delimited_byte_string 1636 1637 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1638 return self.sql(exp.Literal.string(this)) 1639 1640 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1641 return ""
1643 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1644 this = self.sql(expression, "this") 1645 escape = expression.args.get("escape") 1646 1647 if self.dialect.UNICODE_START: 1648 escape_substitute = r"\\\1" 1649 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1650 else: 1651 escape_substitute = r"\\u\1" 1652 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1653 1654 if escape: 1655 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1656 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1657 else: 1658 escape_pattern = ESCAPED_UNICODE_RE 1659 escape_sql = "" 1660 1661 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1662 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1663 1664 return f"{left_quote}{this}{right_quote}{escape_sql}"
1666 def rawstring_sql(self, expression: exp.RawString) -> str: 1667 string = expression.this 1668 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1669 string = string.replace("\\", "\\\\") 1670 1671 string = self.escape_str(string, escape_backslash=False) 1672 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:
1680 def datatype_param_bound_limiter( 1681 self, 1682 expression: exp.DataType, 1683 type_value: exp.DType, 1684 defaults: tuple[int, ...], 1685 bounds: tuple[int | None, ...], 1686 ) -> exp.DataType: 1687 params = expression.expressions 1688 1689 if not params: 1690 if defaults: 1691 expression.set( 1692 "expressions", 1693 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1694 ) 1695 return expression 1696 1697 if not bounds: 1698 return expression 1699 1700 for i, param in enumerate(params): 1701 bound = bounds[i] if i < len(bounds) else None 1702 if bound is None: 1703 continue 1704 1705 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1706 if ( 1707 isinstance(param_value, exp.Literal) 1708 and param_value.is_number 1709 and int(param_value.to_py()) > bound 1710 ): 1711 self.unsupported( 1712 f"{type_value.value} parameter {param_value.name} exceeds " 1713 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1714 ) 1715 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1716 1717 return expression
1719 def datatype_sql(self, expression: exp.DataType) -> str: 1720 nested = "" 1721 values = "" 1722 1723 expr_nested = expression.args.get("nested") 1724 type_value = expression.this 1725 1726 if ( 1727 not expr_nested 1728 and isinstance(type_value, exp.DType) 1729 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1730 ): 1731 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1732 1733 interior = ( 1734 self.expressions( 1735 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1736 ) 1737 if expr_nested and self.pretty 1738 else self.expressions(expression, flat=True) 1739 ) 1740 1741 if type_value in self.UNSUPPORTED_TYPES: 1742 self.unsupported( 1743 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1744 ) 1745 1746 type_sql: t.Any = "" 1747 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1748 type_sql = self.sql(expression, "kind") 1749 elif type_value == exp.DType.CHARACTER_SET: 1750 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1751 else: 1752 type_sql = ( 1753 self.TYPE_MAPPING.get(type_value, type_value.value) 1754 if isinstance(type_value, exp.DType) 1755 else type_value 1756 ) 1757 1758 if interior: 1759 if expr_nested: 1760 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1761 if expression.args.get("values") is not None: 1762 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1763 values = self.expressions(expression, key="values", flat=True) 1764 values = f"{delimiters[0]}{values}{delimiters[1]}" 1765 elif type_value == exp.DType.INTERVAL: 1766 nested = f" {interior}" 1767 else: 1768 nested = f"({interior})" 1769 1770 type_sql = f"{type_sql}{nested}{values}" 1771 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1772 exp.DType.TIMETZ, 1773 exp.DType.TIMESTAMPTZ, 1774 ): 1775 type_sql = f"{type_sql} WITH TIME ZONE" 1776 1777 collate = self.sql(expression, "collate") 1778 if collate: 1779 type_sql = f"{type_sql} COLLATE {collate}" 1780 1781 return type_sql
1783 def directory_sql(self, expression: exp.Directory) -> str: 1784 local = "LOCAL " if expression.args.get("local") else "" 1785 row_format = self.sql(expression, "row_format") 1786 row_format = f" {row_format}" if row_format else "" 1787 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1789 def delete_sql(self, expression: exp.Delete) -> str: 1790 hint = self.sql(expression, "hint") 1791 this = self.sql(expression, "this") 1792 this = f" FROM {this}" if this else "" 1793 using = self.expressions(expression, key="using") 1794 using = f" USING {using}" if using else "" 1795 cluster = self.sql(expression, "cluster") 1796 cluster = f" {cluster}" if cluster else "" 1797 where = self.sql(expression, "where") 1798 returning = self.sql(expression, "returning") 1799 order = self.sql(expression, "order") 1800 limit = self.sql(expression, "limit") 1801 tables = self.expressions(expression, key="tables") 1802 tables = f" {tables}" if tables else "" 1803 if self.RETURNING_END: 1804 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1805 else: 1806 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1807 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1809 def drop_sql(self, expression: exp.Drop) -> str: 1810 this = self.sql(expression, "this") 1811 expressions = self.expressions(expression, flat=True) 1812 expressions = f" ({expressions})" if expressions else "" 1813 kind = expression.args["kind"] 1814 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1815 iceberg = ( 1816 " ICEBERG" 1817 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1818 else "" 1819 ) 1820 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1821 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1822 on_cluster = self.sql(expression, "cluster") 1823 on_cluster = f" {on_cluster}" if on_cluster else "" 1824 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1825 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1826 cascade = " CASCADE" if expression.args.get("cascade") else "" 1827 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1828 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1829 purge = " PURGE" if expression.args.get("purge") else "" 1830 sync = " SYNC" if expression.args.get("sync") else "" 1831 force = " FORCE" if expression.args.get("force") else "" 1832 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}{force}"
1834 def set_operation(self, expression: exp.SetOperation) -> str: 1835 op_type = type(expression) 1836 op_name = op_type.key.upper() 1837 1838 distinct = expression.args.get("distinct") 1839 if ( 1840 distinct is False 1841 and op_type in (exp.Except, exp.Intersect) 1842 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1843 ): 1844 self.unsupported(f"{op_name} ALL is not supported") 1845 1846 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1847 1848 if distinct is None: 1849 distinct = default_distinct 1850 if distinct is None: 1851 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1852 1853 if distinct is default_distinct: 1854 distinct_or_all = "" 1855 else: 1856 distinct_or_all = " DISTINCT" if distinct else " ALL" 1857 1858 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1859 side_kind = f"{side_kind} " if side_kind else "" 1860 1861 by_name = " BY NAME" if expression.args.get("by_name") else "" 1862 on = self.expressions(expression, key="on", flat=True) 1863 on = f" ON ({on})" if on else "" 1864 1865 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1867 def set_operations(self, expression: exp.SetOperation) -> str: 1868 if not self.SET_OP_MODIFIERS: 1869 limit = expression.args.get("limit") 1870 order = expression.args.get("order") 1871 1872 if limit or order: 1873 select = self._move_ctes_to_top_level( 1874 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1875 ) 1876 1877 if limit: 1878 select = select.limit(limit.pop(), copy=False) 1879 if order: 1880 select = select.order_by(order.pop(), copy=False) 1881 return self.sql(select) 1882 1883 sqls: list[str] = [] 1884 stack: list[str | exp.Expr] = [expression] 1885 1886 while stack: 1887 node = stack.pop() 1888 1889 if isinstance(node, exp.SetOperation): 1890 stack.append(node.expression) 1891 stack.append( 1892 self.maybe_comment( 1893 self.set_operation(node), comments=node.comments, separated=True 1894 ) 1895 ) 1896 stack.append(node.this) 1897 else: 1898 sqls.append(self.sql(node)) 1899 1900 this = self.sep().join(sqls) 1901 this = self.query_modifiers(expression, this) 1902 return self.prepend_ctes(expression, this)
1904 def fetch_sql(self, expression: exp.Fetch) -> str: 1905 direction = expression.args.get("direction") 1906 direction = f" {direction}" if direction else "" 1907 count = self.sql(expression, "count") 1908 count = f" {count}" if count else "" 1909 limit_options = self.sql(expression, "limit_options") 1910 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1911 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1913 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1914 percent = " PERCENT" if expression.args.get("percent") else "" 1915 rows = " ROWS" if expression.args.get("rows") else "" 1916 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1917 if not with_ties and rows: 1918 with_ties = " ONLY" 1919 return f"{percent}{rows}{with_ties}"
1933 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1934 using = self.sql(expression, "using") 1935 using = f" USING {using}" if using else "" 1936 columns = self.expressions(expression, key="columns", flat=True) 1937 columns = f"({columns})" if columns else "" 1938 partition_by = self.expressions(expression, key="partition_by", flat=True) 1939 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1940 where = self.sql(expression, "where") 1941 include = self.expressions(expression, key="include", flat=True) 1942 if include: 1943 include = f" INCLUDE ({include})" 1944 with_storage = self.expressions(expression, key="with_storage", flat=True) 1945 with_storage = f" WITH ({with_storage})" if with_storage else "" 1946 tablespace = self.sql(expression, "tablespace") 1947 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1948 on = self.sql(expression, "on") 1949 on = f" ON {on}" if on else "" 1950 1951 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1953 def index_sql(self, expression: exp.Index) -> str: 1954 unique = "UNIQUE " if expression.args.get("unique") else "" 1955 primary = "PRIMARY " if expression.args.get("primary") else "" 1956 amp = "AMP " if expression.args.get("amp") else "" 1957 name = self.sql(expression, "this") 1958 name = f"{name} " if name else "" 1959 table = self.sql(expression, "table") 1960 table = f"{self.INDEX_ON} {table}" if table else "" 1961 1962 index = "INDEX " if not table else "" 1963 1964 params = self.sql(expression, "params") 1965 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1967 def dynamicidentifier_sql(self, expression: exp.DynamicIdentifier) -> str: 1968 this = expression.this 1969 if this and this.is_string: 1970 resolved = maybe_parse(this.name).sql(self.dialect) 1971 if "expressions" in expression.args: 1972 # `IDENTIFIER(...)` invoked as a function, e.g. `IDENTIFIER('my_func')(1, 2)` 1973 # We can't safely emit the call to other dialects since name/arg semantics may differ 1974 self.unsupported( 1975 "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported" 1976 ) 1977 return resolved 1978 self.unsupported("IDENTIFIER() with non-literal arguments is not supported") 1979 return self.func("IDENTIFIER", this)
1981 def identifier_sql(self, expression: exp.Identifier) -> str: 1982 text = expression.name 1983 lower = text.lower() 1984 quoted = expression.quoted 1985 text = lower if self.normalize and not quoted else text 1986 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1987 if ( 1988 quoted 1989 or self.dialect.can_quote(expression, self.identify) 1990 or lower in self.RESERVED_KEYWORDS 1991 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1992 ): 1993 text = ( 1994 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1995 ) 1996 return text
2011 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 2012 input_format = self.sql(expression, "input_format") 2013 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 2014 output_format = self.sql(expression, "output_format") 2015 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 2016 return self.sep().join((input_format, output_format))
2026 def properties_sql(self, expression: exp.Properties) -> str: 2027 root_properties = [] 2028 with_properties = [] 2029 2030 for p in expression.expressions: 2031 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2032 if p_loc == exp.Properties.Location.POST_WITH: 2033 with_properties.append(p) 2034 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2035 root_properties.append(p) 2036 2037 root_props_ast = exp.Properties(expressions=root_properties) 2038 root_props_ast.parent = expression.parent 2039 2040 with_props_ast = exp.Properties(expressions=with_properties) 2041 with_props_ast.parent = expression.parent 2042 2043 root_props = self.root_properties(root_props_ast) 2044 with_props = self.with_properties(with_props_ast) 2045 2046 if root_props and with_props and not self.pretty: 2047 with_props = " " + with_props 2048 2049 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2056 def properties( 2057 self, 2058 properties: exp.Properties, 2059 prefix: str = "", 2060 sep: str = ", ", 2061 suffix: str = "", 2062 wrapped: bool = True, 2063 ) -> str: 2064 if properties.expressions: 2065 expressions = self.expressions(properties, sep=sep, indent=False) 2066 if expressions: 2067 expressions = self.wrap(expressions) if wrapped else expressions 2068 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2069 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2074 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2075 properties_locs = defaultdict(list) 2076 for p in properties.expressions: 2077 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2078 if p_loc != exp.Properties.Location.UNSUPPORTED: 2079 properties_locs[p_loc].append(p) 2080 else: 2081 self.unsupported(f"Unsupported property {p.key}") 2082 2083 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2090 def property_sql(self, expression: exp.Property) -> str: 2091 property_cls = expression.__class__ 2092 if property_cls == exp.Property: 2093 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2094 2095 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2096 if not property_name: 2097 self.unsupported(f"Unsupported property {expression.key}") 2098 2099 return f"{property_name}={self.sql(expression, 'this')}"
2104 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2105 if self.SUPPORTS_CREATE_TABLE_LIKE: 2106 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2107 options = f" {options}" if options else "" 2108 2109 like = f"LIKE {self.sql(expression, 'this')}{options}" 2110 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2111 like = f"({like})" 2112 2113 return like 2114 2115 if expression.expressions: 2116 self.unsupported("Transpilation of LIKE property options is unsupported") 2117 2118 select = exp.select("*").from_(expression.this).limit(0) 2119 return f"AS {self.sql(select)}"
2126 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2127 no = "NO " if expression.args.get("no") else "" 2128 local = expression.args.get("local") 2129 local = f"{local} " if local else "" 2130 dual = "DUAL " if expression.args.get("dual") else "" 2131 before = "BEFORE " if expression.args.get("before") else "" 2132 after = "AFTER " if expression.args.get("after") else "" 2133 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:
2149 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2150 if expression.args.get("no"): 2151 return "NO MERGEBLOCKRATIO" 2152 if expression.args.get("default"): 2153 return "DEFAULT MERGEBLOCKRATIO" 2154 2155 percent = " PERCENT" if expression.args.get("percent") else "" 2156 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2163 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2164 default = expression.args.get("default") 2165 minimum = expression.args.get("minimum") 2166 maximum = expression.args.get("maximum") 2167 if default or minimum or maximum: 2168 if default: 2169 prop = "DEFAULT" 2170 elif minimum: 2171 prop = "MINIMUM" 2172 else: 2173 prop = "MAXIMUM" 2174 return f"{prop} DATABLOCKSIZE" 2175 units = expression.args.get("units") 2176 units = f" {units}" if units else "" 2177 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2179 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2180 autotemp = expression.args.get("autotemp") 2181 always = expression.args.get("always") 2182 default = expression.args.get("default") 2183 manual = expression.args.get("manual") 2184 never = expression.args.get("never") 2185 2186 if autotemp is not None: 2187 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2188 elif always: 2189 prop = "ALWAYS" 2190 elif default: 2191 prop = "DEFAULT" 2192 elif manual: 2193 prop = "MANUAL" 2194 elif never: 2195 prop = "NEVER" 2196 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2198 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2199 no = expression.args.get("no") 2200 no = " NO" if no else "" 2201 concurrent = expression.args.get("concurrent") 2202 concurrent = " CONCURRENT" if concurrent else "" 2203 target = self.sql(expression, "target") 2204 target = f" {target}" if target else "" 2205 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2207 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2208 if isinstance(expression.this, list): 2209 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2210 if expression.this: 2211 modulus = self.sql(expression, "this") 2212 remainder = self.sql(expression, "expression") 2213 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2214 2215 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2216 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2217 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2219 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2220 this = self.sql(expression, "this") 2221 2222 for_values_or_default = expression.expression 2223 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2224 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2225 else: 2226 for_values_or_default = " DEFAULT" 2227 2228 return f"PARTITION OF {this}{for_values_or_default}"
2230 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2231 kind = expression.args.get("kind") 2232 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2233 for_or_in = expression.args.get("for_or_in") 2234 for_or_in = f" {for_or_in}" if for_or_in else "" 2235 lock_type = expression.args.get("lock_type") 2236 override = " OVERRIDE" if expression.args.get("override") else "" 2237 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2239 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2240 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2241 statistics = expression.args.get("statistics") 2242 statistics_sql = "" 2243 if statistics is not None: 2244 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2245 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2247 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2248 this = self.sql(expression, "this") 2249 this = f"HISTORY_TABLE={this}" if this else "" 2250 data_consistency: str | None = self.sql(expression, "data_consistency") 2251 data_consistency = ( 2252 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2253 ) 2254 retention_period: str | None = self.sql(expression, "retention_period") 2255 retention_period = ( 2256 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2257 ) 2258 2259 if this: 2260 on_sql = self.func("ON", this, data_consistency, retention_period) 2261 else: 2262 on_sql = "ON" if expression.args.get("on") else "OFF" 2263 2264 sql = f"SYSTEM_VERSIONING={on_sql}" 2265 2266 return f"WITH({sql})" if expression.args.get("with_") else sql
2268 def insert_sql(self, expression: exp.Insert) -> str: 2269 hint = self.sql(expression, "hint") 2270 overwrite = expression.args.get("overwrite") 2271 2272 if isinstance(expression.this, exp.Directory): 2273 this = " OVERWRITE" if overwrite else " INTO" 2274 else: 2275 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2276 2277 stored = self.sql(expression, "stored") 2278 stored = f" {stored}" if stored else "" 2279 alternative = expression.args.get("alternative") 2280 alternative = f" OR {alternative}" if alternative else "" 2281 ignore = " IGNORE" if expression.args.get("ignore") else "" 2282 is_function = expression.args.get("is_function") 2283 if is_function: 2284 this = f"{this} FUNCTION" 2285 this = f"{this} {self.sql(expression, 'this')}" 2286 2287 exists = " IF EXISTS" if expression.args.get("exists") else "" 2288 where = self.sql(expression, "where") 2289 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2290 using = self.expressions(expression, key="using", flat=True) 2291 using = f"{self.sep()}REPLACE USING ({using})" if using else "" 2292 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2293 on_conflict = self.sql(expression, "conflict") 2294 on_conflict = f" {on_conflict}" if on_conflict else "" 2295 by_name = " BY NAME" if expression.args.get("by_name") else "" 2296 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2297 returning = self.sql(expression, "returning") 2298 2299 if self.RETURNING_END: 2300 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2301 else: 2302 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2303 2304 partition_by = self.sql(expression, "partition") 2305 partition_by = f" {partition_by}" if partition_by else "" 2306 settings = self.sql(expression, "settings") 2307 settings = f" {settings}" if settings else "" 2308 2309 source = self.sql(expression, "source") 2310 source = f"TABLE {source}" if source else "" 2311 2312 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{using}{expression_sql}{source}" 2313 return self.prepend_ctes(expression, sql)
2331 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2332 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2333 2334 constraint = self.sql(expression, "constraint") 2335 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2336 2337 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2338 if conflict_keys: 2339 conflict_keys = f"({conflict_keys})" 2340 2341 index_predicate = self.sql(expression, "index_predicate") 2342 conflict_keys = f"{conflict_keys}{index_predicate} " 2343 2344 action = self.sql(expression, "action") 2345 2346 expressions = self.expressions(expression, flat=True) 2347 if expressions: 2348 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2349 expressions = f" {set_keyword}{expressions}" 2350 2351 where = self.sql(expression, "where") 2352 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2357 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2358 fields = self.sql(expression, "fields") 2359 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2360 escaped = self.sql(expression, "escaped") 2361 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2362 items = self.sql(expression, "collection_items") 2363 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2364 keys = self.sql(expression, "map_keys") 2365 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2366 lines = self.sql(expression, "lines") 2367 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2368 null = self.sql(expression, "null") 2369 null = f" NULL DEFINED AS {null}" if null else "" 2370 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2398 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2399 table = self.table_parts(expression) 2400 only = "ONLY " if expression.args.get("only") else "" 2401 partition = self.sql(expression, "partition") 2402 partition = f" {partition}" if partition else "" 2403 version = self.sql(expression, "version") 2404 version = f" {version}" if version else "" 2405 alias = self.sql(expression, "alias") 2406 alias = f"{sep}{alias}" if alias else "" 2407 2408 sample = self.sql(expression, "sample") 2409 post_alias = "" 2410 pre_alias = "" 2411 2412 if self.dialect.ALIAS_POST_TABLESAMPLE: 2413 pre_alias = sample 2414 else: 2415 post_alias = sample 2416 2417 if self.dialect.ALIAS_POST_VERSION: 2418 pre_alias = f"{pre_alias}{version}" 2419 else: 2420 post_alias = f"{post_alias}{version}" 2421 2422 hints = self.expressions(expression, key="hints", sep=" ") 2423 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2424 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2425 joins = self.indent( 2426 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2427 ) 2428 laterals = self.expressions(expression, key="laterals", sep="") 2429 2430 file_format = self.sql(expression, "format") 2431 pattern = self.sql(expression, "pattern") 2432 if file_format: 2433 pattern = f", PATTERN => {pattern}" if pattern else "" 2434 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2435 elif pattern: 2436 file_format = f" (PATTERN => {pattern})" 2437 2438 ordinality = expression.args.get("ordinality") or "" 2439 if ordinality: 2440 ordinality = f" WITH ORDINALITY{alias}" 2441 alias = "" 2442 2443 when = self.sql(expression, "when") 2444 if when: 2445 if self.HISTORICAL_DATA_POST_ALIAS: 2446 alias = f"{alias} {when}" 2447 else: 2448 table = f"{table} {when}" 2449 2450 changes = self.sql(expression, "changes") 2451 changes = f" {changes}" if changes else "" 2452 2453 rows_from = self.expressions(expression, key="rows_from") 2454 if rows_from: 2455 table = f"ROWS FROM {self.wrap(rows_from)}" 2456 2457 indexed = expression.args.get("indexed") 2458 if indexed is not None: 2459 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2460 else: 2461 indexed = "" 2462 2463 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2465 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2466 table = self.func("TABLE", expression.this) 2467 alias = self.sql(expression, "alias") 2468 alias = f" AS {alias}" if alias else "" 2469 sample = self.sql(expression, "sample") 2470 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2471 joins = self.indent( 2472 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2473 ) 2474 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2476 def tablesample_sql( 2477 self, 2478 expression: exp.TableSample, 2479 tablesample_keyword: str | None = None, 2480 ) -> str: 2481 method = self.sql(expression, "method") 2482 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2483 numerator = self.sql(expression, "bucket_numerator") 2484 denominator = self.sql(expression, "bucket_denominator") 2485 field = self.sql(expression, "bucket_field") 2486 field = f" ON {field}" if field else "" 2487 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2488 seed = self.sql(expression, "seed") 2489 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2490 2491 size = self.sql(expression, "size") 2492 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2493 size = f"{size} ROWS" 2494 2495 percent = self.sql(expression, "percent") 2496 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2497 percent = f"{percent} PERCENT" 2498 2499 expr = f"{bucket}{percent}{size}" 2500 if self.TABLESAMPLE_REQUIRES_PARENS: 2501 expr = f"({expr})" 2502 2503 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2580 def pivot_sql(self, expression: exp.Pivot) -> str: 2581 expressions = self.expressions(expression, flat=True) 2582 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2583 2584 group = self.sql(expression, "group") 2585 2586 if expression.this: 2587 this = self.sql(expression, "this") 2588 if not expressions: 2589 sql = f"UNPIVOT {this}" 2590 else: 2591 on = f"{self.seg('ON')} {expressions}" 2592 into = self.sql(expression, "into") 2593 into = f"{self.seg('INTO')} {into}" if into else "" 2594 using = self.expressions(expression, key="using", flat=True) 2595 using = f"{self.seg('USING')} {using}" if using else "" 2596 sql = f"{direction} {this}{on}{into}{using}{group}" 2597 return self.prepend_ctes(expression, sql) 2598 2599 if not expression.unpivot: 2600 # Wrap IN-list values with explicit aliases where the target dialect would differ 2601 new_field_exprs = self._pivot_in_value_aliases(expression) 2602 if new_field_exprs is not None: 2603 expression.fields[0].set("expressions", new_field_exprs) 2604 2605 alias = self.sql(expression, "alias") 2606 if alias: 2607 alias = f" AS {alias}" if self.PIVOT_ALIAS_WITH_AS else f" {alias}" 2608 2609 fields = self.expressions( 2610 expression, 2611 "fields", 2612 sep=" ", 2613 dynamic=True, 2614 new_line=True, 2615 skip_first=True, 2616 skip_last=True, 2617 ) 2618 2619 include_nulls = expression.args.get("include_nulls") 2620 if include_nulls is not None: 2621 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2622 else: 2623 nulls = "" 2624 2625 default_on_null = self.sql(expression, "default_on_null") 2626 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2627 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2628 return self.prepend_ctes(expression, sql)
2671 def update_sql(self, expression: exp.Update) -> str: 2672 hint = self.sql(expression, "hint") 2673 this = self.sql(expression, "this") 2674 join_sql, from_sql = self._update_from_joins_sql(expression) 2675 set_sql = self.expressions(expression, flat=True) 2676 where_sql = self.sql(expression, "where") 2677 returning = self.sql(expression, "returning") 2678 order = self.sql(expression, "order") 2679 limit = self.sql(expression, "limit") 2680 if self.RETURNING_END: 2681 expression_sql = f"{from_sql}{where_sql}{returning}" 2682 else: 2683 expression_sql = f"{returning}{from_sql}{where_sql}" 2684 options = self.expressions(expression, key="options") 2685 options = f" OPTION({options})" if options else "" 2686 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2687 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2689 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2690 values_as_table = values_as_table and self.VALUES_AS_TABLE 2691 2692 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2693 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2694 args = self.expressions(expression) 2695 alias = self.sql(expression, "alias") 2696 values = f"VALUES{self.seg('')}{args}" 2697 values = ( 2698 f"({values})" 2699 if self.WRAP_DERIVED_VALUES 2700 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2701 else values 2702 ) 2703 values = self.query_modifiers(expression, values) 2704 return f"{values} AS {alias}" if alias else values 2705 2706 # Converts `VALUES...` expression into a series of select unions. 2707 alias_node = expression.args.get("alias") 2708 column_names = alias_node and alias_node.columns 2709 2710 selects: list[exp.Query] = [] 2711 2712 for i, tup in enumerate(expression.expressions): 2713 row = tup.expressions 2714 2715 if i == 0 and column_names: 2716 row = [ 2717 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2718 ] 2719 2720 selects.append(exp.Select(expressions=row)) 2721 2722 if self.pretty: 2723 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2724 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2725 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2726 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2727 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2728 2729 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2730 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2731 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2736 @unsupported_args("expressions") 2737 def into_sql(self, expression: exp.Into) -> str: 2738 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2739 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2740 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2753 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2754 this = self.sql(expression, "this") 2755 2756 columns = self.expressions(expression, flat=True) 2757 2758 from_sql = self.sql(expression, "from_index") 2759 from_sql = f" FROM {from_sql}" if from_sql else "" 2760 2761 properties = expression.args.get("properties") 2762 properties_sql = ( 2763 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2764 ) 2765 2766 return f"{this}({columns}){from_sql}{properties_sql}"
2775 def group_sql(self, expression: exp.Group) -> str: 2776 group_by_all = expression.args.get("all") 2777 if group_by_all is True: 2778 modifier = " ALL" 2779 elif group_by_all is False: 2780 modifier = " DISTINCT" 2781 else: 2782 modifier = "" 2783 2784 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2785 2786 grouping_sets = self.expressions(expression, key="grouping_sets") 2787 cube = self.expressions(expression, key="cube") 2788 rollup = self.expressions(expression, key="rollup") 2789 2790 groupings = csv( 2791 self.seg(grouping_sets) if grouping_sets else "", 2792 self.seg(cube) if cube else "", 2793 self.seg(rollup) if rollup else "", 2794 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2795 sep=self.GROUPINGS_SEP, 2796 ) 2797 2798 if ( 2799 expression.expressions 2800 and groupings 2801 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2802 ): 2803 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2804 2805 return f"{group_by}{groupings}"
2811 def connect_sql(self, expression: exp.Connect) -> str: 2812 start = self.sql(expression, "start") 2813 start = self.seg(f"START WITH {start}") if start else "" 2814 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2815 connect = self.sql(expression, "connect") 2816 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2817 return start + connect
2822 def join_sql(self, expression: exp.Join) -> str: 2823 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2824 side = None 2825 else: 2826 side = expression.side 2827 2828 op_sql = " ".join( 2829 op 2830 for op in ( 2831 expression.method, 2832 "GLOBAL" if expression.args.get("global_") else None, 2833 side, 2834 expression.kind, 2835 expression.hint if self.JOIN_HINTS else None, 2836 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2837 ) 2838 if op 2839 ) 2840 match_cond = self.sql(expression, "match_condition") 2841 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2842 on_sql = self.sql(expression, "on") 2843 using = expression.args.get("using") 2844 2845 if not on_sql and using: 2846 on_sql = csv(*(self.sql(column) for column in using)) 2847 2848 this = expression.this 2849 this_sql = self.sql(this) 2850 2851 exprs = self.expressions(expression) 2852 if exprs: 2853 this_sql = f"{this_sql},{self.seg(exprs)}" 2854 2855 if on_sql: 2856 on_sql = self.indent(on_sql, skip_first=True) 2857 space = self.seg(" " * self.pad) if self.pretty else " " 2858 if using: 2859 on_sql = f"{space}USING ({on_sql})" 2860 else: 2861 on_sql = f"{space}ON {on_sql}" 2862 elif not op_sql: 2863 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2864 return f" {this_sql}" 2865 2866 return f", {this_sql}" 2867 2868 if op_sql != "STRAIGHT_JOIN": 2869 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2870 2871 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2872 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
def
lambda_sql( self, expression: sqlglot.expressions.query.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
2879 def lateral_op(self, expression: exp.Lateral) -> str: 2880 cross_apply = expression.args.get("cross_apply") 2881 2882 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2883 if cross_apply is True: 2884 op = "INNER JOIN " 2885 elif cross_apply is False: 2886 op = "LEFT JOIN " 2887 else: 2888 op = "" 2889 2890 return f"{op}LATERAL"
2892 def lateral_sql(self, expression: exp.Lateral) -> str: 2893 this = self.sql(expression, "this") 2894 2895 if expression.args.get("view"): 2896 alias = expression.args["alias"] 2897 columns = self.expressions(alias, key="columns", flat=True) 2898 table = f" {alias.name}" if alias.name else "" 2899 columns = f" AS {columns}" if columns else "" 2900 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2901 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2902 2903 alias = self.sql(expression, "alias") 2904 alias = f" AS {alias}" if alias else "" 2905 2906 ordinality = expression.args.get("ordinality") or "" 2907 if ordinality: 2908 ordinality = f" WITH ORDINALITY{alias}" 2909 alias = "" 2910 2911 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2913 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2914 this = self.sql(expression, "this") 2915 2916 args = [ 2917 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2918 for e in (expression.args.get(k) for k in ("offset", "expression")) 2919 if e 2920 ] 2921 2922 args_sql = ", ".join(self.sql(e) for e in args) 2923 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2924 expressions = self.expressions(expression, flat=True) 2925 limit_options = self.sql(expression, "limit_options") 2926 expressions = f" BY {expressions}" if expressions else "" 2927 2928 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2930 def offset_sql(self, expression: exp.Offset) -> str: 2931 this = self.sql(expression, "this") 2932 value = expression.expression 2933 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2934 expressions = self.expressions(expression, flat=True) 2935 expressions = f" BY {expressions}" if expressions else "" 2936 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2938 def setitem_sql(self, expression: exp.SetItem) -> str: 2939 kind = self.sql(expression, "kind") 2940 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2941 kind = "" 2942 else: 2943 kind = f"{kind} " if kind else "" 2944 this = self.sql(expression, "this") 2945 expressions = self.expressions(expression) 2946 collate = self.sql(expression, "collate") 2947 collate = f" COLLATE {collate}" if collate else "" 2948 global_ = "GLOBAL " if expression.args.get("global_") else "" 2949 return f"{global_}{kind}{this}{expressions}{collate}"
2956 def queryband_sql(self, expression: exp.QueryBand) -> str: 2957 this = self.sql(expression, "this") 2958 update = " UPDATE" if expression.args.get("update") else "" 2959 scope = self.sql(expression, "scope") 2960 scope = f" FOR {scope}" if scope else "" 2961 2962 return f"QUERY_BAND = {this}{update}{scope}"
2967 def lock_sql(self, expression: exp.Lock) -> str: 2968 if not self.LOCKING_READS_SUPPORTED: 2969 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2970 return "" 2971 2972 update = expression.args["update"] 2973 key = expression.args.get("key") 2974 if update: 2975 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2976 else: 2977 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2978 expressions = self.expressions(expression, flat=True) 2979 expressions = f" OF {expressions}" if expressions else "" 2980 wait = expression.args.get("wait") 2981 2982 if wait is not None: 2983 if isinstance(wait, exp.Literal): 2984 wait = f" WAIT {self.sql(wait)}" 2985 else: 2986 wait = " NOWAIT" if wait else " SKIP LOCKED" 2987 2988 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str( self, text: str, escape_backslash: bool = True, delimiter: str | None = None, escaped_delimiter: str | None = None, is_byte_string: bool = False) -> str:
2996 def escape_str( 2997 self, 2998 text: str, 2999 escape_backslash: bool = True, 3000 delimiter: str | None = None, 3001 escaped_delimiter: str | None = None, 3002 is_byte_string: bool = False, 3003 ) -> str: 3004 if is_byte_string: 3005 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 3006 else: 3007 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 3008 3009 if supports_escape_sequences: 3010 text = "".join( 3011 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 3012 for ch in text 3013 ) 3014 3015 delimiter = delimiter or self.dialect.QUOTE_END 3016 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 3017 3018 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
3020 def loaddata_sql(self, expression: exp.LoadData) -> str: 3021 is_overwrite = expression.args.get("overwrite") 3022 overwrite = " OVERWRITE" if is_overwrite else "" 3023 this = self.sql(expression, "this") 3024 3025 files = expression.args.get("files") 3026 if files: 3027 files_sql = self.expressions(files, flat=True) 3028 files_sql = f"FILES{self.wrap(files_sql)}" 3029 if is_overwrite: 3030 this = f" {this}" 3031 elif expression.args.get("temp"): 3032 this = f" INTO TEMP TABLE {this}" 3033 else: 3034 this = f" INTO TABLE {this}" 3035 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 3036 3037 local = " LOCAL" if expression.args.get("local") else "" 3038 inpath = f" INPATH {self.sql(expression, 'inpath')}" 3039 this = f" INTO TABLE {this}" 3040 partition = self.sql(expression, "partition") 3041 partition = f" {partition}" if partition else "" 3042 input_format = self.sql(expression, "input_format") 3043 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3044 serde = self.sql(expression, "serde") 3045 serde = f" SERDE {serde}" if serde else "" 3046 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3060 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3061 this = self.sql(expression, "this") 3062 this = f"{this} " if this else this 3063 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3064 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3066 def withfill_sql(self, expression: exp.WithFill) -> str: 3067 from_sql = self.sql(expression, "from_") 3068 from_sql = f" FROM {from_sql}" if from_sql else "" 3069 to_sql = self.sql(expression, "to") 3070 to_sql = f" TO {to_sql}" if to_sql else "" 3071 step_sql = self.sql(expression, "step") 3072 step_sql = f" STEP {step_sql}" if step_sql else "" 3073 interpolated_values = [ 3074 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3075 if isinstance(e, exp.Alias) 3076 else self.sql(e, "this") 3077 for e in expression.args.get("interpolate") or [] 3078 ] 3079 interpolate = ( 3080 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3081 ) 3082 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3134 def ordered_sql(self, expression: exp.Ordered) -> str: 3135 desc = expression.args.get("desc") 3136 asc = not desc 3137 3138 nulls_first = expression.args.get("nulls_first") 3139 nulls_last = not nulls_first 3140 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3141 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3142 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3143 3144 this = self.sql(expression, "this") 3145 3146 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3147 nulls_sort_change = "" 3148 if nulls_first and ( 3149 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3150 ): 3151 nulls_sort_change = " NULLS FIRST" 3152 elif ( 3153 nulls_last 3154 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3155 and not nulls_are_last 3156 ): 3157 nulls_sort_change = " NULLS LAST" 3158 3159 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3160 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3161 window = expression.find_ancestor(exp.Window, exp.Select) 3162 3163 if isinstance(window, exp.Window): 3164 window_this = window.this 3165 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3166 window_this = window_this.this 3167 spec = window.args.get("spec") 3168 else: 3169 window_this = None 3170 spec = None 3171 3172 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3173 # without a spec or with a ROWS spec, but not with RANGE 3174 if not ( 3175 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3176 and (not spec or spec.text("kind").upper() == "ROWS") 3177 ): 3178 if window_this and spec: 3179 self.unsupported( 3180 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3181 ) 3182 nulls_sort_change = "" 3183 elif self.NULL_ORDERING_SUPPORTED is False and ( 3184 (asc and nulls_sort_change == " NULLS LAST") 3185 or (desc and nulls_sort_change == " NULLS FIRST") 3186 ): 3187 # BigQuery does not allow these ordering/nulls combinations when used under 3188 # an aggregation func or under a window containing one 3189 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3190 3191 if isinstance(ancestor, exp.Window): 3192 ancestor = ancestor.this 3193 if isinstance(ancestor, exp.AggFunc): 3194 self.unsupported( 3195 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3196 ) 3197 nulls_sort_change = "" 3198 elif self.NULL_ORDERING_SUPPORTED is None: 3199 if expression.this.is_int: 3200 self.unsupported( 3201 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3202 ) 3203 elif not isinstance(expression.this, exp.Rand): 3204 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3205 target = self.sql(resolved) if resolved is not None else this 3206 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3207 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3208 nulls_sort_change = "" 3209 3210 with_fill = self.sql(expression, "with_fill") 3211 with_fill = f" {with_fill}" if with_fill else "" 3212 3213 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3223 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3224 partition = self.partition_by_sql(expression) 3225 order = self.sql(expression, "order") 3226 measures = self.expressions(expression, key="measures") 3227 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3228 rows = self.sql(expression, "rows") 3229 rows = self.seg(rows) if rows else "" 3230 after = self.sql(expression, "after") 3231 after = self.seg(after) if after else "" 3232 pattern = self.sql(expression, "pattern") 3233 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3234 definition_sqls = [ 3235 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3236 for definition in expression.args.get("define", []) 3237 ] 3238 definitions = self.expressions(sqls=definition_sqls) 3239 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3240 body = "".join( 3241 ( 3242 partition, 3243 order, 3244 measures, 3245 rows, 3246 after, 3247 pattern, 3248 define, 3249 ) 3250 ) 3251 alias = self.sql(expression, "alias") 3252 alias = f" {alias}" if alias else "" 3253 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3255 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3256 limit = expression.args.get("limit") 3257 3258 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3259 count = limit.args.get("count") 3260 # "FETCH FIRST ROWS ONLY" without a count means one row per the SQL 3261 # standard; emitting a bare "LIMIT" here would produce invalid SQL. 3262 limit = exp.Limit( 3263 expression=exp.maybe_copy(count) if count is not None else exp.Literal.number(1) 3264 ) 3265 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3266 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3267 3268 return csv( 3269 *sqls, 3270 *[self.sql(join) for join in expression.args.get("joins") or []], 3271 self.sql(expression, "match"), 3272 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3273 self.sql(expression, "prewhere"), 3274 self.sql(expression, "where"), 3275 self.sql(expression, "connect"), 3276 self.sql(expression, "group"), 3277 self.sql(expression, "having"), 3278 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3279 self.sql(expression, "order"), 3280 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3281 *self.after_limit_modifiers(expression), 3282 self.options_modifier(expression), 3283 self.sql(expression, "for_"), 3284 sep="", 3285 )
3291 def forclause_sql(self, expression: exp.ForClause) -> str: 3292 kind = expression.args["kind"] 3293 if kind == "BROWSE": 3294 return f"{self.sep()}FOR BROWSE" 3295 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3296 # the target dialect doesn't support QueryOption, so we drop the clause. 3297 options = self.expressions(expression, key="expressions") 3298 if not options: 3299 return "" 3300 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]:
3319 def select_sql(self, expression: exp.Select) -> str: 3320 into = expression.args.get("into") 3321 if not self.SUPPORTS_SELECT_INTO and into: 3322 into.pop() 3323 3324 hint = self.sql(expression, "hint") 3325 distinct = self.sql(expression, "distinct") 3326 distinct = f" {distinct}" if distinct else "" 3327 kind = self.sql(expression, "kind") 3328 3329 limit = expression.args.get("limit") 3330 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3331 top = self.limit_sql(limit, top=True) 3332 limit.pop() 3333 else: 3334 top = "" 3335 3336 expressions = self.expressions(expression) 3337 3338 if kind: 3339 if kind in self.SELECT_KINDS: 3340 kind = f" AS {kind}" 3341 else: 3342 if kind == "STRUCT": 3343 expressions = self.expressions( 3344 sqls=[ 3345 self.sql( 3346 exp.Struct( 3347 expressions=[ 3348 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3349 if isinstance(e, exp.Alias) 3350 else e 3351 for e in expression.expressions 3352 ] 3353 ) 3354 ) 3355 ] 3356 ) 3357 kind = "" 3358 3359 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3360 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3361 3362 exclude = expression.args.get("exclude") 3363 3364 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3365 exclude_sql = self.expressions(sqls=exclude, flat=True) 3366 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3367 3368 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3369 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3370 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3371 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3372 sql = self.query_modifiers( 3373 expression, 3374 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3375 self.sql(expression, "into", comment=False), 3376 self.sql(expression, "from_", comment=False), 3377 ) 3378 3379 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3380 if expression.args.get("with_"): 3381 sql = self.maybe_comment(sql, expression) 3382 expression.pop_comments() 3383 3384 sql = self.prepend_ctes(expression, sql) 3385 3386 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3387 expression.set("exclude", None) 3388 subquery = expression.subquery(copy=False) 3389 star = exp.Star(except_=exclude) 3390 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3391 3392 if not self.SUPPORTS_SELECT_INTO and into: 3393 if into.args.get("temporary"): 3394 table_kind = " TEMPORARY" 3395 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3396 table_kind = " UNLOGGED" 3397 else: 3398 table_kind = "" 3399 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3400 3401 return sql
3413 def star_sql(self, expression: exp.Star) -> str: 3414 except_ = self.expressions(expression, key="except_", flat=True) 3415 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3416 replace = self.expressions(expression, key="replace", flat=True) 3417 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3418 rename = self.expressions(expression, key="rename", flat=True) 3419 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3420 ilike = self.sql(expression, "ilike") 3421 ilike = f"{self.seg('ILIKE')} {ilike}" if ilike else "" 3422 return f"*{ilike}{except_}{replace}{rename}"
3438 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3439 alias = self.sql(expression, "alias") 3440 alias = f"{sep}{alias}" if alias else "" 3441 sample = self.sql(expression, "sample") 3442 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3443 alias = f"{sample}{alias}" 3444 3445 # Set to None so it's not generated again by self.query_modifiers() 3446 expression.set("sample", None) 3447 3448 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3449 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3450 return self.prepend_ctes(expression, sql)
3456 def unnest_sql(self, expression: exp.Unnest) -> str: 3457 args = self.expressions(expression, flat=True) 3458 3459 alias = expression.args.get("alias") 3460 offset = expression.args.get("offset") 3461 3462 if self.UNNEST_WITH_ORDINALITY: 3463 if alias and isinstance(offset, exp.Expr): 3464 alias.append("columns", offset) 3465 expression.set("offset", None) 3466 3467 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3468 columns = alias.columns 3469 alias = self.sql(columns[0]) if columns else "" 3470 else: 3471 alias = self.sql(alias) 3472 3473 alias = f" AS {alias}" if alias else alias 3474 if self.UNNEST_WITH_ORDINALITY: 3475 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3476 else: 3477 if isinstance(offset, exp.Expr): 3478 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3479 elif offset: 3480 suffix = f"{alias} WITH OFFSET" 3481 else: 3482 suffix = alias 3483 3484 return f"UNNEST({args}){suffix}"
3493 def window_sql(self, expression: exp.Window) -> str: 3494 this = self.sql(expression, "this") 3495 partition = self.partition_by_sql(expression) 3496 order = expression.args.get("order") 3497 order = self.order_sql(order, flat=True) if order else "" 3498 spec = self.sql(expression, "spec") 3499 alias = self.sql(expression, "alias") 3500 over = self.sql(expression, "over") or "OVER" 3501 3502 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3503 3504 first = expression.args.get("first") 3505 if first is None: 3506 first = "" 3507 else: 3508 first = "FIRST" if first else "LAST" 3509 3510 if not partition and not order and not spec and alias: 3511 return f"{this} {alias}" 3512 3513 args = self.format_args( 3514 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3515 ) 3516 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3522 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3523 kind = self.sql(expression, "kind") 3524 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3525 end = ( 3526 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3527 or "CURRENT ROW" 3528 ) 3529 3530 window_spec = f"{kind} BETWEEN {start} AND {end}" 3531 3532 exclude = self.sql(expression, "exclude") 3533 if exclude: 3534 if self.SUPPORTS_WINDOW_EXCLUDE: 3535 window_spec += f" EXCLUDE {exclude}" 3536 else: 3537 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3538 3539 return window_spec
3546 def between_sql(self, expression: exp.Between) -> str: 3547 this = self.sql(expression, "this") 3548 low = self.sql(expression, "low") 3549 high = self.sql(expression, "high") 3550 symmetric = expression.args.get("symmetric") 3551 3552 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3553 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3554 3555 flag = ( 3556 " SYMMETRIC" 3557 if symmetric 3558 else " ASYMMETRIC" 3559 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3560 else "" # silently drop ASYMMETRIC – semantics identical 3561 ) 3562 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]:
3564 def bracket_offset_expressions( 3565 self, expression: exp.Bracket, index_offset: int | None = None 3566 ) -> list[exp.Expr]: 3567 if expression.args.get("json_access"): 3568 return expression.expressions 3569 3570 return apply_index_offset( 3571 expression.this, 3572 expression.expressions, 3573 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3574 dialect=self.dialect, 3575 )
3588 def any_sql(self, expression: exp.Any) -> str: 3589 this = self.sql(expression, "this") 3590 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3591 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3592 this = self.wrap(this) 3593 return f"ANY{this}" 3594 return f"ANY {this}"
3599 def case_sql(self, expression: exp.Case) -> str: 3600 this = self.sql(expression, "this") 3601 statements = [f"CASE {this}" if this else "CASE"] 3602 3603 for e in expression.args["ifs"]: 3604 statements.append(f"WHEN {self.sql(e, 'this')}") 3605 statements.append(f"THEN {self.sql(e, 'true')}") 3606 3607 default = self.sql(expression, "default") 3608 3609 if default: 3610 statements.append(f"ELSE {default}") 3611 3612 statements.append("END") 3613 3614 if self.pretty and self.too_wide(statements): 3615 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3616 3617 return " ".join(statements)
3629 def extract_sql(self, expression: exp.Extract) -> str: 3630 import sqlglot.dialects.dialect 3631 3632 this = ( 3633 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3634 if self.NORMALIZE_EXTRACT_DATE_PARTS 3635 else expression.this 3636 ) 3637 if self.EXTRACT_ALLOWS_QUOTES: 3638 this_sql = self.sql(this) 3639 elif isinstance(this, exp.WeekStart): 3640 this_sql = self.weekstart_name(this) 3641 else: 3642 this_sql = this.name 3643 expression_sql = self.sql(expression, "expression") 3644 3645 return f"EXTRACT({this_sql} FROM {expression_sql})"
3647 def trim_sql(self, expression: exp.Trim) -> str: 3648 trim_type = self.sql(expression, "position") 3649 3650 if trim_type == "LEADING": 3651 func_name = "LTRIM" 3652 elif trim_type == "TRAILING": 3653 func_name = "RTRIM" 3654 else: 3655 func_name = "TRIM" 3656 3657 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3659 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3660 args = expression.expressions 3661 if isinstance(expression, exp.ConcatWs): 3662 args = args[1:] # Skip the delimiter 3663 3664 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3665 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3666 3667 concat_coalesce = ( 3668 self.dialect.CONCAT_WS_COALESCE 3669 if isinstance(expression, exp.ConcatWs) 3670 else self.dialect.CONCAT_COALESCE 3671 ) 3672 3673 if not concat_coalesce and expression.args.get("coalesce"): 3674 3675 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3676 if not e.type: 3677 import sqlglot.optimizer.annotate_types 3678 3679 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3680 3681 if e.is_string or e.is_type(exp.DType.ARRAY): 3682 return e 3683 3684 return exp.func("coalesce", e, exp.Literal.string("")) 3685 3686 args = [_wrap_with_coalesce(e) for e in args] 3687 3688 return args
3690 def concat_sql(self, expression: exp.Concat) -> str: 3691 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3692 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3693 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3694 # instead of coalescing them to empty string. 3695 import sqlglot.dialects.dialect 3696 3697 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3698 3699 expressions = self.convert_concat_args(expression) 3700 3701 # Some dialects don't allow a single-argument CONCAT call 3702 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3703 return self.sql(expressions[0]) 3704 3705 return self.func("CONCAT", *expressions)
3707 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3708 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3709 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3710 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3711 all_args = expression.expressions 3712 expression.set("coalesce", True) 3713 return self.sql( 3714 exp.case() 3715 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3716 .else_(expression) 3717 ) 3718 3719 return self.func( 3720 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3721 )
3727 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3728 expressions = self.expressions(expression, flat=True) 3729 expressions = f" ({expressions})" if expressions else "" 3730 reference = self.sql(expression, "reference") 3731 reference = f" {reference}" if reference else "" 3732 delete = self.sql(expression, "delete") 3733 delete = f" ON DELETE {delete}" if delete else "" 3734 update = self.sql(expression, "update") 3735 update = f" ON UPDATE {update}" if update else "" 3736 options = self.expressions(expression, key="options", flat=True, sep=" ") 3737 options = f" {options}" if options else "" 3738 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3740 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3741 this = self.sql(expression, "this") 3742 this = f" {this}" if this else "" 3743 expressions = self.expressions(expression, flat=True) 3744 include = self.sql(expression, "include") 3745 options = self.expressions(expression, key="options", flat=True, sep=" ") 3746 options = f" {options}" if options else "" 3747 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3756 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3757 if self.MATCH_AGAINST_TABLE_PREFIX: 3758 expressions = [] 3759 for expr in expression.expressions: 3760 if isinstance(expr, exp.Table): 3761 expressions.append(f"TABLE {self.sql(expr)}") 3762 else: 3763 expressions.append(expr) 3764 else: 3765 expressions = expression.expressions 3766 3767 modifier = expression.args.get("modifier") 3768 modifier = f" {modifier}" if modifier else "" 3769 return ( 3770 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3771 )
3784 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3785 if isinstance(expression, exp.JSONPathPart): 3786 transform = self.TRANSFORMS.get(expression.__class__) 3787 if not callable(transform): 3788 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3789 return "" 3790 3791 return transform(self, expression) 3792 3793 if isinstance(expression, int): 3794 return str(expression) 3795 3796 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3797 escaped = expression.replace("'", "\\'") 3798 escaped = f"\\'{expression}\\'" 3799 else: 3800 escaped = expression.replace('"', '\\"') 3801 escaped = f'"{escaped}"' 3802 3803 return escaped
3808 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3809 # Output the Teradata column FORMAT override. 3810 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3811 this = self.sql(expression, "this") 3812 fmt = self.sql(expression, "format") 3813 return f"{this} (FORMAT {fmt})"
3841 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3842 null_handling = expression.args.get("null_handling") 3843 null_handling = f" {null_handling}" if null_handling else "" 3844 return_type = self.sql(expression, "return_type") 3845 return_type = f" RETURNING {return_type}" if return_type else "" 3846 strict = " STRICT" if expression.args.get("strict") else "" 3847 return self.func( 3848 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3849 )
3851 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3852 this = self.sql(expression, "this") 3853 order = self.sql(expression, "order") 3854 null_handling = expression.args.get("null_handling") 3855 null_handling = f" {null_handling}" if null_handling else "" 3856 return_type = self.sql(expression, "return_type") 3857 return_type = f" RETURNING {return_type}" if return_type else "" 3858 strict = " STRICT" if expression.args.get("strict") else "" 3859 return self.func( 3860 "JSON_ARRAYAGG", 3861 this, 3862 suffix=f"{order}{null_handling}{return_type}{strict})", 3863 )
3865 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3866 path = self.sql(expression, "path") 3867 path = f" PATH {path}" if path else "" 3868 nested_schema = self.sql(expression, "nested_schema") 3869 3870 if nested_schema: 3871 return f"NESTED{path} {nested_schema}" 3872 3873 this = self.sql(expression, "this") 3874 kind = self.sql(expression, "kind") 3875 kind = f" {kind}" if kind else "" 3876 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3877 3878 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3879 return f"{this}{kind}{format_json}{path}{ordinality}"
3884 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3885 this = self.sql(expression, "this") 3886 path = self.sql(expression, "path") 3887 path = f", {path}" if path else "" 3888 error_handling = expression.args.get("error_handling") 3889 error_handling = f" {error_handling}" if error_handling else "" 3890 empty_handling = expression.args.get("empty_handling") 3891 empty_handling = f" {empty_handling}" if empty_handling else "" 3892 schema = self.sql(expression, "schema") 3893 return self.func( 3894 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3895 )
3897 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3898 this = self.sql(expression, "this") 3899 kind = self.sql(expression, "kind") 3900 path = self.sql(expression, "path") 3901 path = f" {path}" if path else "" 3902 as_json = " AS JSON" if expression.args.get("as_json") else "" 3903 return f"{this} {kind}{path}{as_json}"
3905 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3906 this = self.sql(expression, "this") 3907 path = self.sql(expression, "path") 3908 path = f", {path}" if path else "" 3909 expressions = self.expressions(expression) 3910 with_ = ( 3911 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3912 if expressions 3913 else "" 3914 ) 3915 return f"OPENJSON({this}{path}){with_}"
3917 def in_sql(self, expression: exp.In) -> str: 3918 query = expression.args.get("query") 3919 unnest = expression.args.get("unnest") 3920 field = expression.args.get("field") 3921 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3922 3923 if query: 3924 in_sql = self.sql(query) 3925 elif unnest: 3926 in_sql = self.in_unnest_op(unnest) 3927 elif field: 3928 in_sql = self.sql(field) 3929 else: 3930 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3931 3932 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3937 def interval_sql(self, expression: exp.Interval) -> str: 3938 include_keyword = not self.AUTO_REFRESH_BARE_INTERVALS or not isinstance( 3939 expression.find_ancestor(exp.AutoRefreshProperty, exp.Select), 3940 exp.AutoRefreshProperty, 3941 ) 3942 interval_keyword = "INTERVAL" if include_keyword else "" 3943 unit_expression = expression.args.get("unit") 3944 unit = self.sql(unit_expression) if unit_expression else "" 3945 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3946 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3947 unit = f" {unit}" if unit else "" 3948 3949 if self.SINGLE_STRING_INTERVAL: 3950 this = expression.this.name if expression.this else "" 3951 if this: 3952 interval_keyword = f"{interval_keyword} " if interval_keyword else "" 3953 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3954 return f"{interval_keyword}'{this}'{unit}" 3955 return f"{interval_keyword}'{this}{unit}'" 3956 return f"{interval_keyword}{unit}" 3957 3958 this = self.sql(expression, "this") 3959 if this: 3960 if not include_keyword and expression.this.is_string: 3961 this = expression.this.name 3962 if not isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES): 3963 this = f"({this})" 3964 if include_keyword: 3965 this = f" {this}" 3966 3967 return f"{interval_keyword}{this}{unit}"
3972 def reference_sql(self, expression: exp.Reference) -> str: 3973 this = self.sql(expression, "this") 3974 expressions = self.expressions(expression, flat=True) 3975 expressions = f"({expressions})" if expressions else "" 3976 options = self.expressions(expression, key="options", flat=True, sep=" ") 3977 options = f" {options}" if options else "" 3978 return f"REFERENCES {this}{expressions}{options}"
3980 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3981 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3982 parent = expression.parent 3983 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3984 3985 return self.func( 3986 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3987 )
4007 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 4008 alias = expression.args["alias"] 4009 4010 parent = expression.parent 4011 pivot = parent and parent.parent 4012 4013 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 4014 identifier_alias = isinstance(alias, exp.Identifier) 4015 literal_alias = isinstance(alias, exp.Literal) 4016 4017 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4018 alias.replace(exp.Literal.string(alias.output_name)) 4019 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 4020 alias.replace(exp.to_identifier(alias.output_name)) 4021 4022 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:
4063 def connector_sql( 4064 self, 4065 expression: exp.Connector, 4066 op: str, 4067 stack: list[str | exp.Expr] | None = None, 4068 ) -> str: 4069 if stack is not None: 4070 stack.append(expression.right) 4071 if expression.comments and self.comments: 4072 op = self.maybe_comment(op, comments=expression.comments) 4073 4074 stack.extend((op, expression.left)) 4075 return op 4076 4077 stack = [expression] 4078 sqls: list[str] = [] 4079 ops = set() 4080 4081 while stack: 4082 node = stack.pop() 4083 if isinstance(node, exp.Connector): 4084 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4085 else: 4086 sql = self.sql(node) 4087 if sqls and sqls[-1] in ops: 4088 sqls[-1] += f" {sql}" 4089 else: 4090 sqls.append(sql) 4091 4092 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4093 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4113 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4114 format_sql = self.sql(expression, "format") 4115 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4116 to_sql = self.sql(expression, "to") 4117 to_sql = f" {to_sql}" if to_sql else "" 4118 action = self.sql(expression, "action") 4119 action = f" {action}" if action else "" 4120 default = self.sql(expression, "default") 4121 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4122 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4152 def comment_sql(self, expression: exp.Comment) -> str: 4153 this = self.sql(expression, "this") 4154 kind = expression.args["kind"] 4155 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4156 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4157 expression_sql = self.sql(expression, "expression") 4158 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4160 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4161 this = self.sql(expression, "this") 4162 delete = " DELETE" if expression.args.get("delete") else "" 4163 recompress = self.sql(expression, "recompress") 4164 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4165 to_disk = self.sql(expression, "to_disk") 4166 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4167 to_volume = self.sql(expression, "to_volume") 4168 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4169 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4171 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4172 where = self.sql(expression, "where") 4173 group = self.sql(expression, "group") 4174 aggregates = self.expressions(expression, key="aggregates") 4175 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4176 4177 if not (where or group or aggregates) and len(expression.expressions) == 1: 4178 return f"TTL {self.expressions(expression, flat=True)}" 4179 4180 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4199 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4200 this = self.sql(expression, "this") 4201 4202 dtype = self.sql(expression, "dtype") 4203 if dtype: 4204 collate = self.sql(expression, "collate") 4205 collate = f" COLLATE {collate}" if collate else "" 4206 using = self.sql(expression, "using") 4207 using = f" USING {using}" if using else "" 4208 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4209 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 4210 4211 default = self.sql(expression, "default") 4212 if default: 4213 return f"ALTER COLUMN {this} SET DEFAULT {default}" 4214 4215 comment = self.sql(expression, "comment") 4216 if comment: 4217 return f"ALTER COLUMN {this} COMMENT {comment}" 4218 4219 visible = expression.args.get("visible") 4220 if visible: 4221 return f"ALTER COLUMN {this} SET {visible}" 4222 4223 allow_null = expression.args.get("allow_null") 4224 drop = expression.args.get("drop") 4225 4226 if not drop and not allow_null: 4227 self.unsupported("Unsupported ALTER COLUMN syntax") 4228 4229 if allow_null is not None: 4230 keyword = "DROP" if drop else "SET" 4231 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4232 4233 return f"ALTER COLUMN {this} DROP DEFAULT"
4235 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4236 this = self.sql(expression, "this") 4237 rename_from = self.sql(expression, "rename_from") 4238 if rename_from: 4239 if not self.SUPPORTS_CHANGE_COLUMN: 4240 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4241 return f"CHANGE COLUMN {rename_from} {this}" 4242 if not self.SUPPORTS_MODIFY_COLUMN: 4243 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4244 return f"MODIFY COLUMN {this}"
4260 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4261 compound = " COMPOUND" if expression.args.get("compound") else "" 4262 this = self.sql(expression, "this") 4263 expressions = self.expressions(expression, flat=True) 4264 expressions = f"({expressions})" if expressions else "" 4265 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4267 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4268 if not self.RENAME_TABLE_WITH_DB: 4269 # Remove db from tables 4270 expression = expression.transform( 4271 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4272 ).assert_is(exp.AlterRename) 4273 this = self.sql(expression, "this") 4274 to_kw = " TO" if include_to else "" 4275 return f"RENAME{to_kw} {this}"
4290 def alter_sql(self, expression: exp.Alter) -> str: 4291 actions = expression.args["actions"] 4292 4293 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4294 actions[0], exp.ColumnDef 4295 ): 4296 actions_sql = self.expressions(expression, key="actions", flat=True) 4297 actions_sql = f"ADD {actions_sql}" 4298 else: 4299 actions_list = [] 4300 for action in actions: 4301 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4302 action_sql = self.add_column_sql(action) 4303 else: 4304 action_sql = self.sql(action) 4305 if isinstance(action, exp.Query): 4306 action_sql = f"AS {action_sql}" 4307 4308 actions_list.append(action_sql) 4309 4310 actions_sql = self.format_args(*actions_list).lstrip("\n") 4311 4312 iceberg = ( 4313 "ICEBERG " 4314 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4315 else "" 4316 ) 4317 exists = " IF EXISTS" if expression.args.get("exists") else "" 4318 on_cluster = self.sql(expression, "cluster") 4319 on_cluster = f" {on_cluster}" if on_cluster else "" 4320 only = " ONLY" if expression.args.get("only") else "" 4321 options = self.expressions(expression, key="options") 4322 options = f", {options}" if options else "" 4323 kind = self.sql(expression, "kind") 4324 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4325 check = " WITH CHECK" if expression.args.get("check") else "" 4326 cascade = ( 4327 " CASCADE" 4328 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4329 else "" 4330 ) 4331 this = self.sql(expression, "this") 4332 this = f" {this}" if this else "" 4333 4334 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4341 def add_column_sql(self, expression: exp.Expr) -> str: 4342 sql = self.sql(expression) 4343 if isinstance(expression, exp.Schema): 4344 column_text = " COLUMNS" 4345 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4346 column_text = " COLUMN" 4347 else: 4348 column_text = "" 4349 4350 return f"ADD{column_text} {sql}"
4363 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4364 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4365 location = self.sql(expression, "location") 4366 location = f" {location}" if location else "" 4367 return f"ADD {exists}{self.sql(expression.this)}{location}"
4369 def distinct_sql(self, expression: exp.Distinct) -> str: 4370 this = self.expressions(expression, flat=True) 4371 4372 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4373 case = exp.case() 4374 for arg in expression.expressions: 4375 case = case.when(arg.is_(exp.null()), exp.null()) 4376 this = self.sql(case.else_(f"({this})")) 4377 4378 this = f" {this}" if this else "" 4379 4380 on = self.sql(expression, "on") 4381 on = f" ON {on}" if on else "" 4382 return f"DISTINCT{this}{on}"
4409 def div_sql(self, expression: exp.Div) -> str: 4410 l, r = expression.left, expression.right 4411 4412 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4413 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4414 4415 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4416 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4417 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4418 4419 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4420 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4421 return self.sql( 4422 exp.cast( 4423 l / r, 4424 to=exp.DType.BIGINT, 4425 ) 4426 ) 4427 4428 return self.binary(expression, "/")
4453 def escape_sql(self, expression: exp.Escape) -> str: 4454 this = expression.this 4455 if ( 4456 isinstance(this, (exp.Like, exp.ILike)) 4457 and isinstance(this.expression, (exp.All, exp.Any)) 4458 and not self.SUPPORTS_LIKE_QUANTIFIERS 4459 ): 4460 return self._like_sql(this, escape=expression) 4461 return self.binary(expression, "ESCAPE")
4472 def is_sql(self, expression: exp.Is) -> str: 4473 negate = expression.args.get("negate") 4474 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4475 positive = bool(expression.expression.this) != bool(negate) 4476 return self.sql(expression.this if positive else exp.not_(expression.this)) 4477 return self.binary(expression, "IS NOT" if negate else "IS")
4578 def log_sql(self, expression: exp.Log) -> str: 4579 this = expression.this 4580 expr = expression.expression 4581 4582 if self.dialect.LOG_BASE_FIRST is False: 4583 this, expr = expr, this 4584 elif self.dialect.LOG_BASE_FIRST is None and expr: 4585 if this.name in ("2", "10"): 4586 return self.func(f"LOG{this.name}", expr) 4587 4588 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4589 4590 return self.func("LOG", this, expr)
4599 def binary(self, expression: exp.Binary, op: str) -> str: 4600 sqls: list[str] = [] 4601 stack: list[None | str | exp.Expr] = [expression] 4602 binary_type = type(expression) 4603 4604 while stack: 4605 node = stack.pop() 4606 4607 if type(node) is binary_type: 4608 op_func = node.args.get("operator") 4609 if op_func: 4610 op = f"OPERATOR({self.sql(op_func)})" 4611 4612 stack.append(node.args.get("expression")) 4613 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4614 stack.append(node.args.get("this")) 4615 else: 4616 sqls.append(self.sql(node)) 4617 4618 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4627 def function_fallback_sql(self, expression: exp.Func) -> str: 4628 args = [] 4629 4630 for key in expression.arg_types: 4631 arg_value = expression.args.get(key) 4632 4633 if isinstance(arg_value, list): 4634 for value in arg_value: 4635 args.append(value) 4636 elif arg_value is not None: 4637 args.append(arg_value) 4638 4639 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4640 name = expression.meta_get("name") or expression.sql_name() 4641 else: 4642 name = expression.sql_name() 4643 4644 return self.func(name, *args)
def
func( self, name: str, *args: Any, prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
def
format_args(self, *args: Any, sep: str = ', ') -> str:
4657 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4658 arg_sqls = tuple( 4659 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4660 ) 4661 if self.pretty and self.too_wide(arg_sqls): 4662 return self.indent( 4663 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4664 ) 4665 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:
4670 def format_time( 4671 self, 4672 expression: exp.Expr, 4673 inverse_time_mapping: dict[str, str] | None = None, 4674 inverse_time_trie: dict | None = None, 4675 ) -> str | None: 4676 return format_time( 4677 self.sql(expression, "format"), 4678 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4679 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4680 )
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:
4682 def expressions( 4683 self, 4684 expression: exp.Expr | None = None, 4685 key: str | None = None, 4686 sqls: t.Collection[str | exp.Expr] | None = None, 4687 flat: bool = False, 4688 indent: bool = True, 4689 skip_first: bool = False, 4690 skip_last: bool = False, 4691 sep: str = ", ", 4692 prefix: str = "", 4693 dynamic: bool = False, 4694 new_line: bool = False, 4695 ) -> str: 4696 expressions = expression.args.get(key or "expressions") if expression else sqls 4697 4698 if not expressions: 4699 return "" 4700 4701 if flat: 4702 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4703 4704 num_sqls = len(expressions) 4705 result_sqls = [] 4706 4707 for i, e in enumerate(expressions): 4708 sql = self.sql(e, comment=False) 4709 if not sql: 4710 continue 4711 4712 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4713 4714 if self.pretty: 4715 if self.leading_comma: 4716 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4717 else: 4718 result_sqls.append( 4719 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4720 ) 4721 else: 4722 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4723 4724 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4725 if new_line: 4726 result_sqls.insert(0, "") 4727 result_sqls.append("") 4728 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4729 else: 4730 result_sql = "".join(result_sqls) 4731 4732 return ( 4733 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4734 if indent 4735 else result_sql 4736 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4738 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4739 flat = flat or isinstance(expression.parent, exp.Properties) 4740 expressions_sql = self.expressions(expression, flat=flat) 4741 if flat: 4742 return f"{op} {expressions_sql}" 4743 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4745 def naked_property(self, expression: exp.Property) -> str: 4746 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4747 if not property_name: 4748 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4749 return f"{property_name} {self.sql(expression, 'this')}"
4757 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4758 this = self.sql(expression, "this") 4759 expressions = self.no_identify(self.expressions, expression) 4760 expressions = ( 4761 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4762 ) 4763 return f"{this}{expressions}" if expressions.strip() != "" else this
4782 def when_sql(self, expression: exp.When) -> str: 4783 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4784 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4785 condition = self.sql(expression, "condition") 4786 condition = f" AND {condition}" if condition else "" 4787 4788 then_expression = expression.args.get("then") 4789 if isinstance(then_expression, exp.Insert): 4790 this = self.sql(then_expression, "this") 4791 this = f"INSERT {this}" if this else "INSERT" 4792 then = self.sql(then_expression, "expression") 4793 then = f"{this} VALUES {then}" if then else this 4794 elif isinstance(then_expression, exp.Update): 4795 if isinstance(then_expression.args.get("expressions"), exp.Star): 4796 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4797 else: 4798 expressions_sql = self.expressions(then_expression) 4799 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4800 else: 4801 then = self.sql(then_expression) 4802 4803 if isinstance(then_expression, (exp.Insert, exp.Update)): 4804 where = self.sql(then_expression, "where") 4805 if where and not self.SUPPORTS_MERGE_WHERE: 4806 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4807 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4808 where = "" 4809 then = f"{then}{where}" 4810 return f"WHEN {matched}{source}{condition} THEN {then}"
4815 def merge_sql(self, expression: exp.Merge) -> str: 4816 table = expression.this 4817 table_alias = "" 4818 4819 hints = table.args.get("hints") 4820 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4821 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4822 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4823 4824 this = self.sql(table) 4825 using = f"USING {self.sql(expression, 'using')}" 4826 whens = self.sql(expression, "whens") 4827 4828 on = self.sql(expression, "on") 4829 on = f"ON {on}" if on else "" 4830 4831 if not on: 4832 on = self.expressions(expression, key="using_cond") 4833 on = f"USING ({on})" if on else "" 4834 4835 returning = self.sql(expression, "returning") 4836 if returning: 4837 whens = f"{whens}{returning}" 4838 4839 sep = self.sep() 4840 4841 return self.prepend_ctes( 4842 expression, 4843 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4844 )
@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:
4850 @unsupported_args("default") 4851 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4852 if not self.SUPPORTS_TO_NUMBER: 4853 self.unsupported("Unsupported TO_NUMBER function") 4854 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4855 4856 fmt = expression.args.get("format") 4857 if not fmt: 4858 self.unsupported("Conversion format is required for TO_NUMBER") 4859 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4860 4861 return self.func("TO_NUMBER", expression.this, fmt)
4863 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4864 this = self.sql(expression, "this") 4865 kind = self.sql(expression, "kind") 4866 settings_sql = self.expressions(expression, key="settings", sep=" ") 4867 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4868 return f"{this}({kind}{args})"
def
duplicatekeyproperty_sql( self, expression: sqlglot.expressions.properties.DuplicateKeyProperty) -> str:
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.properties.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
def
distributedbyproperty_sql( self, expression: sqlglot.expressions.properties.DistributedByProperty) -> str:
4889 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4890 expressions = self.expressions(expression, flat=True) 4891 expressions = f" {self.wrap(expressions)}" if expressions else "" 4892 buckets = self.sql(expression, "buckets") 4893 kind = self.sql(expression, "kind") 4894 buckets = f" BUCKETS {buckets}" if buckets else "" 4895 order = self.sql(expression, "order") 4896 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
4901 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4902 expressions = self.expressions(expression, key="expressions", flat=True) 4903 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4904 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4905 buckets = self.sql(expression, "buckets") 4906 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4908 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4909 this = self.sql(expression, "this") 4910 having = self.sql(expression, "having") 4911 4912 if having: 4913 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4914 4915 return self.func("ANY_VALUE", this)
4917 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4918 transform = self.func("TRANSFORM", *expression.expressions) 4919 row_format_before = self.sql(expression, "row_format_before") 4920 row_format_before = f" {row_format_before}" if row_format_before else "" 4921 record_writer = self.sql(expression, "record_writer") 4922 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4923 using = f" USING {self.sql(expression, 'command_script')}" 4924 schema = self.sql(expression, "schema") 4925 schema = f" AS {schema}" if schema else "" 4926 row_format_after = self.sql(expression, "row_format_after") 4927 row_format_after = f" {row_format_after}" if row_format_after else "" 4928 record_reader = self.sql(expression, "record_reader") 4929 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4930 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:
4932 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4933 key_block_size = self.sql(expression, "key_block_size") 4934 if key_block_size: 4935 return f"KEY_BLOCK_SIZE = {key_block_size}" 4936 4937 using = self.sql(expression, "using") 4938 if using: 4939 return f"USING {using}" 4940 4941 parser = self.sql(expression, "parser") 4942 if parser: 4943 return f"WITH PARSER {parser}" 4944 4945 comment = self.sql(expression, "comment") 4946 if comment: 4947 return f"COMMENT {comment}" 4948 4949 visible = expression.args.get("visible") 4950 if visible is not None: 4951 return "VISIBLE" if visible else "INVISIBLE" 4952 4953 engine_attr = self.sql(expression, "engine_attr") 4954 if engine_attr: 4955 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4956 4957 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4958 if secondary_engine_attr: 4959 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4960 4961 self.unsupported("Unsupported index constraint option.") 4962 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
4968 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4969 kind = self.sql(expression, "kind") 4970 kind = f"{kind} INDEX" if kind else "INDEX" 4971 this = self.sql(expression, "this") 4972 this = f" {this}" if this else "" 4973 index_type = self.sql(expression, "index_type") 4974 index_type = f" USING {index_type}" if index_type else "" 4975 expressions = self.expressions(expression, flat=True) 4976 expressions = f" ({expressions})" if expressions else "" 4977 options = self.expressions(expression, key="options", sep=" ") 4978 options = f" {options}" if options else "" 4979 return f"{kind}{this}{index_type}{expressions}{options}"
4981 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4982 if self.NVL2_SUPPORTED: 4983 return self.function_fallback_sql(expression) 4984 4985 case = exp.Case().when( 4986 expression.this.is_(exp.null()).not_(copy=False), 4987 expression.args["true"], 4988 copy=False, 4989 ) 4990 else_cond = expression.args.get("false") 4991 if else_cond: 4992 case.else_(else_cond, copy=False) 4993 4994 return self.sql(case)
4996 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4997 this = self.sql(expression, "this") 4998 expr = self.sql(expression, "expression") 4999 position = self.sql(expression, "position") 5000 position = f", {position}" if position else "" 5001 iterator = self.sql(expression, "iterator") 5002 condition = self.sql(expression, "condition") 5003 condition = f" IF {condition}" if condition else "" 5004 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
5054 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 5055 this_sql = self.sql(expression, "this") 5056 if isinstance(expression.this, exp.Table): 5057 this_sql = f"TABLE {this_sql}" 5058 5059 return self.func( 5060 "FORECAST", 5061 this_sql, 5062 expression.args.get("data_col"), 5063 expression.args.get("timestamp_col"), 5064 expression.args.get("model"), 5065 expression.args.get("id_cols"), 5066 expression.args.get("horizon"), 5067 expression.args.get("forecast_end_timestamp"), 5068 expression.args.get("confidence_level"), 5069 expression.args.get("output_historical_time_series"), 5070 expression.args.get("context_window"), 5071 )
5073 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 5074 this_sql = self.sql(expression, "this") 5075 if isinstance(expression.this, exp.Table): 5076 this_sql = f"TABLE {this_sql}" 5077 5078 return self.func( 5079 "FEATURES_AT_TIME", 5080 this_sql, 5081 expression.args.get("time"), 5082 expression.args.get("num_rows"), 5083 expression.args.get("ignore_feature_nulls"), 5084 )
5086 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5087 this_sql = self.sql(expression, "this") 5088 if isinstance(expression.this, exp.Table): 5089 this_sql = f"TABLE {this_sql}" 5090 5091 query_table = self.sql(expression, "query_table") 5092 if isinstance(expression.args["query_table"], exp.Table): 5093 query_table = f"TABLE {query_table}" 5094 5095 return self.func( 5096 "VECTOR_SEARCH", 5097 this_sql, 5098 expression.args.get("column_to_search"), 5099 query_table, 5100 expression.args.get("query_column_to_search"), 5101 expression.args.get("top_k"), 5102 expression.args.get("distance_type"), 5103 expression.args.get("options"), 5104 )
5116 def toarray_sql(self, expression: exp.ToArray) -> str: 5117 arg = expression.this 5118 if not arg.type: 5119 import sqlglot.optimizer.annotate_types 5120 5121 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5122 5123 if arg.is_type(exp.DType.ARRAY): 5124 return self.sql(arg) 5125 5126 cond_for_null = arg.is_(exp.null()) 5127 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5129 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5130 this = expression.this 5131 time_format = self.format_time(expression) 5132 5133 if time_format: 5134 return self.sql( 5135 exp.cast( 5136 exp.StrToTime(this=this, format=expression.args["format"]), 5137 exp.DType.TIME, 5138 ) 5139 ) 5140 5141 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5142 return self.sql(this) 5143 5144 return self.sql(exp.cast(this, exp.DType.TIME))
5146 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5147 this = expression.this 5148 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5149 return self.sql(this) 5150 5151 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5153 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5154 this = expression.this 5155 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5156 return self.sql(this) 5157 5158 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5160 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5161 this = expression.this 5162 time_format = self.format_time(expression) 5163 safe = expression.args.get("safe") 5164 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5165 return self.sql( 5166 exp.cast( 5167 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5168 exp.DType.DATE, 5169 ) 5170 ) 5171 5172 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5173 return self.sql(this) 5174 5175 if safe: 5176 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5177 5178 return self.sql(exp.cast(this, exp.DType.DATE))
5190 def lastday_sql(self, expression: exp.LastDay) -> str: 5191 if self.LAST_DAY_SUPPORTS_DATE_PART: 5192 return self.function_fallback_sql(expression) 5193 5194 unit = expression.args.get("unit") 5195 if unit and unit.name.upper() != "MONTH": 5196 self.unsupported("Date parts are not supported in LAST_DAY.") 5197 5198 return self.func("LAST_DAY", expression.this)
5210 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5211 if self.CAN_IMPLEMENT_ARRAY_ANY: 5212 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5213 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5214 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5215 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5216 5217 import sqlglot.dialects.dialect 5218 5219 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5220 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5221 self.unsupported("ARRAY_ANY is unsupported") 5222 5223 return self.function_fallback_sql(expression)
5225 def struct_sql(self, expression: exp.Struct) -> str: 5226 expression.set( 5227 "expressions", 5228 [ 5229 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5230 if isinstance(e, exp.PropertyEQ) 5231 else e 5232 for e in expression.expressions 5233 ], 5234 ) 5235 5236 return self.function_fallback_sql(expression)
5244 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5245 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5246 tables = f" {self.expressions(expression)}" 5247 5248 exists = " IF EXISTS" if expression.args.get("exists") else "" 5249 5250 on_cluster = self.sql(expression, "cluster") 5251 on_cluster = f" {on_cluster}" if on_cluster else "" 5252 5253 identity = self.sql(expression, "identity") 5254 identity = f" {identity} IDENTITY" if identity else "" 5255 5256 option = self.sql(expression, "option") 5257 option = f" {option}" if option else "" 5258 5259 partition = self.sql(expression, "partition") 5260 partition = f" {partition}" if partition else "" 5261 5262 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5266 def convert_sql(self, expression: exp.Convert) -> str: 5267 to = expression.this 5268 value = expression.expression 5269 style = expression.args.get("style") 5270 safe = expression.args.get("safe") 5271 strict = expression.args.get("strict") 5272 5273 if not to or not value: 5274 return "" 5275 5276 # Retrieve length of datatype and override to default if not specified 5277 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5278 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5279 5280 transformed: exp.Expr | None = None 5281 cast = exp.Cast if strict else exp.TryCast 5282 5283 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5284 if isinstance(style, exp.Literal) and style.is_int: 5285 import sqlglot.dialects.tsql 5286 5287 style_value = style.name 5288 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5289 if not converted_style: 5290 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5291 5292 fmt = exp.Literal.string(converted_style) 5293 5294 if to.this == exp.DType.DATE: 5295 transformed = exp.StrToDate(this=value, format=fmt) 5296 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5297 transformed = exp.StrToTime(this=value, format=fmt) 5298 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5299 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5300 elif to.this == exp.DType.TEXT: 5301 transformed = exp.TimeToStr(this=value, format=fmt) 5302 5303 if not transformed: 5304 transformed = cast(this=value, to=to, safe=safe) 5305 5306 return self.sql(transformed)
5387 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5388 option = self.sql(expression, "this") 5389 5390 if expression.expressions: 5391 upper = option.upper() 5392 5393 # Snowflake FILE_FORMAT options are separated by whitespace 5394 sep = " " if upper == "FILE_FORMAT" else ", " 5395 5396 # Databricks copy/format options do not set their list of values with EQ 5397 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5398 values = self.expressions(expression, flat=True, sep=sep) 5399 return f"{option}{op}({values})" 5400 5401 value = self.sql(expression, "expression") 5402 5403 if not value: 5404 return option 5405 5406 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5407 5408 return f"{option}{op}{value}"
5410 def credentials_sql(self, expression: exp.Credentials) -> str: 5411 cred_expr = expression.args.get("credentials") 5412 if isinstance(cred_expr, exp.Literal): 5413 # Redshift case: CREDENTIALS <string> 5414 credentials = self.sql(expression, "credentials") 5415 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5416 else: 5417 # Snowflake case: CREDENTIALS = (...) 5418 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5419 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5420 5421 storage = self.sql(expression, "storage") 5422 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5423 5424 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5425 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5426 5427 iam_role = self.sql(expression, "iam_role") 5428 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5429 5430 region = self.sql(expression, "region") 5431 region = f" REGION {region}" if region else "" 5432 5433 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5435 def copy_sql(self, expression: exp.Copy) -> str: 5436 this = self.sql(expression, "this") 5437 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5438 5439 credentials = self.sql(expression, "credentials") 5440 credentials = self.seg(credentials) if credentials else "" 5441 files = self.expressions(expression, key="files", flat=True) 5442 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5443 5444 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5445 params = self.expressions( 5446 expression, 5447 key="params", 5448 sep=sep, 5449 new_line=True, 5450 skip_last=True, 5451 skip_first=True, 5452 indent=self.COPY_PARAMS_ARE_WRAPPED, 5453 ) 5454 5455 if params: 5456 if self.COPY_PARAMS_ARE_WRAPPED: 5457 params = f" WITH ({params})" 5458 elif not self.pretty and (files or credentials): 5459 params = f" {params}" 5460 5461 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5466 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5467 on_sql = "ON" if expression.args.get("on") else "OFF" 5468 filter_col: str | None = self.sql(expression, "filter_column") 5469 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5470 retention_period: str | None = self.sql(expression, "retention_period") 5471 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5472 5473 if filter_col or retention_period: 5474 on_sql = self.func("ON", filter_col, retention_period) 5475 5476 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5478 def maskingpolicycolumnconstraint_sql( 5479 self, expression: exp.MaskingPolicyColumnConstraint 5480 ) -> str: 5481 this = self.sql(expression, "this") 5482 expressions = self.expressions(expression, flat=True) 5483 expressions = f" USING ({expressions})" if expressions else "" 5484 return f"MASKING POLICY {this}{expressions}"
5494 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5495 this = self.sql(expression, "this") 5496 expr = expression.expression 5497 5498 if isinstance(expr, exp.Func): 5499 # T-SQL's CLR functions are case sensitive 5500 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5501 else: 5502 expr = self.sql(expression, "expression") 5503 5504 return self.scope_resolution(expr, this)
5512 def rand_sql(self, expression: exp.Rand) -> str: 5513 lower = self.sql(expression, "lower") 5514 upper = self.sql(expression, "upper") 5515 5516 if lower and upper: 5517 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5518 return self.func("RAND", expression.this)
5520 def changes_sql(self, expression: exp.Changes) -> str: 5521 information = self.sql(expression, "information") 5522 information = f"INFORMATION => {information}" 5523 at_before = self.sql(expression, "at_before") 5524 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5525 end = self.sql(expression, "end") 5526 end = f"{self.seg('')}{end}" if end else "" 5527 5528 return f"CHANGES ({information}){at_before}{end}"
5530 def pad_sql(self, expression: exp.Pad) -> str: 5531 prefix = "L" if expression.args.get("is_left") else "R" 5532 5533 fill_pattern = self.sql(expression, "fill_pattern") or None 5534 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5535 fill_pattern = "' '" 5536 5537 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5543 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5544 generate_series = exp.GenerateSeries(**expression.args) 5545 5546 parent = expression.parent 5547 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5548 parent = parent.parent 5549 5550 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5551 return self.sql(exp.Unnest(expressions=[generate_series])) 5552 5553 if isinstance(parent, exp.Select): 5554 self.unsupported("GenerateSeries projection unnesting is not supported.") 5555 5556 return self.sql(generate_series)
5558 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5559 if self.SUPPORTS_CONVERT_TIMEZONE: 5560 return self.function_fallback_sql(expression) 5561 5562 source_tz = expression.args.get("source_tz") 5563 target_tz = expression.args.get("target_tz") 5564 timestamp = expression.args.get("timestamp") 5565 5566 if source_tz and timestamp: 5567 timestamp = exp.AtTimeZone( 5568 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5569 ) 5570 5571 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5572 5573 return self.sql(expr)
5575 def json_sql(self, expression: exp.JSON) -> str: 5576 this = self.sql(expression, "this") 5577 this = f" {this}" if this else "" 5578 5579 _with = expression.args.get("with_") 5580 5581 if _with is None: 5582 with_sql = "" 5583 elif not _with: 5584 with_sql = " WITHOUT" 5585 else: 5586 with_sql = " WITH" 5587 5588 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5589 5590 return f"JSON{this}{with_sql}{unique_sql}"
5592 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5593 path = self.sql(expression, "path") 5594 returning = self.sql(expression, "returning") 5595 returning = f" RETURNING {returning}" if returning else "" 5596 5597 on_condition = self.sql(expression, "on_condition") 5598 on_condition = f" {on_condition}" if on_condition else "" 5599 5600 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5606 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5607 else_ = "ELSE " if expression.args.get("else_") else "" 5608 condition = self.sql(expression, "expression") 5609 condition = f"WHEN {condition} THEN " if condition else else_ 5610 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5611 return f"{condition}{insert}"
5619 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5620 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5621 empty = expression.args.get("empty") 5622 empty = ( 5623 f"DEFAULT {empty} ON EMPTY" 5624 if isinstance(empty, exp.Expr) 5625 else self.sql(expression, "empty") 5626 ) 5627 5628 error = expression.args.get("error") 5629 error = ( 5630 f"DEFAULT {error} ON ERROR" 5631 if isinstance(error, exp.Expr) 5632 else self.sql(expression, "error") 5633 ) 5634 5635 if error and empty: 5636 error = ( 5637 f"{empty} {error}" 5638 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5639 else f"{error} {empty}" 5640 ) 5641 empty = "" 5642 5643 null = self.sql(expression, "null") 5644 5645 return f"{empty}{error}{null}"
5651 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5652 this = self.sql(expression, "this") 5653 path = self.sql(expression, "path") 5654 5655 passing = self.expressions(expression, "passing") 5656 passing = f" PASSING {passing}" if passing else "" 5657 5658 on_condition = self.sql(expression, "on_condition") 5659 on_condition = f" {on_condition}" if on_condition else "" 5660 5661 path = f"{path}{passing}{on_condition}" 5662 5663 return self.func("JSON_EXISTS", this, path)
5705 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5706 array_agg = self.function_fallback_sql(expression) 5707 column_expr = expression.this 5708 if isinstance(column_expr, exp.Order): 5709 column_expr = column_expr.this 5710 5711 return self._add_arrayagg_null_filter(array_agg, expression, column_expr)
5792 def overlay_sql(self, expression: exp.Overlay) -> str: 5793 this = self.sql(expression, "this") 5794 expr = self.sql(expression, "expression") 5795 from_sql = self.sql(expression, "from_") 5796 for_sql = self.sql(expression, "for_") 5797 for_sql = f" FOR {for_sql}" if for_sql else "" 5798 5799 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
5806 def string_sql(self, expression: exp.String) -> str: 5807 this = expression.this 5808 zone = expression.args.get("zone") 5809 5810 if zone: 5811 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5812 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5813 # set for source_tz to transpile the time conversion before the STRING cast 5814 this = exp.ConvertTimezone( 5815 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5816 ) 5817 5818 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
5828 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5829 filler = self.sql(expression, "this") 5830 filler = f" {filler}" if filler else "" 5831 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5832 return f"TRUNCATE{filler} {with_count}"
5834 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5835 if self.SUPPORTS_UNIX_SECONDS: 5836 return self.function_fallback_sql(expression) 5837 5838 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5839 5840 return self.sql( 5841 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5842 )
5844 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5845 dim = expression.expression 5846 5847 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5848 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5849 if not (dim.is_int and dim.name == "1"): 5850 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5851 dim = None 5852 5853 # If dimension is required but not specified, default initialize it 5854 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5855 dim = exp.Literal.number(1) 5856 5857 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5859 def attach_sql(self, expression: exp.Attach) -> str: 5860 this = self.sql(expression, "this") 5861 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5862 expressions = self.expressions(expression) 5863 expressions = f" ({expressions})" if expressions else "" 5864 5865 return f"ATTACH{exists_sql} {this}{expressions}"
5867 def detach_sql(self, expression: exp.Detach) -> str: 5868 kind = self.sql(expression, "kind") 5869 kind = f" {kind}" if kind else "" 5870 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5871 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5872 exists = " IF EXISTS" if expression.args.get("exists") else "" 5873 if exists: 5874 kind = kind or " DATABASE" 5875 5876 this = self.sql(expression, "this") 5877 this = f" {this}" if this else "" 5878 cluster = self.sql(expression, "cluster") 5879 cluster = f" {cluster}" if cluster else "" 5880 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5881 sync = " SYNC" if expression.args.get("sync") else "" 5882 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
5895 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5896 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5897 encode = f"{encode} {self.sql(expression, 'this')}" 5898 5899 properties = expression.args.get("properties") 5900 if properties: 5901 encode = f"{encode} {self.properties(properties)}" 5902 5903 return encode
5905 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5906 this = self.sql(expression, "this") 5907 include = f"INCLUDE {this}" 5908 5909 column_def = self.sql(expression, "column_def") 5910 if column_def: 5911 include = f"{include} {column_def}" 5912 5913 alias = self.sql(expression, "alias") 5914 if alias: 5915 include = f"{include} AS {alias}" 5916 5917 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
5930 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5931 partitions = self.expressions(expression, "partition_expressions") 5932 create = self.expressions(expression, "create_expressions") 5933 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
5935 def partitionbyrangepropertydynamic_sql( 5936 self, expression: exp.PartitionByRangePropertyDynamic 5937 ) -> str: 5938 start = self.sql(expression, "start") 5939 end = self.sql(expression, "end") 5940 5941 every = expression.args["every"] 5942 if isinstance(every, exp.Interval) and every.this.is_string: 5943 every.this.replace(exp.Literal.number(every.name)) 5944 5945 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5958 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5959 kind = self.sql(expression, "kind") 5960 option = self.sql(expression, "option") 5961 option = f" {option}" if option else "" 5962 this = self.sql(expression, "this") 5963 this = f" {this}" if this else "" 5964 columns = self.expressions(expression) 5965 columns = f" {columns}" if columns else "" 5966 return f"{kind}{option} STATISTICS{this}{columns}"
5968 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5969 this = self.sql(expression, "this") 5970 columns = self.expressions(expression) 5971 inner_expression = self.sql(expression, "expression") 5972 inner_expression = f" {inner_expression}" if inner_expression else "" 5973 update_options = self.sql(expression, "update_options") 5974 update_options = f" {update_options} UPDATE" if update_options else "" 5975 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
5986 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5987 kind = self.sql(expression, "kind") 5988 this = self.sql(expression, "this") 5989 this = f" {this}" if this else "" 5990 inner_expression = self.sql(expression, "expression") 5991 return f"VALIDATE {kind}{this}{inner_expression}"
5993 def analyze_sql(self, expression: exp.Analyze) -> str: 5994 options = self.expressions(expression, key="options", sep=" ") 5995 options = f" {options}" if options else "" 5996 kind = self.sql(expression, "kind") 5997 kind = f" {kind}" if kind else "" 5998 this = self.sql(expression, "this") 5999 this = f" {this}" if this else "" 6000 mode = self.sql(expression, "mode") 6001 mode = f" {mode}" if mode else "" 6002 properties = self.sql(expression, "properties") 6003 properties = f" {properties}" if properties else "" 6004 partition = self.sql(expression, "partition") 6005 partition = f" {partition}" if partition else "" 6006 inner_expression = self.sql(expression, "expression") 6007 inner_expression = f" {inner_expression}" if inner_expression else "" 6008 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
6010 def xmltable_sql(self, expression: exp.XMLTable) -> str: 6011 this = self.sql(expression, "this") 6012 namespaces = self.expressions(expression, key="namespaces") 6013 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 6014 passing = self.expressions(expression, key="passing") 6015 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 6016 columns = self.expressions(expression, key="columns") 6017 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 6018 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 6019 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
6025 def export_sql(self, expression: exp.Export) -> str: 6026 this = self.sql(expression, "this") 6027 connection = self.sql(expression, "connection") 6028 connection = f"WITH CONNECTION {connection} " if connection else "" 6029 options = self.sql(expression, "options") 6030 return f"EXPORT DATA {connection}{options} AS {this}"
6036 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 6037 variables = self.expressions(expression, "this") 6038 default = self.sql(expression, "default") 6039 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 6040 6041 kind = self.sql(expression, "kind") 6042 if isinstance(expression.args.get("kind"), exp.Schema): 6043 kind = f"TABLE {kind}" 6044 6045 kind = f" {kind}" if kind else "" 6046 6047 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
6049 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 6050 kind = self.sql(expression, "kind") 6051 this = self.sql(expression, "this") 6052 set = self.sql(expression, "expression") 6053 using = self.sql(expression, "using") 6054 using = f" USING {using}" if using else "" 6055 6056 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 6057 6058 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql( self, expression: sqlglot.expressions.core.CombinedParameterizedAgg) -> str:
def
get_put_sql( self, expression: sqlglot.expressions.query.Put | sqlglot.expressions.query.Get) -> str:
6081 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 6082 # Snowflake GET/PUT statements: 6083 # PUT <file> <internalStage> <properties> 6084 # GET <internalStage> <file> <properties> 6085 props = expression.args.get("properties") 6086 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 6087 this = self.sql(expression, "this") 6088 target = self.sql(expression, "target") 6089 6090 if isinstance(expression, exp.Put): 6091 return f"PUT {this} {target}{props_sql}" 6092 else: 6093 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
6095 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6096 this = self.sql(expression, "this") 6097 expr = self.sql(expression, "expression") 6098 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6099 return f"TRANSLATE({this} USING {expr}{with_error})"
6101 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6102 if self.SUPPORTS_DECODE_CASE: 6103 return self.func("DECODE", *expression.expressions) 6104 6105 decode_expr, *expressions = expression.expressions 6106 6107 ifs = [] 6108 for search, result in zip(expressions[::2], expressions[1::2]): 6109 if isinstance(search, exp.Literal): 6110 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6111 elif isinstance(search, exp.Null): 6112 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6113 else: 6114 if isinstance(search, exp.Binary): 6115 search = exp.paren(search) 6116 6117 cond = exp.or_( 6118 decode_expr.eq(search), 6119 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6120 copy=False, 6121 ) 6122 ifs.append(exp.If(this=cond, true=result)) 6123 6124 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6125 return self.sql(case)
6127 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6128 this = self.sql(expression, "this") 6129 this = self.seg(this, sep="") 6130 dimensions = self.expressions( 6131 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6132 ) 6133 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6134 metrics = self.expressions( 6135 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6136 ) 6137 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6138 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6139 facts = self.seg(f"FACTS {facts}") if facts else "" 6140 where = self.sql(expression, "where") 6141 where = self.seg(f"WHERE {where}") if where else "" 6142 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6143 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6145 def getextract_sql(self, expression: exp.GetExtract) -> str: 6146 this = expression.this 6147 expr = expression.expression 6148 6149 if not this.type or not expression.type: 6150 import sqlglot.optimizer.annotate_types 6151 6152 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6153 6154 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6155 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6156 6157 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6174 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6175 method = self.sql(expression, "method") 6176 kind = expression.args.get("kind") 6177 if not kind: 6178 return f"REFRESH {method}" 6179 6180 every = self.sql(expression, "every") 6181 unit = self.sql(expression, "unit") 6182 every = f" EVERY {every} {unit}" if every else "" 6183 starts = self.sql(expression, "starts") 6184 starts = f" STARTS {starts}" if starts else "" 6185 6186 return f"REFRESH {method} ON {kind}{every}{starts}"
6195 def uuid_sql(self, expression: exp.Uuid) -> str: 6196 is_string = expression.args.get("is_string", False) 6197 uuid_func_sql = self.func("UUID") 6198 6199 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6200 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6201 6202 return uuid_func_sql
6204 def initcap_sql(self, expression: exp.Initcap) -> str: 6205 delimiters = expression.expression 6206 6207 if delimiters: 6208 # do not generate delimiters arg if we are round-tripping from default delimiters 6209 if ( 6210 delimiters.is_string 6211 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6212 ): 6213 delimiters = None 6214 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6215 self.unsupported("INITCAP does not support custom delimiters") 6216 delimiters = None 6217 6218 return self.func("INITCAP", expression.this, delimiters)
6228 def weekstart_name(self, expression: exp.WeekStart) -> str: 6229 import sqlglot.dialects.dialect 6230 6231 # WEEK(<day>) is BigQuery-only syntax, so it degrades to the plain WEEK unit 6232 this = expression.this.name.upper() 6233 6234 dow_from_week_start_day = sqlglot.dialects.dialect.WEEK_START_DAY_TO_DOW.get(this) 6235 dow_from_week_offset = sqlglot.dialects.dialect.week_offset_to_dow(self.dialect.WEEK_OFFSET) 6236 6237 if dow_from_week_start_day != dow_from_week_offset: 6238 self.unsupported( 6239 f"WEEK({this}) is not supported; falling back to the default week start day" 6240 ) 6241 6242 return "WEEK"
6244 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6245 name = self.weekstart_name(expression) 6246 6247 # DateTrunc stores string literal units, whereas TimeUnit expressions store keywords 6248 if isinstance(expression.parent, exp.DateTrunc): 6249 return self.sql(exp.Literal.string(name)) 6250 6251 return name
def
functionspecification_sql(self, expression: sqlglot.expressions.query.FunctionSpecification) -> str:
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: