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.helper import csv, name_sequence, seq_get 13from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 14from sqlglot.time import format_time 15from sqlglot.tokens import TokenType 16 17if t.TYPE_CHECKING: 18 from sqlglot._typing import E 19 from sqlglot.dialects.dialect import DialectType 20 21 G = t.TypeVar("G", bound="Generator") 22 GeneratorMethod = t.Callable[[G, E], str] 23 24logger = logging.getLogger("sqlglot") 25 26ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 27UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 28 29 30def unsupported_args( 31 *args: str | tuple[str, str], 32) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 33 """ 34 Decorator that can be used to mark certain args of an `Expr` subclass as unsupported. 35 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 36 """ 37 diagnostic_by_arg: dict[str, str | None] = {} 38 for arg in args: 39 if isinstance(arg, str): 40 diagnostic_by_arg[arg] = None 41 else: 42 diagnostic_by_arg[arg[0]] = arg[1] 43 44 def decorator(func: GeneratorMethod) -> GeneratorMethod: 45 @wraps(func) 46 def _func(generator: G, expression: E) -> str: 47 expression_name = expression.__class__.__name__ 48 dialect_name = generator.dialect.__class__.__name__ 49 50 for arg_name, diagnostic in diagnostic_by_arg.items(): 51 if expression.args.get(arg_name): 52 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 53 arg_name, expression_name, dialect_name 54 ) 55 generator.unsupported(diagnostic) 56 57 return func(generator, expression) 58 59 return _func 60 61 return decorator 62 63 64AFTER_HAVING_MODIFIER_TRANSFORMS: dict[str, t.Any] = { 65 "windows": lambda self, e: ( 66 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 67 if e.args.get("windows") 68 else "" 69 ), 70 "qualify": lambda self, e: self.sql(e, "qualify"), 71} 72 73 74_DISPATCH_CACHE: dict[type[Generator], dict[type[exp.Expr], t.Callable[..., str]]] = {} 75 76 77def _build_dispatch( 78 cls: type[Generator], 79) -> dict[type[exp.Expr], t.Callable[..., str]]: 80 dispatch: dict[type[exp.Expr], t.Callable[..., str]] = dict(cls.TRANSFORMS) 81 82 for attr_name in dir(cls): 83 if not attr_name.endswith("_sql") or attr_name.startswith("_"): 84 continue 85 86 expr_key = attr_name[:-4] 87 expr_cls = exp.EXPR_CLASSES.get(expr_key) 88 89 if expr_cls and expr_cls not in dispatch: 90 dispatch[expr_cls] = getattr(cls, attr_name) 91 92 return dispatch 93 94 95class Generator: 96 """ 97 Generator converts a given syntax tree to the corresponding SQL string. 98 99 Args: 100 pretty: Whether to format the produced SQL string. 101 Default: False. 102 identify: Determines when an identifier should be quoted. Possible values are: 103 False (default): Never quote, except in cases where it's mandatory by the dialect. 104 True: Always quote except for specials cases. 105 'safe': Only quote identifiers that are case insensitive. 106 normalize: Whether to normalize identifiers to lowercase. 107 Default: False. 108 pad: The pad size in a formatted string. For example, this affects the indentation of 109 a projection in a query, relative to its nesting level. 110 Default: 2. 111 indent: The indentation size in a formatted string. For example, this affects the 112 indentation of subqueries and filters under a `WHERE` clause. 113 Default: 2. 114 normalize_functions: How to normalize function names. Possible values are: 115 "upper" or True (default): Convert names to uppercase. 116 "lower": Convert names to lowercase. 117 False: Disables function name normalization. 118 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 119 Default ErrorLevel.WARN. 120 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 121 This is only relevant if unsupported_level is ErrorLevel.RAISE. 122 Default: 3 123 leading_comma: Whether the comma is leading or trailing in select expressions. 124 This is only relevant when generating in pretty mode. 125 Default: False 126 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 127 The default is on the smaller end because the length only represents a segment and not the true 128 line length. 129 Default: 80 130 comments: Whether to preserve comments in the output SQL code. 131 Default: True 132 """ 133 134 TRANSFORMS: t.ClassVar[dict[type[exp.Expr], t.Callable[..., str]]] = { 135 **JSON_PATH_PART_TRANSFORMS, 136 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 137 exp.AllowedValuesProperty: lambda self, e: ( 138 f"ALLOWED_VALUES {self.expressions(e, flat=True)}" 139 ), 140 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 141 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 142 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 143 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 144 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 145 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 146 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 147 exp.CaseSpecificColumnConstraint: lambda _, e: ( 148 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 149 ), 150 exp.Ceil: lambda self, e: self.ceil_floor(e), 151 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 152 exp.CharacterSetProperty: lambda self, e: ( 153 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 154 ), 155 exp.ClusteredColumnConstraint: lambda self, e: ( 156 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 157 ), 158 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 159 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 160 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 161 exp.ConvertToCharset: lambda self, e: self.func( 162 "CONVERT", e.this, e.args["dest"], e.args.get("source") 163 ), 164 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 165 exp.CredentialsProperty: lambda self, e: ( 166 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 167 ), 168 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 169 exp.SessionUser: lambda *_: "SESSION_USER", 170 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 171 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 172 exp.ApiProperty: lambda *_: "API", 173 exp.ApplicationProperty: lambda *_: "APPLICATION", 174 exp.CatalogProperty: lambda *_: "CATALOG", 175 exp.ComputeProperty: lambda *_: "COMPUTE", 176 exp.DatabaseProperty: lambda *_: "DATABASE", 177 exp.DynamicProperty: lambda *_: "DYNAMIC", 178 exp.EmptyProperty: lambda *_: "EMPTY", 179 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 180 exp.EndStatement: lambda *_: "END", 181 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 182 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 183 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 184 exp.EphemeralColumnConstraint: lambda self, e: ( 185 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 186 ), 187 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 188 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 189 exp.Except: lambda self, e: self.set_operations(e), 190 exp.ExternalProperty: lambda *_: "EXTERNAL", 191 exp.Floor: lambda self, e: self.ceil_floor(e), 192 exp.Get: lambda self, e: self.get_put_sql(e), 193 exp.GlobalProperty: lambda *_: "GLOBAL", 194 exp.HeapProperty: lambda *_: "HEAP", 195 exp.HybridProperty: lambda *_: "HYBRID", 196 exp.IcebergProperty: lambda *_: "ICEBERG", 197 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 198 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 199 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 200 exp.Intersect: lambda self, e: self.set_operations(e), 201 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 202 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 203 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 204 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 205 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 206 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 207 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 208 exp.LanguageProperty: lambda self, e: self.naked_property(e), 209 exp.LocationProperty: lambda self, e: self.naked_property(e), 210 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 211 exp.MaskingProperty: lambda *_: "MASKING", 212 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 213 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 214 exp.NetworkProperty: lambda *_: "NETWORK", 215 exp.NonClusteredColumnConstraint: lambda self, e: ( 216 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 217 ), 218 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 219 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 220 exp.OnCommitProperty: lambda _, e: ( 221 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 222 ), 223 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 224 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 225 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 226 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 227 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 228 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 229 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 230 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 231 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 232 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 233 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 234 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 235 f"PROJECTION POLICY {self.sql(e, 'this')}" 236 ), 237 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 238 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 239 exp.Put: lambda self, e: self.get_put_sql(e), 240 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 241 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 242 ), 243 exp.ReturnsProperty: lambda self, e: ( 244 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 245 ), 246 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 247 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 248 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 249 exp.SecureProperty: lambda *_: "SECURE", 250 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 251 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 252 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 253 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 254 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 255 exp.SqlReadWriteProperty: lambda _, e: e.name, 256 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 257 exp.StabilityProperty: lambda _, e: e.name, 258 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 259 exp.StreamingTableProperty: lambda *_: "STREAMING", 260 exp.StrictProperty: lambda *_: "STRICT", 261 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 262 exp.TableColumn: lambda self, e: self.sql(e.this), 263 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 264 exp.TemporaryProperty: lambda *_: "TEMPORARY", 265 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 266 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 267 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 268 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 269 exp.TransientProperty: lambda *_: "TRANSIENT", 270 exp.VirtualProperty: lambda *_: "VIRTUAL", 271 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 272 exp.Union: lambda self, e: self.set_operations(e), 273 exp.UnloggedProperty: lambda *_: "UNLOGGED", 274 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 275 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 276 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 277 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 278 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 279 exp.UtcTimestamp: lambda self, e: self.sql( 280 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 281 ), 282 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 283 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 284 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 285 exp.VolatileProperty: lambda *_: "VOLATILE", 286 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 287 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 288 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 289 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 290 exp.ForceProperty: lambda *_: "FORCE", 291 } 292 293 # Whether null ordering is supported in order by 294 # True: Full Support, None: No support, False: No support for certain cases 295 # such as window specifications, aggregate functions etc 296 NULL_ORDERING_SUPPORTED: bool | None = True 297 298 # Window functions that support NULLS FIRST/LAST 299 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 300 301 # Whether ignore nulls is inside the agg or outside. 302 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 303 IGNORE_NULLS_IN_FUNC = False 304 305 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 306 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 307 IGNORE_NULLS_BEFORE_ORDER = True 308 309 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 310 LOCKING_READS_SUPPORTED = False 311 312 # Whether the EXCEPT and INTERSECT operations can return duplicates 313 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 314 315 # Wrap derived values in parens, usually standard but spark doesn't support it 316 WRAP_DERIVED_VALUES = True 317 318 # Whether create function uses an AS before the RETURN 319 CREATE_FUNCTION_RETURN_AS = True 320 321 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 322 MATCHED_BY_SOURCE = True 323 324 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 325 SUPPORTS_MERGE_WHERE = False 326 327 # Whether the INTERVAL expression works only with values like '1 day' 328 SINGLE_STRING_INTERVAL = False 329 330 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 331 INTERVAL_ALLOWS_PLURAL_FORM = True 332 333 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 334 LIMIT_FETCH = "ALL" 335 336 # Whether limit and fetch allows expresions or just limits 337 LIMIT_ONLY_LITERALS = False 338 339 # Whether a table is allowed to be renamed with a db 340 RENAME_TABLE_WITH_DB = True 341 342 # The separator for grouping sets and rollups 343 GROUPINGS_SEP = "," 344 345 # The string used for creating an index on a table 346 INDEX_ON = "ON" 347 348 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 349 INOUT_SEPARATOR = " " 350 351 # Whether join hints should be generated 352 JOIN_HINTS = True 353 354 # Whether directed joins are supported 355 DIRECTED_JOINS = False 356 357 # Whether table hints should be generated 358 TABLE_HINTS = True 359 360 # Whether query hints should be generated 361 QUERY_HINTS = True 362 363 # What kind of separator to use for query hints 364 QUERY_HINT_SEP = ", " 365 366 # Whether comparing against booleans (e.g. x IS TRUE) is supported 367 IS_BOOL_ALLOWED = True 368 369 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 370 DUPLICATE_KEY_UPDATE_WITH_SET = True 371 372 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 373 LIMIT_IS_TOP = False 374 375 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 376 RETURNING_END = True 377 378 # Whether to generate an unquoted value for EXTRACT's date part argument 379 EXTRACT_ALLOWS_QUOTES = True 380 381 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 382 TZ_TO_WITH_TIME_ZONE = False 383 384 # Whether the NVL2 function is supported 385 NVL2_SUPPORTED = True 386 387 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 388 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 389 390 # Whether VALUES statements can be used as derived tables. 391 # MySQL 5 and Redshift do not allow this, so when False, it will convert 392 # SELECT * VALUES into SELECT UNION 393 VALUES_AS_TABLE = True 394 395 # Whether the word COLUMN is included when adding a column with ALTER TABLE 396 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 397 398 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 399 UNNEST_WITH_ORDINALITY = True 400 401 # Whether FILTER (WHERE cond) can be used for conditional aggregation 402 AGGREGATE_FILTER_SUPPORTED = True 403 404 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 405 SEMI_ANTI_JOIN_WITH_SIDE = True 406 407 # Whether to include the type of a computed column in the CREATE DDL 408 COMPUTED_COLUMN_WITH_TYPE = True 409 410 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 411 SUPPORTS_TABLE_COPY = True 412 413 # Whether parentheses are required around the table sample's expression 414 TABLESAMPLE_REQUIRES_PARENS = True 415 416 # Whether a table sample clause's size needs to be followed by the ROWS keyword 417 TABLESAMPLE_SIZE_IS_ROWS = True 418 419 # The keyword(s) to use when generating a sample clause 420 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 421 422 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 423 TABLESAMPLE_WITH_METHOD = True 424 425 # The keyword to use when specifying the seed of a sample clause 426 TABLESAMPLE_SEED_KEYWORD = "SEED" 427 428 # Whether COLLATE is a function instead of a binary operator 429 COLLATE_IS_FUNC = False 430 431 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 432 DATA_TYPE_SPECIFIERS_ALLOWED = False 433 434 # Whether conditions require booleans WHERE x = 0 vs WHERE x 435 ENSURE_BOOLS = False 436 437 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 438 CTE_RECURSIVE_KEYWORD_REQUIRED = True 439 440 # Whether CONCAT requires >1 arguments 441 SUPPORTS_SINGLE_ARG_CONCAT = True 442 443 # Whether LAST_DAY function supports a date part argument 444 LAST_DAY_SUPPORTS_DATE_PART = True 445 446 # Whether named columns are allowed in table aliases 447 SUPPORTS_TABLE_ALIAS_COLUMNS = True 448 449 # Whether named columns are allowed in CTE definitions 450 SUPPORTS_NAMED_CTE_COLUMNS = True 451 452 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 453 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 454 455 # What delimiter to use for separating JSON key/value pairs 456 JSON_KEY_VALUE_PAIR_SEP = ":" 457 458 # INSERT OVERWRITE TABLE x override 459 INSERT_OVERWRITE = " OVERWRITE TABLE" 460 461 # Whether the SELECT .. INTO syntax is used instead of CTAS 462 SUPPORTS_SELECT_INTO = False 463 464 # Whether UNLOGGED tables can be created 465 SUPPORTS_UNLOGGED_TABLES = False 466 467 # Whether the CREATE TABLE LIKE statement is supported 468 SUPPORTS_CREATE_TABLE_LIKE = True 469 470 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 471 SUPPORTS_MODIFY_COLUMN = False 472 473 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 474 SUPPORTS_CHANGE_COLUMN = False 475 476 # Whether the LikeProperty needs to be specified inside of the schema clause 477 LIKE_PROPERTY_INSIDE_SCHEMA = False 478 479 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 480 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 481 MULTI_ARG_DISTINCT = True 482 483 # Whether the JSON extraction operators expect a value of type JSON 484 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 485 486 # Whether bracketed keys like ["foo"] are supported in JSON paths 487 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 488 489 # Whether to escape keys using single quotes in JSON paths 490 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 491 492 # The JSONPathPart expressions supported by this dialect 493 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 494 495 # Whether any(f(x) for x in array) can be implemented by this dialect 496 CAN_IMPLEMENT_ARRAY_ANY = False 497 498 # Whether the function TO_NUMBER is supported 499 SUPPORTS_TO_NUMBER = True 500 501 # Whether EXCLUDE in window specification is supported 502 SUPPORTS_WINDOW_EXCLUDE = False 503 504 # Whether or not set op modifiers apply to the outer set op or select. 505 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 506 # True means limit 1 happens after the set op, False means it it happens on y. 507 SET_OP_MODIFIERS = True 508 509 # Whether parameters from COPY statement are wrapped in parentheses 510 COPY_PARAMS_ARE_WRAPPED = True 511 512 # Whether values of params are set with "=" token or empty space 513 COPY_PARAMS_EQ_REQUIRED = False 514 515 # Whether COPY statement has INTO keyword 516 COPY_HAS_INTO_KEYWORD = True 517 518 # Whether the conditional TRY(expression) function is supported 519 TRY_SUPPORTED = True 520 521 # Whether the UESCAPE syntax in unicode strings is supported 522 SUPPORTS_UESCAPE = True 523 524 # Function used to replace escaped unicode codes in unicode strings 525 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 526 527 # The keyword to use when generating a star projection with excluded columns 528 STAR_EXCEPT = "EXCEPT" 529 530 # The HEX function name 531 HEX_FUNC = "HEX" 532 533 # The keywords to use when prefixing & separating WITH based properties 534 WITH_PROPERTIES_PREFIX = "WITH" 535 536 # Whether to quote the generated expression of exp.JsonPath 537 QUOTE_JSON_PATH = True 538 539 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 540 PAD_FILL_PATTERN_IS_REQUIRED = False 541 542 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 543 SUPPORTS_EXPLODING_PROJECTIONS = True 544 545 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 546 ARRAY_CONCAT_IS_VAR_LEN = True 547 548 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 549 SUPPORTS_CONVERT_TIMEZONE = False 550 551 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 552 SUPPORTS_MEDIAN = True 553 554 # Whether UNIX_SECONDS(timestamp) is supported 555 SUPPORTS_UNIX_SECONDS = False 556 557 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 558 ALTER_SET_WRAPPED = False 559 560 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 561 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 562 # TODO: The normalization should be done by default once we've tested it across all dialects. 563 NORMALIZE_EXTRACT_DATE_PARTS = False 564 565 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 566 PARSE_JSON_NAME: str | None = "PARSE_JSON" 567 568 # The function name of the exp.ArraySize expression 569 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 570 571 # The syntax to use when altering the type of a column 572 ALTER_SET_TYPE = "SET DATA TYPE" 573 574 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 575 # None -> Doesn't support it at all 576 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 577 # True (Postgres) -> Explicitly requires it 578 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 579 580 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 581 SUPPORTS_DECODE_CASE = True 582 583 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 584 SUPPORTS_BETWEEN_FLAGS = False 585 586 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 587 SUPPORTS_LIKE_QUANTIFIERS = True 588 589 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 590 MATCH_AGAINST_TABLE_PREFIX: str | None = None 591 592 # Whether to include the VARIABLE keyword for SET assignments 593 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 594 595 # The keyword to use for default value assignment in DECLARE statements 596 DECLARE_DEFAULT_ASSIGNMENT = "=" 597 598 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 599 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 600 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 601 UPDATE_STATEMENT_SUPPORTS_FROM = True 602 603 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 604 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 605 606 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 607 # - Snowflake: DROP ICEBERG TABLE a.b; 608 # - DuckDB: DROP TABLE a.b; 609 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 610 611 TYPE_MAPPING: t.ClassVar = { 612 exp.DType.DATETIME2: "TIMESTAMP", 613 exp.DType.NCHAR: "CHAR", 614 exp.DType.NVARCHAR: "VARCHAR", 615 exp.DType.MEDIUMTEXT: "TEXT", 616 exp.DType.LONGTEXT: "TEXT", 617 exp.DType.TINYTEXT: "TEXT", 618 exp.DType.BLOB: "VARBINARY", 619 exp.DType.MEDIUMBLOB: "BLOB", 620 exp.DType.LONGBLOB: "BLOB", 621 exp.DType.TINYBLOB: "BLOB", 622 exp.DType.INET: "INET", 623 exp.DType.ROWVERSION: "VARBINARY", 624 exp.DType.SMALLDATETIME: "TIMESTAMP", 625 } 626 627 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 628 629 # mapping of DType to its default parameters, bounds 630 TYPE_PARAM_SETTINGS: t.ClassVar[ 631 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 632 ] = {} 633 634 TIME_PART_SINGULARS: t.ClassVar = { 635 "MICROSECONDS": "MICROSECOND", 636 "SECONDS": "SECOND", 637 "MINUTES": "MINUTE", 638 "HOURS": "HOUR", 639 "DAYS": "DAY", 640 "WEEKS": "WEEK", 641 "MONTHS": "MONTH", 642 "QUARTERS": "QUARTER", 643 "YEARS": "YEAR", 644 } 645 646 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 647 "cluster": lambda self, e: self.sql(e, "cluster"), 648 "distribute": lambda self, e: self.sql(e, "distribute"), 649 "sort": lambda self, e: self.sql(e, "sort"), 650 **AFTER_HAVING_MODIFIER_TRANSFORMS, 651 } 652 653 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 654 655 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 656 657 PARAMETER_TOKEN = "@" 658 NAMED_PLACEHOLDER_TOKEN = ":" 659 660 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 661 662 PROPERTIES_LOCATION: t.ClassVar = { 663 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 664 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 665 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 666 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 667 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 668 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 669 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 671 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 672 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 673 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 674 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 675 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 676 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 677 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 678 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 679 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 681 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 682 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 683 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 684 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 685 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 686 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 687 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 688 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 689 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 690 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 691 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 692 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 693 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 694 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 695 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 696 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 697 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 698 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 699 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 700 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 701 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 702 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 703 exp.HeapProperty: exp.Properties.Location.POST_WITH, 704 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 705 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 706 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 707 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 708 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 709 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 710 exp.JournalProperty: exp.Properties.Location.POST_NAME, 711 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 712 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 713 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 714 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 715 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 716 exp.LogProperty: exp.Properties.Location.POST_NAME, 717 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 718 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 719 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 720 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 721 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 722 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 723 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 725 exp.Order: exp.Properties.Location.POST_SCHEMA, 726 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 728 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 729 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 730 exp.Property: exp.Properties.Location.POST_WITH, 731 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 735 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 736 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 737 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 742 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 743 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 744 exp.Set: exp.Properties.Location.POST_SCHEMA, 745 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 746 exp.SetProperty: exp.Properties.Location.POST_CREATE, 747 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 748 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 749 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 750 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 751 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 754 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 757 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 758 exp.Tags: exp.Properties.Location.POST_WITH, 759 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 760 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 761 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 762 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 763 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 764 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 765 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 766 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 767 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 768 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 769 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 770 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 771 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 772 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 773 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 775 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 776 } 777 778 # Keywords that can't be used as unquoted identifier names 779 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 780 781 # Exprs whose comments are separated from them for better formatting 782 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 783 exp.Command, 784 exp.Create, 785 exp.Describe, 786 exp.Delete, 787 exp.Drop, 788 exp.From, 789 exp.Insert, 790 exp.Join, 791 exp.MultitableInserts, 792 exp.Order, 793 exp.Group, 794 exp.Having, 795 exp.Select, 796 exp.SetOperation, 797 exp.Update, 798 exp.Where, 799 exp.With, 800 ) 801 802 # Exprs that should not have their comments generated in maybe_comment 803 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 804 exp.Binary, 805 exp.SetOperation, 806 ) 807 808 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 809 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 810 exp.Column, 811 exp.Literal, 812 exp.Neg, 813 exp.Paren, 814 ) 815 816 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 817 exp.DType.NVARCHAR, 818 exp.DType.VARCHAR, 819 exp.DType.CHAR, 820 exp.DType.NCHAR, 821 } 822 823 # Exprs that need to have all CTEs under them bubbled up to them 824 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 825 826 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 827 828 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 829 830 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 831 832 __slots__ = ( 833 "pretty", 834 "identify", 835 "normalize", 836 "pad", 837 "_indent", 838 "normalize_functions", 839 "unsupported_level", 840 "max_unsupported", 841 "leading_comma", 842 "max_text_width", 843 "comments", 844 "dialect", 845 "unsupported_messages", 846 "_escaped_quote_end", 847 "_escaped_byte_quote_end", 848 "_escaped_identifier_end", 849 "_next_name", 850 "_identifier_start", 851 "_identifier_end", 852 "_quote_json_path_key_using_brackets", 853 "_dispatch", 854 ) 855 856 def __init__( 857 self, 858 pretty: bool | int | None = None, 859 identify: str | bool = False, 860 normalize: bool = False, 861 pad: int = 2, 862 indent: int = 2, 863 normalize_functions: str | bool | None = None, 864 unsupported_level: ErrorLevel = ErrorLevel.WARN, 865 max_unsupported: int = 3, 866 leading_comma: bool = False, 867 max_text_width: int = 80, 868 comments: bool = True, 869 dialect: DialectType = None, 870 ): 871 import sqlglot 872 import sqlglot.dialects.dialect 873 874 self.pretty = pretty if pretty is not None else sqlglot.pretty 875 self.identify = identify 876 self.normalize = normalize 877 self.pad = pad 878 self._indent = indent 879 self.unsupported_level = unsupported_level 880 self.max_unsupported = max_unsupported 881 self.leading_comma = leading_comma 882 self.max_text_width = max_text_width 883 self.comments = comments 884 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 885 886 # This is both a Dialect property and a Generator argument, so we prioritize the latter 887 self.normalize_functions = ( 888 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 889 ) 890 891 self.unsupported_messages: list[str] = [] 892 self._escaped_quote_end: str = ( 893 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 894 ) 895 self._escaped_byte_quote_end: str = ( 896 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 897 if self.dialect.BYTE_END 898 else "" 899 ) 900 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 901 902 self._next_name = name_sequence("_t") 903 904 self._identifier_start = self.dialect.IDENTIFIER_START 905 self._identifier_end = self.dialect.IDENTIFIER_END 906 907 self._quote_json_path_key_using_brackets = True 908 909 cls = type(self) 910 dispatch = _DISPATCH_CACHE.get(cls) 911 if dispatch is None: 912 dispatch = _build_dispatch(cls) 913 _DISPATCH_CACHE[cls] = dispatch 914 self._dispatch = dispatch 915 916 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 917 """ 918 Generates the SQL string corresponding to the given syntax tree. 919 920 Args: 921 expression: The syntax tree. 922 copy: Whether to copy the expression. The generator performs mutations so 923 it is safer to copy. 924 925 Returns: 926 The SQL string corresponding to `expression`. 927 """ 928 if copy: 929 expression = expression.copy() 930 931 expression = self.preprocess(expression) 932 933 self.unsupported_messages = [] 934 sql = self.sql(expression).strip() 935 936 if self.pretty: 937 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 938 939 if self.unsupported_level == ErrorLevel.IGNORE: 940 return sql 941 942 if self.unsupported_level == ErrorLevel.WARN: 943 for msg in self.unsupported_messages: 944 logger.warning(msg) 945 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 946 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 947 948 return sql 949 950 def preprocess(self, expression: exp.Expr) -> exp.Expr: 951 """Apply generic preprocessing transformations to a given expression.""" 952 expression = self._move_ctes_to_top_level(expression) 953 954 if self.ENSURE_BOOLS: 955 import sqlglot.transforms 956 957 expression = sqlglot.transforms.ensure_bools(expression) 958 959 return expression 960 961 def _move_ctes_to_top_level(self, expression: E) -> E: 962 if ( 963 not expression.parent 964 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 965 and any(node.parent is not expression for node in expression.find_all(exp.With)) 966 ): 967 import sqlglot.transforms 968 969 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 970 return expression 971 972 def unsupported(self, message: str) -> None: 973 if self.unsupported_level == ErrorLevel.IMMEDIATE: 974 raise UnsupportedError(message) 975 self.unsupported_messages.append(message) 976 977 def sep(self, sep: str = " ") -> str: 978 return f"{sep.strip()}\n" if self.pretty else sep 979 980 def seg(self, sql: str, sep: str = " ") -> str: 981 return f"{self.sep(sep)}{sql}" 982 983 def sanitize_comment(self, comment: str) -> str: 984 comment = " " + comment if comment[0].strip() else comment 985 comment = comment + " " if comment[-1].strip() else comment 986 987 # Escape block comment markers to prevent premature closure or unintended nesting. 988 # This is necessary because single-line comments (--) are converted to block comments 989 # (/* */) on output, and any */ in the original text would close the comment early. 990 comment = comment.replace("*/", "* /").replace("/*", "/ *") 991 992 return comment 993 994 def maybe_comment( 995 self, 996 sql: str, 997 expression: exp.Expr | None = None, 998 comments: list[str] | None = None, 999 separated: bool = False, 1000 ) -> str: 1001 comments = ( 1002 ((expression and expression.comments) if comments is None else comments) # type: ignore 1003 if self.comments 1004 else None 1005 ) 1006 1007 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1008 return sql 1009 1010 comments_sql = " ".join( 1011 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 1012 ) 1013 1014 if not comments_sql: 1015 return sql 1016 1017 comments_sql = self._replace_line_breaks(comments_sql) 1018 1019 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1020 return ( 1021 f"{self.sep()}{comments_sql}{sql}" 1022 if not sql or sql[0].isspace() 1023 else f"{comments_sql}{self.sep()}{sql}" 1024 ) 1025 1026 return f"{sql} {comments_sql}" 1027 1028 def wrap(self, expression: exp.Expr | str) -> str: 1029 this_sql = ( 1030 self.sql(expression) 1031 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1032 else self.sql(expression, "this") 1033 ) 1034 if not this_sql: 1035 return "()" 1036 1037 this_sql = self.indent(this_sql, level=1, pad=0) 1038 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1039 1040 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1041 original = self.identify 1042 self.identify = False 1043 result = func(*args, **kwargs) 1044 self.identify = original 1045 return result 1046 1047 def normalize_func(self, name: str) -> str: 1048 if self.normalize_functions == "upper" or self.normalize_functions is True: 1049 return name.upper() 1050 if self.normalize_functions == "lower": 1051 return name.lower() 1052 return name 1053 1054 def indent( 1055 self, 1056 sql: str, 1057 level: int = 0, 1058 pad: int | None = None, 1059 skip_first: bool = False, 1060 skip_last: bool = False, 1061 ) -> str: 1062 if not self.pretty or not sql: 1063 return sql 1064 1065 pad = self.pad if pad is None else pad 1066 lines = sql.split("\n") 1067 1068 return "\n".join( 1069 ( 1070 line 1071 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1072 else f"{' ' * (level * self._indent + pad)}{line}" 1073 ) 1074 for i, line in enumerate(lines) 1075 ) 1076 1077 def sql( 1078 self, 1079 expression: str | exp.Expr | None, 1080 key: str | None = None, 1081 comment: bool = True, 1082 ) -> str: 1083 if not expression: 1084 return "" 1085 1086 if isinstance(expression, str): 1087 return expression 1088 1089 if key: 1090 value = expression.args.get(key) 1091 if value: 1092 return self.sql(value) 1093 return "" 1094 1095 handler = self._dispatch.get(expression.__class__) 1096 1097 if handler: 1098 sql = handler(self, expression) 1099 elif isinstance(expression, exp.Func): 1100 sql = self.function_fallback_sql(expression) 1101 elif isinstance(expression, exp.Property): 1102 sql = self.property_sql(expression) 1103 else: 1104 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1105 1106 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1107 1108 def uncache_sql(self, expression: exp.Uncache) -> str: 1109 table = self.sql(expression, "this") 1110 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1111 return f"UNCACHE TABLE{exists_sql} {table}" 1112 1113 def cache_sql(self, expression: exp.Cache) -> str: 1114 lazy = " LAZY" if expression.args.get("lazy") else "" 1115 table = self.sql(expression, "this") 1116 options = expression.args.get("options") 1117 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1118 sql = self.sql(expression, "expression") 1119 sql = f" AS{self.sep()}{sql}" if sql else "" 1120 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1121 return self.prepend_ctes(expression, sql) 1122 1123 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1124 default = "DEFAULT " if expression.args.get("default") else "" 1125 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1126 1127 def column_parts(self, expression: exp.Column) -> str: 1128 return ".".join( 1129 self.sql(part) 1130 for part in ( 1131 expression.args.get("catalog"), 1132 expression.args.get("db"), 1133 expression.args.get("table"), 1134 expression.args.get("this"), 1135 ) 1136 if part 1137 ) 1138 1139 def column_sql(self, expression: exp.Column) -> str: 1140 join_mark = " (+)" if expression.args.get("join_mark") else "" 1141 1142 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1143 join_mark = "" 1144 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1145 1146 return f"{self.column_parts(expression)}{join_mark}" 1147 1148 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1149 return self.column_sql(expression) 1150 1151 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1152 this = self.sql(expression, "this") 1153 this = f" {this}" if this else "" 1154 position = self.sql(expression, "position") 1155 return f"{position}{this}" 1156 1157 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1158 column = self.sql(expression, "this") 1159 kind = self.sql(expression, "kind") 1160 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1161 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1162 kind = f"{sep}{kind}" if kind else "" 1163 constraints = f" {constraints}" if constraints else "" 1164 position = self.sql(expression, "position") 1165 position = f" {position}" if position else "" 1166 1167 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1168 kind = "" 1169 1170 return f"{exists}{column}{kind}{constraints}{position}" 1171 1172 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1173 this = self.sql(expression, "this") 1174 kind_sql = self.sql(expression, "kind").strip() 1175 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1176 1177 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1178 this = self.sql(expression, "this") 1179 if expression.args.get("not_null"): 1180 persisted = " PERSISTED NOT NULL" 1181 elif expression.args.get("persisted"): 1182 persisted = " PERSISTED" 1183 else: 1184 persisted = "" 1185 1186 return f"AS {this}{persisted}" 1187 1188 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1189 return self.token_sql(TokenType.AUTO_INCREMENT) 1190 1191 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1192 if isinstance(expression.this, list): 1193 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1194 else: 1195 this = self.sql(expression, "this") 1196 1197 return f"COMPRESS {this}" 1198 1199 def generatedasidentitycolumnconstraint_sql( 1200 self, expression: exp.GeneratedAsIdentityColumnConstraint 1201 ) -> str: 1202 this = "" 1203 if expression.this is not None: 1204 on_null = " ON NULL" if expression.args.get("on_null") else "" 1205 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1206 1207 start = expression.args.get("start") 1208 start = f"START WITH {start}" if start else "" 1209 increment = expression.args.get("increment") 1210 increment = f" INCREMENT BY {increment}" if increment else "" 1211 minvalue = expression.args.get("minvalue") 1212 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1213 maxvalue = expression.args.get("maxvalue") 1214 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1215 cycle = expression.args.get("cycle") 1216 cycle_sql = "" 1217 1218 if cycle is not None: 1219 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1220 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1221 1222 sequence_opts = "" 1223 if start or increment or cycle_sql: 1224 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1225 sequence_opts = f" ({sequence_opts.strip()})" 1226 1227 expr = self.sql(expression, "expression") 1228 expr = f"({expr})" if expr else "IDENTITY" 1229 1230 return f"GENERATED{this} AS {expr}{sequence_opts}" 1231 1232 def generatedasrowcolumnconstraint_sql( 1233 self, expression: exp.GeneratedAsRowColumnConstraint 1234 ) -> str: 1235 start = "START" if expression.args.get("start") else "END" 1236 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1237 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1238 1239 def periodforsystemtimeconstraint_sql( 1240 self, expression: exp.PeriodForSystemTimeConstraint 1241 ) -> str: 1242 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1243 1244 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1245 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1246 1247 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1248 desc = expression.args.get("desc") 1249 if desc is not None: 1250 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1251 options = self.expressions(expression, key="options", flat=True, sep=" ") 1252 options = f" {options}" if options else "" 1253 return f"PRIMARY KEY{options}" 1254 1255 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1256 this = self.sql(expression, "this") 1257 this = f" {this}" if this else "" 1258 index_type = expression.args.get("index_type") 1259 index_type = f" USING {index_type}" if index_type else "" 1260 on_conflict = self.sql(expression, "on_conflict") 1261 on_conflict = f" {on_conflict}" if on_conflict else "" 1262 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1263 options = self.expressions(expression, key="options", flat=True, sep=" ") 1264 options = f" {options}" if options else "" 1265 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1266 1267 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1268 input_ = expression.args.get("input_") 1269 output = expression.args.get("output") 1270 variadic = expression.args.get("variadic") 1271 1272 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1273 if variadic: 1274 return "VARIADIC" 1275 1276 if input_ and output: 1277 return f"IN{self.INOUT_SEPARATOR}OUT" 1278 if input_: 1279 return "IN" 1280 if output: 1281 return "OUT" 1282 1283 return "" 1284 1285 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1286 return self.sql(expression, "this") 1287 1288 def create_sql(self, expression: exp.Create) -> str: 1289 kind = self.sql(expression, "kind") 1290 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1291 1292 properties = expression.args.get("properties") 1293 1294 if ( 1295 kind == "TRIGGER" 1296 and properties 1297 and properties.expressions 1298 and isinstance(properties.expressions[0], exp.TriggerProperties) 1299 and properties.expressions[0].args.get("constraint") 1300 ): 1301 kind = f"CONSTRAINT {kind}" 1302 1303 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1304 1305 this = self.createable_sql(expression, properties_locs) 1306 1307 properties_sql = "" 1308 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1309 exp.Properties.Location.POST_WITH 1310 ): 1311 props_ast = exp.Properties( 1312 expressions=[ 1313 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1314 *properties_locs[exp.Properties.Location.POST_WITH], 1315 ] 1316 ) 1317 props_ast.parent = expression 1318 properties_sql = self.sql(props_ast) 1319 1320 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1321 properties_sql = self.sep() + properties_sql 1322 elif not self.pretty: 1323 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1324 properties_sql = f" {properties_sql}" 1325 1326 begin = " BEGIN" if expression.args.get("begin") else "" 1327 1328 expression_sql = self.sql(expression, "expression") 1329 if expression_sql: 1330 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1331 1332 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1333 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1334 ): 1335 postalias_props_sql = "" 1336 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1337 postalias_props_sql = self.properties( 1338 exp.Properties( 1339 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1340 ), 1341 wrapped=False, 1342 ) 1343 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1344 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1345 1346 postindex_props_sql = "" 1347 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1348 postindex_props_sql = self.properties( 1349 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1350 wrapped=False, 1351 prefix=" ", 1352 ) 1353 1354 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1355 indexes = f" {indexes}" if indexes else "" 1356 index_sql = indexes + postindex_props_sql 1357 1358 replace = " OR REPLACE" if expression.args.get("replace") else "" 1359 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1360 unique = " UNIQUE" if expression.args.get("unique") else "" 1361 1362 clustered = expression.args.get("clustered") 1363 if clustered is None: 1364 clustered_sql = "" 1365 elif clustered: 1366 clustered_sql = " CLUSTERED COLUMNSTORE" 1367 else: 1368 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1369 1370 postcreate_props_sql = "" 1371 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1372 postcreate_props_sql = self.properties( 1373 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1374 sep=" ", 1375 prefix=" ", 1376 wrapped=False, 1377 ) 1378 1379 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1380 1381 postexpression_props_sql = "" 1382 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1383 postexpression_props_sql = self.properties( 1384 exp.Properties( 1385 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1386 ), 1387 sep=" ", 1388 prefix=" ", 1389 wrapped=False, 1390 ) 1391 1392 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1393 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1394 no_schema_binding = ( 1395 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1396 ) 1397 1398 clone = self.sql(expression, "clone") 1399 clone = f" {clone}" if clone else "" 1400 1401 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1402 properties_expression = f"{expression_sql}{properties_sql}" 1403 else: 1404 properties_expression = f"{properties_sql}{expression_sql}" 1405 1406 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1407 return self.prepend_ctes(expression, expression_sql) 1408 1409 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1410 start = self.sql(expression, "start") 1411 start = f"START WITH {start}" if start else "" 1412 increment = self.sql(expression, "increment") 1413 increment = f" INCREMENT BY {increment}" if increment else "" 1414 minvalue = self.sql(expression, "minvalue") 1415 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1416 maxvalue = self.sql(expression, "maxvalue") 1417 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1418 owned = self.sql(expression, "owned") 1419 owned = f" OWNED BY {owned}" if owned else "" 1420 1421 cache = expression.args.get("cache") 1422 if cache is None: 1423 cache_str = "" 1424 elif cache is True: 1425 cache_str = " CACHE" 1426 else: 1427 cache_str = f" CACHE {cache}" 1428 1429 options = self.expressions(expression, key="options", flat=True, sep=" ") 1430 options = f" {options}" if options else "" 1431 1432 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1433 1434 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1435 timing = expression.args.get("timing", "") 1436 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1437 timing_events = f"{timing} {events}".strip() if timing or events else "" 1438 1439 parts = [timing_events, "ON", self.sql(expression, "table")] 1440 1441 if referenced_table := expression.args.get("referenced_table"): 1442 parts.extend(["FROM", self.sql(referenced_table)]) 1443 1444 if deferrable := expression.args.get("deferrable"): 1445 parts.append(deferrable) 1446 1447 if initially := expression.args.get("initially"): 1448 parts.append(f"INITIALLY {initially}") 1449 1450 if referencing := expression.args.get("referencing"): 1451 parts.append(self.sql(referencing)) 1452 1453 if for_each := expression.args.get("for_each"): 1454 parts.append(f"FOR EACH {for_each}") 1455 1456 if when := expression.args.get("when"): 1457 parts.append(f"WHEN ({self.sql(when)})") 1458 1459 parts.append(self.sql(expression, "execute")) 1460 1461 return self.sep().join(parts) 1462 1463 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1464 parts = [] 1465 1466 if old_alias := expression.args.get("old"): 1467 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1468 1469 if new_alias := expression.args.get("new"): 1470 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1471 1472 return f"REFERENCING {' '.join(parts)}" 1473 1474 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1475 columns = expression.args.get("columns") 1476 if columns: 1477 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1478 1479 return self.sql(expression, "this") 1480 1481 def clone_sql(self, expression: exp.Clone) -> str: 1482 this = self.sql(expression, "this") 1483 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1484 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1485 return f"{shallow}{keyword} {this}" 1486 1487 def describe_sql(self, expression: exp.Describe) -> str: 1488 style = expression.args.get("style") 1489 style = f" {style}" if style else "" 1490 partition = self.sql(expression, "partition") 1491 partition = f" {partition}" if partition else "" 1492 format = self.sql(expression, "format") 1493 format = f" {format}" if format else "" 1494 as_json = " AS JSON" if expression.args.get("as_json") else "" 1495 1496 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1497 1498 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1499 tag = self.sql(expression, "tag") 1500 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1501 1502 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1503 with_ = self.sql(expression, "with_") 1504 if with_: 1505 sql = f"{with_}{self.sep()}{sql}" 1506 return sql 1507 1508 def with_sql(self, expression: exp.With) -> str: 1509 sql = self.expressions(expression, flat=True) 1510 recursive = ( 1511 "RECURSIVE " 1512 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1513 else "" 1514 ) 1515 search = self.sql(expression, "search") 1516 search = f" {search}" if search else "" 1517 1518 return f"WITH {recursive}{sql}{search}" 1519 1520 def cte_sql(self, expression: exp.CTE) -> str: 1521 alias = expression.args.get("alias") 1522 if alias: 1523 alias.add_comments(expression.pop_comments()) 1524 1525 alias_sql = self.sql(expression, "alias") 1526 1527 materialized = expression.args.get("materialized") 1528 if materialized is False: 1529 materialized = "NOT MATERIALIZED " 1530 elif materialized: 1531 materialized = "MATERIALIZED " 1532 1533 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1534 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1535 1536 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1537 1538 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1539 alias = self.sql(expression, "this") 1540 columns = self.expressions(expression, key="columns", flat=True) 1541 columns = f"({columns})" if columns else "" 1542 1543 if ( 1544 columns 1545 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1546 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1547 ): 1548 columns = "" 1549 self.unsupported("Named columns are not supported in table alias.") 1550 1551 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1552 alias = self._next_name() 1553 1554 return f"{alias}{columns}" 1555 1556 def bitstring_sql(self, expression: exp.BitString) -> str: 1557 this = self.sql(expression, "this") 1558 if self.dialect.BIT_START: 1559 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1560 return f"{int(this, 2)}" 1561 1562 def hexstring_sql( 1563 self, expression: exp.HexString, binary_function_repr: str | None = None 1564 ) -> str: 1565 this = self.sql(expression, "this") 1566 is_integer_type = expression.args.get("is_integer") 1567 1568 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1569 not self.dialect.HEX_START and not binary_function_repr 1570 ): 1571 # Integer representation will be returned if: 1572 # - The read dialect treats the hex value as integer literal but not the write 1573 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1574 return f"{int(this, 16)}" 1575 1576 if not is_integer_type: 1577 # Read dialect treats the hex value as BINARY/BLOB 1578 if binary_function_repr: 1579 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1580 return self.func(binary_function_repr, exp.Literal.string(this)) 1581 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1582 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1583 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1584 1585 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1586 1587 def bytestring_sql(self, expression: exp.ByteString) -> str: 1588 this = self.sql(expression, "this") 1589 if self.dialect.BYTE_START: 1590 escaped_byte_string = self.escape_str( 1591 this, 1592 escape_backslash=False, 1593 delimiter=self.dialect.BYTE_END, 1594 escaped_delimiter=self._escaped_byte_quote_end, 1595 is_byte_string=True, 1596 ) 1597 is_bytes = expression.args.get("is_bytes", False) 1598 delimited_byte_string = ( 1599 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1600 ) 1601 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1602 return self.sql( 1603 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1604 ) 1605 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1606 return self.sql( 1607 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1608 ) 1609 1610 return delimited_byte_string 1611 1612 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1613 return self.sql(exp.Literal.string(this)) 1614 1615 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1616 return "" 1617 1618 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1619 this = self.sql(expression, "this") 1620 escape = expression.args.get("escape") 1621 1622 if self.dialect.UNICODE_START: 1623 escape_substitute = r"\\\1" 1624 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1625 else: 1626 escape_substitute = r"\\u\1" 1627 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1628 1629 if escape: 1630 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1631 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1632 else: 1633 escape_pattern = ESCAPED_UNICODE_RE 1634 escape_sql = "" 1635 1636 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1637 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1638 1639 return f"{left_quote}{this}{right_quote}{escape_sql}" 1640 1641 def rawstring_sql(self, expression: exp.RawString) -> str: 1642 string = expression.this 1643 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1644 string = string.replace("\\", "\\\\") 1645 1646 string = self.escape_str(string, escape_backslash=False) 1647 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1648 1649 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1650 this = self.sql(expression, "this") 1651 specifier = self.sql(expression, "expression") 1652 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1653 return f"{this}{specifier}" 1654 1655 def datatype_param_bound_limiter( 1656 self, 1657 expression: exp.DataType, 1658 type_value: exp.DType, 1659 defaults: tuple[int, ...], 1660 bounds: tuple[int | None, ...], 1661 ) -> exp.DataType: 1662 params = expression.expressions 1663 1664 if not params: 1665 if defaults: 1666 expression.set( 1667 "expressions", 1668 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1669 ) 1670 return expression 1671 1672 if not bounds: 1673 return expression 1674 1675 for i, param in enumerate(params): 1676 bound = bounds[i] if i < len(bounds) else None 1677 if bound is None: 1678 continue 1679 1680 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1681 if ( 1682 isinstance(param_value, exp.Literal) 1683 and param_value.is_number 1684 and int(param_value.to_py()) > bound 1685 ): 1686 self.unsupported( 1687 f"{type_value.value} parameter {param_value.name} exceeds " 1688 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1689 ) 1690 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1691 1692 return expression 1693 1694 def datatype_sql(self, expression: exp.DataType) -> str: 1695 nested = "" 1696 values = "" 1697 1698 expr_nested = expression.args.get("nested") 1699 type_value = expression.this 1700 1701 if ( 1702 not expr_nested 1703 and isinstance(type_value, exp.DType) 1704 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1705 ): 1706 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1707 1708 interior = ( 1709 self.expressions( 1710 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1711 ) 1712 if expr_nested and self.pretty 1713 else self.expressions(expression, flat=True) 1714 ) 1715 1716 if type_value in self.UNSUPPORTED_TYPES: 1717 self.unsupported( 1718 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1719 ) 1720 1721 type_sql: t.Any = "" 1722 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1723 type_sql = self.sql(expression, "kind") 1724 elif type_value == exp.DType.CHARACTER_SET: 1725 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1726 else: 1727 type_sql = ( 1728 self.TYPE_MAPPING.get(type_value, type_value.value) 1729 if isinstance(type_value, exp.DType) 1730 else type_value 1731 ) 1732 1733 if interior: 1734 if expr_nested: 1735 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1736 if expression.args.get("values") is not None: 1737 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1738 values = self.expressions(expression, key="values", flat=True) 1739 values = f"{delimiters[0]}{values}{delimiters[1]}" 1740 elif type_value == exp.DType.INTERVAL: 1741 nested = f" {interior}" 1742 else: 1743 nested = f"({interior})" 1744 1745 type_sql = f"{type_sql}{nested}{values}" 1746 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1747 exp.DType.TIMETZ, 1748 exp.DType.TIMESTAMPTZ, 1749 ): 1750 type_sql = f"{type_sql} WITH TIME ZONE" 1751 1752 collate = self.sql(expression, "collate") 1753 if collate: 1754 type_sql = f"{type_sql} COLLATE {collate}" 1755 1756 return type_sql 1757 1758 def directory_sql(self, expression: exp.Directory) -> str: 1759 local = "LOCAL " if expression.args.get("local") else "" 1760 row_format = self.sql(expression, "row_format") 1761 row_format = f" {row_format}" if row_format else "" 1762 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1763 1764 def delete_sql(self, expression: exp.Delete) -> str: 1765 hint = self.sql(expression, "hint") 1766 this = self.sql(expression, "this") 1767 this = f" FROM {this}" if this else "" 1768 using = self.expressions(expression, key="using") 1769 using = f" USING {using}" if using else "" 1770 cluster = self.sql(expression, "cluster") 1771 cluster = f" {cluster}" if cluster else "" 1772 where = self.sql(expression, "where") 1773 returning = self.sql(expression, "returning") 1774 order = self.sql(expression, "order") 1775 limit = self.sql(expression, "limit") 1776 tables = self.expressions(expression, key="tables") 1777 tables = f" {tables}" if tables else "" 1778 if self.RETURNING_END: 1779 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1780 else: 1781 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1782 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1783 1784 def drop_sql(self, expression: exp.Drop) -> str: 1785 this = self.sql(expression, "this") 1786 expressions = self.expressions(expression, flat=True) 1787 expressions = f" ({expressions})" if expressions else "" 1788 kind = expression.args["kind"] 1789 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1790 iceberg = ( 1791 " ICEBERG" 1792 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1793 else "" 1794 ) 1795 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1796 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1797 on_cluster = self.sql(expression, "cluster") 1798 on_cluster = f" {on_cluster}" if on_cluster else "" 1799 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1800 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1801 cascade = " CASCADE" if expression.args.get("cascade") else "" 1802 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1803 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1804 purge = " PURGE" if expression.args.get("purge") else "" 1805 sync = " SYNC" if expression.args.get("sync") else "" 1806 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}" 1807 1808 def set_operation(self, expression: exp.SetOperation) -> str: 1809 op_type = type(expression) 1810 op_name = op_type.key.upper() 1811 1812 distinct = expression.args.get("distinct") 1813 if ( 1814 distinct is False 1815 and op_type in (exp.Except, exp.Intersect) 1816 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1817 ): 1818 self.unsupported(f"{op_name} ALL is not supported") 1819 1820 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1821 1822 if distinct is None: 1823 distinct = default_distinct 1824 if distinct is None: 1825 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1826 1827 if distinct is default_distinct: 1828 distinct_or_all = "" 1829 else: 1830 distinct_or_all = " DISTINCT" if distinct else " ALL" 1831 1832 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1833 side_kind = f"{side_kind} " if side_kind else "" 1834 1835 by_name = " BY NAME" if expression.args.get("by_name") else "" 1836 on = self.expressions(expression, key="on", flat=True) 1837 on = f" ON ({on})" if on else "" 1838 1839 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1840 1841 def set_operations(self, expression: exp.SetOperation) -> str: 1842 if not self.SET_OP_MODIFIERS: 1843 limit = expression.args.get("limit") 1844 order = expression.args.get("order") 1845 1846 if limit or order: 1847 select = self._move_ctes_to_top_level( 1848 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1849 ) 1850 1851 if limit: 1852 select = select.limit(limit.pop(), copy=False) 1853 if order: 1854 select = select.order_by(order.pop(), copy=False) 1855 return self.sql(select) 1856 1857 sqls: list[str] = [] 1858 stack: list[str | exp.Expr] = [expression] 1859 1860 while stack: 1861 node = stack.pop() 1862 1863 if isinstance(node, exp.SetOperation): 1864 stack.append(node.expression) 1865 stack.append( 1866 self.maybe_comment( 1867 self.set_operation(node), comments=node.comments, separated=True 1868 ) 1869 ) 1870 stack.append(node.this) 1871 else: 1872 sqls.append(self.sql(node)) 1873 1874 this = self.sep().join(sqls) 1875 this = self.query_modifiers(expression, this) 1876 return self.prepend_ctes(expression, this) 1877 1878 def fetch_sql(self, expression: exp.Fetch) -> str: 1879 direction = expression.args.get("direction") 1880 direction = f" {direction}" if direction else "" 1881 count = self.sql(expression, "count") 1882 count = f" {count}" if count else "" 1883 limit_options = self.sql(expression, "limit_options") 1884 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1885 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1886 1887 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1888 percent = " PERCENT" if expression.args.get("percent") else "" 1889 rows = " ROWS" if expression.args.get("rows") else "" 1890 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1891 if not with_ties and rows: 1892 with_ties = " ONLY" 1893 return f"{percent}{rows}{with_ties}" 1894 1895 def filter_sql(self, expression: exp.Filter) -> str: 1896 if self.AGGREGATE_FILTER_SUPPORTED: 1897 this = self.sql(expression, "this") 1898 where = self.sql(expression, "expression").strip() 1899 return f"{this} FILTER({where})" 1900 1901 agg = expression.this 1902 agg_arg = agg.this 1903 cond = expression.expression.this 1904 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1905 return self.sql(agg) 1906 1907 def hint_sql(self, expression: exp.Hint) -> str: 1908 if not self.QUERY_HINTS: 1909 self.unsupported("Hints are not supported") 1910 return "" 1911 1912 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1913 1914 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1915 using = self.sql(expression, "using") 1916 using = f" USING {using}" if using else "" 1917 columns = self.expressions(expression, key="columns", flat=True) 1918 columns = f"({columns})" if columns else "" 1919 partition_by = self.expressions(expression, key="partition_by", flat=True) 1920 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1921 where = self.sql(expression, "where") 1922 include = self.expressions(expression, key="include", flat=True) 1923 if include: 1924 include = f" INCLUDE ({include})" 1925 with_storage = self.expressions(expression, key="with_storage", flat=True) 1926 with_storage = f" WITH ({with_storage})" if with_storage else "" 1927 tablespace = self.sql(expression, "tablespace") 1928 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1929 on = self.sql(expression, "on") 1930 on = f" ON {on}" if on else "" 1931 1932 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1933 1934 def index_sql(self, expression: exp.Index) -> str: 1935 unique = "UNIQUE " if expression.args.get("unique") else "" 1936 primary = "PRIMARY " if expression.args.get("primary") else "" 1937 amp = "AMP " if expression.args.get("amp") else "" 1938 name = self.sql(expression, "this") 1939 name = f"{name} " if name else "" 1940 table = self.sql(expression, "table") 1941 table = f"{self.INDEX_ON} {table}" if table else "" 1942 1943 index = "INDEX " if not table else "" 1944 1945 params = self.sql(expression, "params") 1946 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1947 1948 def identifier_sql(self, expression: exp.Identifier) -> str: 1949 text = expression.name 1950 lower = text.lower() 1951 quoted = expression.quoted 1952 text = lower if self.normalize and not quoted else text 1953 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1954 if ( 1955 quoted 1956 or self.dialect.can_quote(expression, self.identify) 1957 or lower in self.RESERVED_KEYWORDS 1958 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1959 ): 1960 text = ( 1961 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1962 ) 1963 return text 1964 1965 def hex_sql(self, expression: exp.Hex) -> str: 1966 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1967 if self.dialect.HEX_LOWERCASE: 1968 text = self.func("LOWER", text) 1969 1970 return text 1971 1972 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1973 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1974 if not self.dialect.HEX_LOWERCASE: 1975 text = self.func("LOWER", text) 1976 return text 1977 1978 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1979 input_format = self.sql(expression, "input_format") 1980 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1981 output_format = self.sql(expression, "output_format") 1982 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1983 return self.sep().join((input_format, output_format)) 1984 1985 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1986 string = self.sql(exp.Literal.string(expression.name)) 1987 return f"{prefix}{string}" 1988 1989 def partition_sql(self, expression: exp.Partition) -> str: 1990 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1991 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1992 1993 def properties_sql(self, expression: exp.Properties) -> str: 1994 root_properties = [] 1995 with_properties = [] 1996 1997 for p in expression.expressions: 1998 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1999 if p_loc == exp.Properties.Location.POST_WITH: 2000 with_properties.append(p) 2001 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2002 root_properties.append(p) 2003 2004 root_props_ast = exp.Properties(expressions=root_properties) 2005 root_props_ast.parent = expression.parent 2006 2007 with_props_ast = exp.Properties(expressions=with_properties) 2008 with_props_ast.parent = expression.parent 2009 2010 root_props = self.root_properties(root_props_ast) 2011 with_props = self.with_properties(with_props_ast) 2012 2013 if root_props and with_props and not self.pretty: 2014 with_props = " " + with_props 2015 2016 return root_props + with_props 2017 2018 def root_properties(self, properties: exp.Properties) -> str: 2019 if properties.expressions: 2020 return self.expressions(properties, indent=False, sep=" ") 2021 return "" 2022 2023 def properties( 2024 self, 2025 properties: exp.Properties, 2026 prefix: str = "", 2027 sep: str = ", ", 2028 suffix: str = "", 2029 wrapped: bool = True, 2030 ) -> str: 2031 if properties.expressions: 2032 expressions = self.expressions(properties, sep=sep, indent=False) 2033 if expressions: 2034 expressions = self.wrap(expressions) if wrapped else expressions 2035 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2036 return "" 2037 2038 def with_properties(self, properties: exp.Properties) -> str: 2039 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2040 2041 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2042 properties_locs = defaultdict(list) 2043 for p in properties.expressions: 2044 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2045 if p_loc != exp.Properties.Location.UNSUPPORTED: 2046 properties_locs[p_loc].append(p) 2047 else: 2048 self.unsupported(f"Unsupported property {p.key}") 2049 2050 return properties_locs 2051 2052 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2053 if isinstance(expression.this, exp.Dot): 2054 return self.sql(expression, "this") 2055 return f"'{expression.name}'" if string_key else expression.name 2056 2057 def property_sql(self, expression: exp.Property) -> str: 2058 property_cls = expression.__class__ 2059 if property_cls == exp.Property: 2060 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2061 2062 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2063 if not property_name: 2064 self.unsupported(f"Unsupported property {expression.key}") 2065 2066 return f"{property_name}={self.sql(expression, 'this')}" 2067 2068 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2069 return f"UUID {self.sql(expression, 'this')}" 2070 2071 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2072 if self.SUPPORTS_CREATE_TABLE_LIKE: 2073 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2074 options = f" {options}" if options else "" 2075 2076 like = f"LIKE {self.sql(expression, 'this')}{options}" 2077 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2078 like = f"({like})" 2079 2080 return like 2081 2082 if expression.expressions: 2083 self.unsupported("Transpilation of LIKE property options is unsupported") 2084 2085 select = exp.select("*").from_(expression.this).limit(0) 2086 return f"AS {self.sql(select)}" 2087 2088 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2089 no = "NO " if expression.args.get("no") else "" 2090 protection = " PROTECTION" if expression.args.get("protection") else "" 2091 return f"{no}FALLBACK{protection}" 2092 2093 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2094 no = "NO " if expression.args.get("no") else "" 2095 local = expression.args.get("local") 2096 local = f"{local} " if local else "" 2097 dual = "DUAL " if expression.args.get("dual") else "" 2098 before = "BEFORE " if expression.args.get("before") else "" 2099 after = "AFTER " if expression.args.get("after") else "" 2100 return f"{no}{local}{dual}{before}{after}JOURNAL" 2101 2102 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2103 freespace = self.sql(expression, "this") 2104 percent = " PERCENT" if expression.args.get("percent") else "" 2105 return f"FREESPACE={freespace}{percent}" 2106 2107 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2108 if expression.args.get("default"): 2109 property = "DEFAULT" 2110 elif expression.args.get("on"): 2111 property = "ON" 2112 else: 2113 property = "OFF" 2114 return f"CHECKSUM={property}" 2115 2116 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2117 if expression.args.get("no"): 2118 return "NO MERGEBLOCKRATIO" 2119 if expression.args.get("default"): 2120 return "DEFAULT MERGEBLOCKRATIO" 2121 2122 percent = " PERCENT" if expression.args.get("percent") else "" 2123 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2124 2125 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2126 expressions = self.expressions(expression, flat=True) 2127 expressions = f"({expressions})" if expressions else "" 2128 return f"USING {self.sql(expression, 'this')}{expressions}" 2129 2130 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2131 default = expression.args.get("default") 2132 minimum = expression.args.get("minimum") 2133 maximum = expression.args.get("maximum") 2134 if default or minimum or maximum: 2135 if default: 2136 prop = "DEFAULT" 2137 elif minimum: 2138 prop = "MINIMUM" 2139 else: 2140 prop = "MAXIMUM" 2141 return f"{prop} DATABLOCKSIZE" 2142 units = expression.args.get("units") 2143 units = f" {units}" if units else "" 2144 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2145 2146 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2147 autotemp = expression.args.get("autotemp") 2148 always = expression.args.get("always") 2149 default = expression.args.get("default") 2150 manual = expression.args.get("manual") 2151 never = expression.args.get("never") 2152 2153 if autotemp is not None: 2154 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2155 elif always: 2156 prop = "ALWAYS" 2157 elif default: 2158 prop = "DEFAULT" 2159 elif manual: 2160 prop = "MANUAL" 2161 elif never: 2162 prop = "NEVER" 2163 return f"BLOCKCOMPRESSION={prop}" 2164 2165 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2166 no = expression.args.get("no") 2167 no = " NO" if no else "" 2168 concurrent = expression.args.get("concurrent") 2169 concurrent = " CONCURRENT" if concurrent else "" 2170 target = self.sql(expression, "target") 2171 target = f" {target}" if target else "" 2172 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2173 2174 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2175 if isinstance(expression.this, list): 2176 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2177 if expression.this: 2178 modulus = self.sql(expression, "this") 2179 remainder = self.sql(expression, "expression") 2180 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2181 2182 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2183 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2184 return f"FROM ({from_expressions}) TO ({to_expressions})" 2185 2186 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2187 this = self.sql(expression, "this") 2188 2189 for_values_or_default = expression.expression 2190 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2191 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2192 else: 2193 for_values_or_default = " DEFAULT" 2194 2195 return f"PARTITION OF {this}{for_values_or_default}" 2196 2197 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2198 kind = expression.args.get("kind") 2199 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2200 for_or_in = expression.args.get("for_or_in") 2201 for_or_in = f" {for_or_in}" if for_or_in else "" 2202 lock_type = expression.args.get("lock_type") 2203 override = " OVERRIDE" if expression.args.get("override") else "" 2204 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2205 2206 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2207 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2208 statistics = expression.args.get("statistics") 2209 statistics_sql = "" 2210 if statistics is not None: 2211 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2212 return f"{data_sql}{statistics_sql}" 2213 2214 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2215 this = self.sql(expression, "this") 2216 this = f"HISTORY_TABLE={this}" if this else "" 2217 data_consistency: str | None = self.sql(expression, "data_consistency") 2218 data_consistency = ( 2219 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2220 ) 2221 retention_period: str | None = self.sql(expression, "retention_period") 2222 retention_period = ( 2223 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2224 ) 2225 2226 if this: 2227 on_sql = self.func("ON", this, data_consistency, retention_period) 2228 else: 2229 on_sql = "ON" if expression.args.get("on") else "OFF" 2230 2231 sql = f"SYSTEM_VERSIONING={on_sql}" 2232 2233 return f"WITH({sql})" if expression.args.get("with_") else sql 2234 2235 def insert_sql(self, expression: exp.Insert) -> str: 2236 hint = self.sql(expression, "hint") 2237 overwrite = expression.args.get("overwrite") 2238 2239 if isinstance(expression.this, exp.Directory): 2240 this = " OVERWRITE" if overwrite else " INTO" 2241 else: 2242 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2243 2244 stored = self.sql(expression, "stored") 2245 stored = f" {stored}" if stored else "" 2246 alternative = expression.args.get("alternative") 2247 alternative = f" OR {alternative}" if alternative else "" 2248 ignore = " IGNORE" if expression.args.get("ignore") else "" 2249 is_function = expression.args.get("is_function") 2250 if is_function: 2251 this = f"{this} FUNCTION" 2252 this = f"{this} {self.sql(expression, 'this')}" 2253 2254 exists = " IF EXISTS" if expression.args.get("exists") else "" 2255 where = self.sql(expression, "where") 2256 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2257 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2258 on_conflict = self.sql(expression, "conflict") 2259 on_conflict = f" {on_conflict}" if on_conflict else "" 2260 by_name = " BY NAME" if expression.args.get("by_name") else "" 2261 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2262 returning = self.sql(expression, "returning") 2263 2264 if self.RETURNING_END: 2265 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2266 else: 2267 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2268 2269 partition_by = self.sql(expression, "partition") 2270 partition_by = f" {partition_by}" if partition_by else "" 2271 settings = self.sql(expression, "settings") 2272 settings = f" {settings}" if settings else "" 2273 2274 source = self.sql(expression, "source") 2275 source = f"TABLE {source}" if source else "" 2276 2277 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2278 return self.prepend_ctes(expression, sql) 2279 2280 def introducer_sql(self, expression: exp.Introducer) -> str: 2281 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2282 2283 def kill_sql(self, expression: exp.Kill) -> str: 2284 kind = self.sql(expression, "kind") 2285 kind = f" {kind}" if kind else "" 2286 this = self.sql(expression, "this") 2287 this = f" {this}" if this else "" 2288 return f"KILL{kind}{this}" 2289 2290 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2291 return expression.name 2292 2293 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2294 return expression.name 2295 2296 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2297 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2298 2299 constraint = self.sql(expression, "constraint") 2300 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2301 2302 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2303 if conflict_keys: 2304 conflict_keys = f"({conflict_keys})" 2305 2306 index_predicate = self.sql(expression, "index_predicate") 2307 conflict_keys = f"{conflict_keys}{index_predicate} " 2308 2309 action = self.sql(expression, "action") 2310 2311 expressions = self.expressions(expression, flat=True) 2312 if expressions: 2313 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2314 expressions = f" {set_keyword}{expressions}" 2315 2316 where = self.sql(expression, "where") 2317 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2318 2319 def returning_sql(self, expression: exp.Returning) -> str: 2320 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2321 2322 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2323 fields = self.sql(expression, "fields") 2324 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2325 escaped = self.sql(expression, "escaped") 2326 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2327 items = self.sql(expression, "collection_items") 2328 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2329 keys = self.sql(expression, "map_keys") 2330 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2331 lines = self.sql(expression, "lines") 2332 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2333 null = self.sql(expression, "null") 2334 null = f" NULL DEFINED AS {null}" if null else "" 2335 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2336 2337 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2338 return f"WITH ({self.expressions(expression, flat=True)})" 2339 2340 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2341 this = f"{self.sql(expression, 'this')} INDEX" 2342 target = self.sql(expression, "target") 2343 target = f" FOR {target}" if target else "" 2344 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2345 2346 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2347 this = self.sql(expression, "this") 2348 kind = self.sql(expression, "kind") 2349 expr = self.sql(expression, "expression") 2350 return f"{this} ({kind} => {expr})" 2351 2352 def table_parts(self, expression: exp.Table) -> str: 2353 return ".".join( 2354 self.sql(part) 2355 for part in ( 2356 expression.args.get("catalog"), 2357 expression.args.get("db"), 2358 expression.args.get("this"), 2359 ) 2360 if part is not None 2361 ) 2362 2363 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2364 table = self.table_parts(expression) 2365 only = "ONLY " if expression.args.get("only") else "" 2366 partition = self.sql(expression, "partition") 2367 partition = f" {partition}" if partition else "" 2368 version = self.sql(expression, "version") 2369 version = f" {version}" if version else "" 2370 alias = self.sql(expression, "alias") 2371 alias = f"{sep}{alias}" if alias else "" 2372 2373 sample = self.sql(expression, "sample") 2374 post_alias = "" 2375 pre_alias = "" 2376 2377 if self.dialect.ALIAS_POST_TABLESAMPLE: 2378 pre_alias = sample 2379 else: 2380 post_alias = sample 2381 2382 if self.dialect.ALIAS_POST_VERSION: 2383 pre_alias = f"{pre_alias}{version}" 2384 else: 2385 post_alias = f"{post_alias}{version}" 2386 2387 hints = self.expressions(expression, key="hints", sep=" ") 2388 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2389 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2390 joins = self.indent( 2391 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2392 ) 2393 laterals = self.expressions(expression, key="laterals", sep="") 2394 2395 file_format = self.sql(expression, "format") 2396 if file_format: 2397 pattern = self.sql(expression, "pattern") 2398 pattern = f", PATTERN => {pattern}" if pattern else "" 2399 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2400 2401 ordinality = expression.args.get("ordinality") or "" 2402 if ordinality: 2403 ordinality = f" WITH ORDINALITY{alias}" 2404 alias = "" 2405 2406 when = self.sql(expression, "when") 2407 if when: 2408 table = f"{table} {when}" 2409 2410 changes = self.sql(expression, "changes") 2411 changes = f" {changes}" if changes else "" 2412 2413 rows_from = self.expressions(expression, key="rows_from") 2414 if rows_from: 2415 table = f"ROWS FROM {self.wrap(rows_from)}" 2416 2417 indexed = expression.args.get("indexed") 2418 if indexed is not None: 2419 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2420 else: 2421 indexed = "" 2422 2423 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2424 2425 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2426 table = self.func("TABLE", expression.this) 2427 alias = self.sql(expression, "alias") 2428 alias = f" AS {alias}" if alias else "" 2429 sample = self.sql(expression, "sample") 2430 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2431 joins = self.indent( 2432 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2433 ) 2434 return f"{table}{alias}{pivots}{sample}{joins}" 2435 2436 def tablesample_sql( 2437 self, 2438 expression: exp.TableSample, 2439 tablesample_keyword: str | None = None, 2440 ) -> str: 2441 method = self.sql(expression, "method") 2442 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2443 numerator = self.sql(expression, "bucket_numerator") 2444 denominator = self.sql(expression, "bucket_denominator") 2445 field = self.sql(expression, "bucket_field") 2446 field = f" ON {field}" if field else "" 2447 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2448 seed = self.sql(expression, "seed") 2449 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2450 2451 size = self.sql(expression, "size") 2452 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2453 size = f"{size} ROWS" 2454 2455 percent = self.sql(expression, "percent") 2456 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2457 percent = f"{percent} PERCENT" 2458 2459 expr = f"{bucket}{percent}{size}" 2460 if self.TABLESAMPLE_REQUIRES_PARENS: 2461 expr = f"({expr})" 2462 2463 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2464 2465 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2466 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2467 # the stored column name differs from the target dialect's natural output. 2468 columns = expression.args.get("columns") 2469 if not columns or len(expression.fields) != 1: 2470 return None 2471 2472 args = expression.args 2473 parser_cls = self.dialect.parser_class 2474 2475 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2476 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2477 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2478 2479 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2480 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2481 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2482 2483 if ( 2484 src_identify_pivot_strings == tgt_identify_pivot_strings 2485 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2486 and src_pivot_column_naming == tgt_pivot_column_naming 2487 ): 2488 return None 2489 2490 in_exprs = expression.fields[0].expressions 2491 step = len(columns) // len(in_exprs) 2492 2493 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2494 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2495 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2496 first_stored = columns[0].name 2497 2498 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2499 if not first_stored.startswith(first_base): 2500 return None 2501 2502 suffix = first_stored[len(first_base) :] 2503 2504 # Whether the target dialect would append an agg-name suffix for this pivot. 2505 # Spark single-agg uniquely drops the agg alias entirely. 2506 target_has_suffix = ( 2507 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2508 ) and any(a.alias for a in expression.expressions) 2509 source_has_suffix = suffix != "" 2510 2511 new_exprs: list[exp.Expression] = [] 2512 modified = False 2513 for val_idx, e in enumerate(in_exprs): 2514 if isinstance(e, exp.PivotAlias): 2515 new_exprs.append(e) 2516 continue 2517 2518 i = val_idx * step 2519 stored_full = columns[i].name 2520 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2521 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2522 2523 # Source had a suffix, but target won't apply one 2524 if source_has_suffix and not target_has_suffix: 2525 new_exprs.append( 2526 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2527 ) 2528 modified = True 2529 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2530 elif stored_value != target_value: 2531 new_exprs.append( 2532 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2533 ) 2534 modified = True 2535 else: 2536 new_exprs.append(e) 2537 2538 return new_exprs if modified else None 2539 2540 def pivot_sql(self, expression: exp.Pivot) -> str: 2541 expressions = self.expressions(expression, flat=True) 2542 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2543 2544 group = self.sql(expression, "group") 2545 2546 if expression.this: 2547 this = self.sql(expression, "this") 2548 if not expressions: 2549 sql = f"UNPIVOT {this}" 2550 else: 2551 on = f"{self.seg('ON')} {expressions}" 2552 into = self.sql(expression, "into") 2553 into = f"{self.seg('INTO')} {into}" if into else "" 2554 using = self.expressions(expression, key="using", flat=True) 2555 using = f"{self.seg('USING')} {using}" if using else "" 2556 sql = f"{direction} {this}{on}{into}{using}{group}" 2557 return self.prepend_ctes(expression, sql) 2558 2559 if not expression.unpivot: 2560 # Wrap IN-list values with explicit aliases where the target dialect would differ 2561 new_field_exprs = self._pivot_in_value_aliases(expression) 2562 if new_field_exprs is not None: 2563 expression.fields[0].set("expressions", new_field_exprs) 2564 2565 alias = self.sql(expression, "alias") 2566 alias = f" AS {alias}" if alias else "" 2567 2568 fields = self.expressions( 2569 expression, 2570 "fields", 2571 sep=" ", 2572 dynamic=True, 2573 new_line=True, 2574 skip_first=True, 2575 skip_last=True, 2576 ) 2577 2578 include_nulls = expression.args.get("include_nulls") 2579 if include_nulls is not None: 2580 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2581 else: 2582 nulls = "" 2583 2584 default_on_null = self.sql(expression, "default_on_null") 2585 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2586 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2587 return self.prepend_ctes(expression, sql) 2588 2589 def version_sql(self, expression: exp.Version) -> str: 2590 this = f"FOR {expression.name}" 2591 kind = expression.text("kind") 2592 expr = self.sql(expression, "expression") 2593 return f"{this} {kind} {expr}" 2594 2595 def tuple_sql(self, expression: exp.Tuple) -> str: 2596 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2597 2598 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2599 """ 2600 Returns (join_sql, from_sql) for UPDATE statements. 2601 - join_sql: placed after UPDATE table, before SET 2602 - from_sql: placed after SET clause (standard position) 2603 Dialects like MySQL need to convert FROM to JOIN syntax. 2604 """ 2605 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2606 return ("", self.sql(expression, "from_")) 2607 2608 # Qualify unqualified columns in SET clause with the target table 2609 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2610 target_table = expression.this 2611 if isinstance(target_table, exp.Table): 2612 target_name = exp.to_identifier(target_table.alias_or_name) 2613 for eq in expression.expressions: 2614 col = eq.this 2615 if isinstance(col, exp.Column) and not col.table: 2616 col.set("table", target_name) 2617 2618 table = from_expr.this 2619 if nested_joins := table.args.get("joins", []): 2620 table.set("joins", None) 2621 2622 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2623 for nested in nested_joins: 2624 if not nested.args.get("on") and not nested.args.get("using"): 2625 nested.set("on", exp.true()) 2626 join_sql += self.sql(nested) 2627 2628 return (join_sql, "") 2629 2630 def update_sql(self, expression: exp.Update) -> str: 2631 hint = self.sql(expression, "hint") 2632 this = self.sql(expression, "this") 2633 join_sql, from_sql = self._update_from_joins_sql(expression) 2634 set_sql = self.expressions(expression, flat=True) 2635 where_sql = self.sql(expression, "where") 2636 returning = self.sql(expression, "returning") 2637 order = self.sql(expression, "order") 2638 limit = self.sql(expression, "limit") 2639 if self.RETURNING_END: 2640 expression_sql = f"{from_sql}{where_sql}{returning}" 2641 else: 2642 expression_sql = f"{returning}{from_sql}{where_sql}" 2643 options = self.expressions(expression, key="options") 2644 options = f" OPTION({options})" if options else "" 2645 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2646 return self.prepend_ctes(expression, sql) 2647 2648 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2649 values_as_table = values_as_table and self.VALUES_AS_TABLE 2650 2651 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2652 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2653 args = self.expressions(expression) 2654 alias = self.sql(expression, "alias") 2655 values = f"VALUES{self.seg('')}{args}" 2656 values = ( 2657 f"({values})" 2658 if self.WRAP_DERIVED_VALUES 2659 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2660 else values 2661 ) 2662 values = self.query_modifiers(expression, values) 2663 return f"{values} AS {alias}" if alias else values 2664 2665 # Converts `VALUES...` expression into a series of select unions. 2666 alias_node = expression.args.get("alias") 2667 column_names = alias_node and alias_node.columns 2668 2669 selects: list[exp.Query] = [] 2670 2671 for i, tup in enumerate(expression.expressions): 2672 row = tup.expressions 2673 2674 if i == 0 and column_names: 2675 row = [ 2676 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2677 ] 2678 2679 selects.append(exp.Select(expressions=row)) 2680 2681 if self.pretty: 2682 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2683 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2684 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2685 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2686 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2687 2688 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2689 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2690 return f"({unions}){alias}" 2691 2692 def var_sql(self, expression: exp.Var) -> str: 2693 return self.sql(expression, "this") 2694 2695 @unsupported_args("expressions") 2696 def into_sql(self, expression: exp.Into) -> str: 2697 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2698 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2699 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2700 2701 def from_sql(self, expression: exp.From) -> str: 2702 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2703 2704 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2705 grouping_sets = self.expressions(expression, indent=False) 2706 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2707 2708 def rollup_sql(self, expression: exp.Rollup) -> str: 2709 expressions = self.expressions(expression, indent=False) 2710 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2711 2712 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2713 this = self.sql(expression, "this") 2714 2715 columns = self.expressions(expression, flat=True) 2716 2717 from_sql = self.sql(expression, "from_index") 2718 from_sql = f" FROM {from_sql}" if from_sql else "" 2719 2720 properties = expression.args.get("properties") 2721 properties_sql = ( 2722 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2723 ) 2724 2725 return f"{this}({columns}){from_sql}{properties_sql}" 2726 2727 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2728 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2729 2730 def cube_sql(self, expression: exp.Cube) -> str: 2731 expressions = self.expressions(expression, indent=False) 2732 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2733 2734 def group_sql(self, expression: exp.Group) -> str: 2735 group_by_all = expression.args.get("all") 2736 if group_by_all is True: 2737 modifier = " ALL" 2738 elif group_by_all is False: 2739 modifier = " DISTINCT" 2740 else: 2741 modifier = "" 2742 2743 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2744 2745 grouping_sets = self.expressions(expression, key="grouping_sets") 2746 cube = self.expressions(expression, key="cube") 2747 rollup = self.expressions(expression, key="rollup") 2748 2749 groupings = csv( 2750 self.seg(grouping_sets) if grouping_sets else "", 2751 self.seg(cube) if cube else "", 2752 self.seg(rollup) if rollup else "", 2753 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2754 sep=self.GROUPINGS_SEP, 2755 ) 2756 2757 if ( 2758 expression.expressions 2759 and groupings 2760 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2761 ): 2762 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2763 2764 return f"{group_by}{groupings}" 2765 2766 def having_sql(self, expression: exp.Having) -> str: 2767 this = self.indent(self.sql(expression, "this")) 2768 return f"{self.seg('HAVING')}{self.sep()}{this}" 2769 2770 def connect_sql(self, expression: exp.Connect) -> str: 2771 start = self.sql(expression, "start") 2772 start = self.seg(f"START WITH {start}") if start else "" 2773 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2774 connect = self.sql(expression, "connect") 2775 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2776 return start + connect 2777 2778 def prior_sql(self, expression: exp.Prior) -> str: 2779 return f"PRIOR {self.sql(expression, 'this')}" 2780 2781 def join_sql(self, expression: exp.Join) -> str: 2782 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2783 side = None 2784 else: 2785 side = expression.side 2786 2787 op_sql = " ".join( 2788 op 2789 for op in ( 2790 expression.method, 2791 "GLOBAL" if expression.args.get("global_") else None, 2792 side, 2793 expression.kind, 2794 expression.hint if self.JOIN_HINTS else None, 2795 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2796 ) 2797 if op 2798 ) 2799 match_cond = self.sql(expression, "match_condition") 2800 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2801 on_sql = self.sql(expression, "on") 2802 using = expression.args.get("using") 2803 2804 if not on_sql and using: 2805 on_sql = csv(*(self.sql(column) for column in using)) 2806 2807 this = expression.this 2808 this_sql = self.sql(this) 2809 2810 exprs = self.expressions(expression) 2811 if exprs: 2812 this_sql = f"{this_sql},{self.seg(exprs)}" 2813 2814 if on_sql: 2815 on_sql = self.indent(on_sql, skip_first=True) 2816 space = self.seg(" " * self.pad) if self.pretty else " " 2817 if using: 2818 on_sql = f"{space}USING ({on_sql})" 2819 else: 2820 on_sql = f"{space}ON {on_sql}" 2821 elif not op_sql: 2822 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2823 return f" {this_sql}" 2824 2825 return f", {this_sql}" 2826 2827 if op_sql != "STRAIGHT_JOIN": 2828 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2829 2830 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2831 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2832 2833 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2834 args = self.expressions(expression, flat=True) 2835 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2836 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2837 2838 def lateral_op(self, expression: exp.Lateral) -> str: 2839 cross_apply = expression.args.get("cross_apply") 2840 2841 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2842 if cross_apply is True: 2843 op = "INNER JOIN " 2844 elif cross_apply is False: 2845 op = "LEFT JOIN " 2846 else: 2847 op = "" 2848 2849 return f"{op}LATERAL" 2850 2851 def lateral_sql(self, expression: exp.Lateral) -> str: 2852 this = self.sql(expression, "this") 2853 2854 if expression.args.get("view"): 2855 alias = expression.args["alias"] 2856 columns = self.expressions(alias, key="columns", flat=True) 2857 table = f" {alias.name}" if alias.name else "" 2858 columns = f" AS {columns}" if columns else "" 2859 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2860 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2861 2862 alias = self.sql(expression, "alias") 2863 alias = f" AS {alias}" if alias else "" 2864 2865 ordinality = expression.args.get("ordinality") or "" 2866 if ordinality: 2867 ordinality = f" WITH ORDINALITY{alias}" 2868 alias = "" 2869 2870 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2871 2872 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2873 this = self.sql(expression, "this") 2874 2875 args = [ 2876 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2877 for e in (expression.args.get(k) for k in ("offset", "expression")) 2878 if e 2879 ] 2880 2881 args_sql = ", ".join(self.sql(e) for e in args) 2882 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2883 expressions = self.expressions(expression, flat=True) 2884 limit_options = self.sql(expression, "limit_options") 2885 expressions = f" BY {expressions}" if expressions else "" 2886 2887 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2888 2889 def offset_sql(self, expression: exp.Offset) -> str: 2890 this = self.sql(expression, "this") 2891 value = expression.expression 2892 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2893 expressions = self.expressions(expression, flat=True) 2894 expressions = f" BY {expressions}" if expressions else "" 2895 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2896 2897 def setitem_sql(self, expression: exp.SetItem) -> str: 2898 kind = self.sql(expression, "kind") 2899 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2900 kind = "" 2901 else: 2902 kind = f"{kind} " if kind else "" 2903 this = self.sql(expression, "this") 2904 expressions = self.expressions(expression) 2905 collate = self.sql(expression, "collate") 2906 collate = f" COLLATE {collate}" if collate else "" 2907 global_ = "GLOBAL " if expression.args.get("global_") else "" 2908 return f"{global_}{kind}{this}{expressions}{collate}" 2909 2910 def set_sql(self, expression: exp.Set) -> str: 2911 expressions = f" {self.expressions(expression, flat=True)}" 2912 tag = " TAG" if expression.args.get("tag") else "" 2913 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2914 2915 def queryband_sql(self, expression: exp.QueryBand) -> str: 2916 this = self.sql(expression, "this") 2917 update = " UPDATE" if expression.args.get("update") else "" 2918 scope = self.sql(expression, "scope") 2919 scope = f" FOR {scope}" if scope else "" 2920 2921 return f"QUERY_BAND = {this}{update}{scope}" 2922 2923 def pragma_sql(self, expression: exp.Pragma) -> str: 2924 return f"PRAGMA {self.sql(expression, 'this')}" 2925 2926 def lock_sql(self, expression: exp.Lock) -> str: 2927 if not self.LOCKING_READS_SUPPORTED: 2928 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2929 return "" 2930 2931 update = expression.args["update"] 2932 key = expression.args.get("key") 2933 if update: 2934 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2935 else: 2936 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2937 expressions = self.expressions(expression, flat=True) 2938 expressions = f" OF {expressions}" if expressions else "" 2939 wait = expression.args.get("wait") 2940 2941 if wait is not None: 2942 if isinstance(wait, exp.Literal): 2943 wait = f" WAIT {self.sql(wait)}" 2944 else: 2945 wait = " NOWAIT" if wait else " SKIP LOCKED" 2946 2947 return f"{lock_type}{expressions}{wait or ''}" 2948 2949 def literal_sql(self, expression: exp.Literal) -> str: 2950 text = expression.this or "" 2951 if expression.is_string: 2952 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2953 return text 2954 2955 def escape_str( 2956 self, 2957 text: str, 2958 escape_backslash: bool = True, 2959 delimiter: str | None = None, 2960 escaped_delimiter: str | None = None, 2961 is_byte_string: bool = False, 2962 ) -> str: 2963 if is_byte_string: 2964 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2965 else: 2966 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2967 2968 if supports_escape_sequences: 2969 text = "".join( 2970 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2971 for ch in text 2972 ) 2973 2974 delimiter = delimiter or self.dialect.QUOTE_END 2975 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2976 2977 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2978 2979 def loaddata_sql(self, expression: exp.LoadData) -> str: 2980 is_overwrite = expression.args.get("overwrite") 2981 overwrite = " OVERWRITE" if is_overwrite else "" 2982 this = self.sql(expression, "this") 2983 2984 files = expression.args.get("files") 2985 if files: 2986 files_sql = self.expressions(files, flat=True) 2987 files_sql = f"FILES{self.wrap(files_sql)}" 2988 if is_overwrite: 2989 this = f" {this}" 2990 elif expression.args.get("temp"): 2991 this = f" INTO TEMP TABLE {this}" 2992 else: 2993 this = f" INTO TABLE {this}" 2994 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2995 2996 local = " LOCAL" if expression.args.get("local") else "" 2997 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2998 this = f" INTO TABLE {this}" 2999 partition = self.sql(expression, "partition") 3000 partition = f" {partition}" if partition else "" 3001 input_format = self.sql(expression, "input_format") 3002 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3003 serde = self.sql(expression, "serde") 3004 serde = f" SERDE {serde}" if serde else "" 3005 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3006 3007 def null_sql(self, *_) -> str: 3008 return "NULL" 3009 3010 def boolean_sql(self, expression: exp.Boolean) -> str: 3011 return "TRUE" if expression.this else "FALSE" 3012 3013 def booland_sql(self, expression: exp.Booland) -> str: 3014 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3015 3016 def boolor_sql(self, expression: exp.Boolor) -> str: 3017 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3018 3019 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3020 this = self.sql(expression, "this") 3021 this = f"{this} " if this else this 3022 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3023 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3024 3025 def withfill_sql(self, expression: exp.WithFill) -> str: 3026 from_sql = self.sql(expression, "from_") 3027 from_sql = f" FROM {from_sql}" if from_sql else "" 3028 to_sql = self.sql(expression, "to") 3029 to_sql = f" TO {to_sql}" if to_sql else "" 3030 step_sql = self.sql(expression, "step") 3031 step_sql = f" STEP {step_sql}" if step_sql else "" 3032 interpolated_values = [ 3033 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3034 if isinstance(e, exp.Alias) 3035 else self.sql(e, "this") 3036 for e in expression.args.get("interpolate") or [] 3037 ] 3038 interpolate = ( 3039 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3040 ) 3041 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3042 3043 def cluster_sql(self, expression: exp.Cluster) -> str: 3044 return self.op_expressions("CLUSTER BY", expression) 3045 3046 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3047 if expression.this: 3048 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3049 return "" 3050 expressions = self.expressions(expression, flat=True) 3051 return f"CLUSTER BY ({expressions})" 3052 3053 def distribute_sql(self, expression: exp.Distribute) -> str: 3054 return self.op_expressions("DISTRIBUTE BY", expression) 3055 3056 def sort_sql(self, expression: exp.Sort) -> str: 3057 return self.op_expressions("SORT BY", expression) 3058 3059 def _resolve_ordered_for_null_ordering_simulation( 3060 self, expression: exp.Ordered 3061 ) -> exp.Expr | None: 3062 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3063 3064 Returns the underlying expression of the uniquely-matching projection 3065 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3066 simulation, since the CASE is evaluated in FROM-clause scope rather 3067 than alias scope (MySQL error 1052). Returns None if no safe 3068 substitution applies, leaving the original behaviour unchanged. 3069 """ 3070 this = expression.this 3071 if not (isinstance(this, exp.Column) and not this.table): 3072 return None 3073 3074 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3075 if not isinstance(ancestor, exp.Select): 3076 return None 3077 3078 column_name = this.name 3079 matched: list[exp.Expr] = [ 3080 p.this if isinstance(p, exp.Alias) else p 3081 for p in ancestor.selects 3082 if p.output_name == column_name 3083 ] 3084 match = matched[0] if len(matched) == 1 else None 3085 3086 # Skip the substitution when it would be identical to the existing 3087 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3088 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3089 return None 3090 3091 return match 3092 3093 def ordered_sql(self, expression: exp.Ordered) -> str: 3094 desc = expression.args.get("desc") 3095 asc = not desc 3096 3097 nulls_first = expression.args.get("nulls_first") 3098 nulls_last = not nulls_first 3099 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3100 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3101 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3102 3103 this = self.sql(expression, "this") 3104 3105 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3106 nulls_sort_change = "" 3107 if nulls_first and ( 3108 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3109 ): 3110 nulls_sort_change = " NULLS FIRST" 3111 elif ( 3112 nulls_last 3113 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3114 and not nulls_are_last 3115 ): 3116 nulls_sort_change = " NULLS LAST" 3117 3118 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3119 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3120 window = expression.find_ancestor(exp.Window, exp.Select) 3121 3122 if isinstance(window, exp.Window): 3123 window_this = window.this 3124 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3125 window_this = window_this.this 3126 spec = window.args.get("spec") 3127 else: 3128 window_this = None 3129 spec = None 3130 3131 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3132 # without a spec or with a ROWS spec, but not with RANGE 3133 if not ( 3134 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3135 and (not spec or spec.text("kind").upper() == "ROWS") 3136 ): 3137 if window_this and spec: 3138 self.unsupported( 3139 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3140 ) 3141 nulls_sort_change = "" 3142 elif self.NULL_ORDERING_SUPPORTED is False and ( 3143 (asc and nulls_sort_change == " NULLS LAST") 3144 or (desc and nulls_sort_change == " NULLS FIRST") 3145 ): 3146 # BigQuery does not allow these ordering/nulls combinations when used under 3147 # an aggregation func or under a window containing one 3148 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3149 3150 if isinstance(ancestor, exp.Window): 3151 ancestor = ancestor.this 3152 if isinstance(ancestor, exp.AggFunc): 3153 self.unsupported( 3154 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3155 ) 3156 nulls_sort_change = "" 3157 elif self.NULL_ORDERING_SUPPORTED is None: 3158 if expression.this.is_int: 3159 self.unsupported( 3160 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3161 ) 3162 elif not isinstance(expression.this, exp.Rand): 3163 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3164 target = self.sql(resolved) if resolved is not None else this 3165 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3166 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3167 nulls_sort_change = "" 3168 3169 with_fill = self.sql(expression, "with_fill") 3170 with_fill = f" {with_fill}" if with_fill else "" 3171 3172 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3173 3174 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3175 window_frame = self.sql(expression, "window_frame") 3176 window_frame = f"{window_frame} " if window_frame else "" 3177 3178 this = self.sql(expression, "this") 3179 3180 return f"{window_frame}{this}" 3181 3182 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3183 partition = self.partition_by_sql(expression) 3184 order = self.sql(expression, "order") 3185 measures = self.expressions(expression, key="measures") 3186 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3187 rows = self.sql(expression, "rows") 3188 rows = self.seg(rows) if rows else "" 3189 after = self.sql(expression, "after") 3190 after = self.seg(after) if after else "" 3191 pattern = self.sql(expression, "pattern") 3192 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3193 definition_sqls = [ 3194 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3195 for definition in expression.args.get("define", []) 3196 ] 3197 definitions = self.expressions(sqls=definition_sqls) 3198 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3199 body = "".join( 3200 ( 3201 partition, 3202 order, 3203 measures, 3204 rows, 3205 after, 3206 pattern, 3207 define, 3208 ) 3209 ) 3210 alias = self.sql(expression, "alias") 3211 alias = f" {alias}" if alias else "" 3212 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3213 3214 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3215 limit = expression.args.get("limit") 3216 3217 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3218 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3219 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3220 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3221 3222 return csv( 3223 *sqls, 3224 *[self.sql(join) for join in expression.args.get("joins") or []], 3225 self.sql(expression, "match"), 3226 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3227 self.sql(expression, "prewhere"), 3228 self.sql(expression, "where"), 3229 self.sql(expression, "connect"), 3230 self.sql(expression, "group"), 3231 self.sql(expression, "having"), 3232 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3233 self.sql(expression, "order"), 3234 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3235 *self.after_limit_modifiers(expression), 3236 self.options_modifier(expression), 3237 self.sql(expression, "for_"), 3238 sep="", 3239 ) 3240 3241 def options_modifier(self, expression: exp.Expr) -> str: 3242 options = self.expressions(expression, key="options") 3243 return f" {options}" if options else "" 3244 3245 def forclause_sql(self, expression: exp.ForClause) -> str: 3246 kind = expression.args["kind"] 3247 if kind == "BROWSE": 3248 return f"{self.sep()}FOR BROWSE" 3249 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3250 # the target dialect doesn't support QueryOption, so we drop the clause. 3251 options = self.expressions(expression, key="expressions") 3252 if not options: 3253 return "" 3254 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3255 3256 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3257 self.unsupported("Unsupported query option.") 3258 return "" 3259 3260 def offset_limit_modifiers( 3261 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3262 ) -> list[str]: 3263 return [ 3264 self.sql(expression, "offset") if fetch else self.sql(limit), 3265 self.sql(limit) if fetch else self.sql(expression, "offset"), 3266 ] 3267 3268 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3269 locks = self.expressions(expression, key="locks", sep=" ") 3270 locks = f" {locks}" if locks else "" 3271 return [locks, self.sql(expression, "sample")] 3272 3273 def select_sql(self, expression: exp.Select) -> str: 3274 into = expression.args.get("into") 3275 if not self.SUPPORTS_SELECT_INTO and into: 3276 into.pop() 3277 3278 hint = self.sql(expression, "hint") 3279 distinct = self.sql(expression, "distinct") 3280 distinct = f" {distinct}" if distinct else "" 3281 kind = self.sql(expression, "kind") 3282 3283 limit = expression.args.get("limit") 3284 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3285 top = self.limit_sql(limit, top=True) 3286 limit.pop() 3287 else: 3288 top = "" 3289 3290 expressions = self.expressions(expression) 3291 3292 if kind: 3293 if kind in self.SELECT_KINDS: 3294 kind = f" AS {kind}" 3295 else: 3296 if kind == "STRUCT": 3297 expressions = self.expressions( 3298 sqls=[ 3299 self.sql( 3300 exp.Struct( 3301 expressions=[ 3302 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3303 if isinstance(e, exp.Alias) 3304 else e 3305 for e in expression.expressions 3306 ] 3307 ) 3308 ) 3309 ] 3310 ) 3311 kind = "" 3312 3313 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3314 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3315 3316 exclude = expression.args.get("exclude") 3317 3318 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3319 exclude_sql = self.expressions(sqls=exclude, flat=True) 3320 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3321 3322 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3323 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3324 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3325 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3326 sql = self.query_modifiers( 3327 expression, 3328 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3329 self.sql(expression, "into", comment=False), 3330 self.sql(expression, "from_", comment=False), 3331 ) 3332 3333 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3334 if expression.args.get("with_"): 3335 sql = self.maybe_comment(sql, expression) 3336 expression.pop_comments() 3337 3338 sql = self.prepend_ctes(expression, sql) 3339 3340 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3341 expression.set("exclude", None) 3342 subquery = expression.subquery(copy=False) 3343 star = exp.Star(except_=exclude) 3344 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3345 3346 if not self.SUPPORTS_SELECT_INTO and into: 3347 if into.args.get("temporary"): 3348 table_kind = " TEMPORARY" 3349 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3350 table_kind = " UNLOGGED" 3351 else: 3352 table_kind = "" 3353 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3354 3355 return sql 3356 3357 def schema_sql(self, expression: exp.Schema) -> str: 3358 this = self.sql(expression, "this") 3359 sql = self.schema_columns_sql(expression) 3360 return f"{this} {sql}" if this and sql else this or sql 3361 3362 def schema_columns_sql(self, expression: exp.Expr) -> str: 3363 if expression.expressions: 3364 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3365 return "" 3366 3367 def star_sql(self, expression: exp.Star) -> str: 3368 except_ = self.expressions(expression, key="except_", flat=True) 3369 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3370 replace = self.expressions(expression, key="replace", flat=True) 3371 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3372 rename = self.expressions(expression, key="rename", flat=True) 3373 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3374 return f"*{except_}{replace}{rename}" 3375 3376 def parameter_sql(self, expression: exp.Parameter) -> str: 3377 this = self.sql(expression, "this") 3378 return f"{self.PARAMETER_TOKEN}{this}" 3379 3380 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3381 this = self.sql(expression, "this") 3382 kind = expression.text("kind") 3383 if kind: 3384 kind = f"{kind}." 3385 return f"@@{kind}{this}" 3386 3387 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3388 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3389 3390 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3391 alias = self.sql(expression, "alias") 3392 alias = f"{sep}{alias}" if alias else "" 3393 sample = self.sql(expression, "sample") 3394 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3395 alias = f"{sample}{alias}" 3396 3397 # Set to None so it's not generated again by self.query_modifiers() 3398 expression.set("sample", None) 3399 3400 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3401 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3402 return self.prepend_ctes(expression, sql) 3403 3404 def qualify_sql(self, expression: exp.Qualify) -> str: 3405 this = self.indent(self.sql(expression, "this")) 3406 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3407 3408 def unnest_sql(self, expression: exp.Unnest) -> str: 3409 args = self.expressions(expression, flat=True) 3410 3411 alias = expression.args.get("alias") 3412 offset = expression.args.get("offset") 3413 3414 if self.UNNEST_WITH_ORDINALITY: 3415 if alias and isinstance(offset, exp.Expr): 3416 alias.append("columns", offset) 3417 expression.set("offset", None) 3418 3419 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3420 columns = alias.columns 3421 alias = self.sql(columns[0]) if columns else "" 3422 else: 3423 alias = self.sql(alias) 3424 3425 alias = f" AS {alias}" if alias else alias 3426 if self.UNNEST_WITH_ORDINALITY: 3427 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3428 else: 3429 if isinstance(offset, exp.Expr): 3430 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3431 elif offset: 3432 suffix = f"{alias} WITH OFFSET" 3433 else: 3434 suffix = alias 3435 3436 return f"UNNEST({args}){suffix}" 3437 3438 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3439 return "" 3440 3441 def where_sql(self, expression: exp.Where) -> str: 3442 this = self.indent(self.sql(expression, "this")) 3443 return f"{self.seg('WHERE')}{self.sep()}{this}" 3444 3445 def window_sql(self, expression: exp.Window) -> str: 3446 this = self.sql(expression, "this") 3447 partition = self.partition_by_sql(expression) 3448 order = expression.args.get("order") 3449 order = self.order_sql(order, flat=True) if order else "" 3450 spec = self.sql(expression, "spec") 3451 alias = self.sql(expression, "alias") 3452 over = self.sql(expression, "over") or "OVER" 3453 3454 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3455 3456 first = expression.args.get("first") 3457 if first is None: 3458 first = "" 3459 else: 3460 first = "FIRST" if first else "LAST" 3461 3462 if not partition and not order and not spec and alias: 3463 return f"{this} {alias}" 3464 3465 args = self.format_args( 3466 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3467 ) 3468 return f"{this} ({args})" 3469 3470 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3471 partition = self.expressions(expression, key="partition_by", flat=True) 3472 return f"PARTITION BY {partition}" if partition else "" 3473 3474 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3475 kind = self.sql(expression, "kind") 3476 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3477 end = ( 3478 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3479 or "CURRENT ROW" 3480 ) 3481 3482 window_spec = f"{kind} BETWEEN {start} AND {end}" 3483 3484 exclude = self.sql(expression, "exclude") 3485 if exclude: 3486 if self.SUPPORTS_WINDOW_EXCLUDE: 3487 window_spec += f" EXCLUDE {exclude}" 3488 else: 3489 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3490 3491 return window_spec 3492 3493 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3494 this = self.sql(expression, "this") 3495 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3496 return f"{this} WITHIN GROUP ({expression_sql})" 3497 3498 def between_sql(self, expression: exp.Between) -> str: 3499 this = self.sql(expression, "this") 3500 low = self.sql(expression, "low") 3501 high = self.sql(expression, "high") 3502 symmetric = expression.args.get("symmetric") 3503 3504 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3505 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3506 3507 flag = ( 3508 " SYMMETRIC" 3509 if symmetric 3510 else " ASYMMETRIC" 3511 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3512 else "" # silently drop ASYMMETRIC – semantics identical 3513 ) 3514 return f"{this} BETWEEN{flag} {low} AND {high}" 3515 3516 def bracket_offset_expressions( 3517 self, expression: exp.Bracket, index_offset: int | None = None 3518 ) -> list[exp.Expr]: 3519 if expression.args.get("json_access"): 3520 return expression.expressions 3521 3522 return apply_index_offset( 3523 expression.this, 3524 expression.expressions, 3525 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3526 dialect=self.dialect, 3527 ) 3528 3529 def bracket_sql(self, expression: exp.Bracket) -> str: 3530 expressions = self.bracket_offset_expressions(expression) 3531 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3532 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3533 3534 def all_sql(self, expression: exp.All) -> str: 3535 this = self.sql(expression, "this") 3536 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3537 this = self.wrap(this) 3538 return f"ALL {this}" 3539 3540 def any_sql(self, expression: exp.Any) -> str: 3541 this = self.sql(expression, "this") 3542 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3543 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3544 this = self.wrap(this) 3545 return f"ANY{this}" 3546 return f"ANY {this}" 3547 3548 def exists_sql(self, expression: exp.Exists) -> str: 3549 return f"EXISTS{self.wrap(expression)}" 3550 3551 def case_sql(self, expression: exp.Case) -> str: 3552 this = self.sql(expression, "this") 3553 statements = [f"CASE {this}" if this else "CASE"] 3554 3555 for e in expression.args["ifs"]: 3556 statements.append(f"WHEN {self.sql(e, 'this')}") 3557 statements.append(f"THEN {self.sql(e, 'true')}") 3558 3559 default = self.sql(expression, "default") 3560 3561 if default: 3562 statements.append(f"ELSE {default}") 3563 3564 statements.append("END") 3565 3566 if self.pretty and self.too_wide(statements): 3567 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3568 3569 return " ".join(statements) 3570 3571 def constraint_sql(self, expression: exp.Constraint) -> str: 3572 this = self.sql(expression, "this") 3573 expressions = self.expressions(expression, flat=True) 3574 return f"CONSTRAINT {this} {expressions}" 3575 3576 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3577 order = expression.args.get("order") 3578 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3579 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3580 3581 def extract_sql(self, expression: exp.Extract) -> str: 3582 import sqlglot.dialects.dialect 3583 3584 this = ( 3585 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3586 if self.NORMALIZE_EXTRACT_DATE_PARTS 3587 else expression.this 3588 ) 3589 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3590 expression_sql = self.sql(expression, "expression") 3591 3592 return f"EXTRACT({this_sql} FROM {expression_sql})" 3593 3594 def trim_sql(self, expression: exp.Trim) -> str: 3595 trim_type = self.sql(expression, "position") 3596 3597 if trim_type == "LEADING": 3598 func_name = "LTRIM" 3599 elif trim_type == "TRAILING": 3600 func_name = "RTRIM" 3601 else: 3602 func_name = "TRIM" 3603 3604 return self.func(func_name, expression.this, expression.expression) 3605 3606 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3607 args = expression.expressions 3608 if isinstance(expression, exp.ConcatWs): 3609 args = args[1:] # Skip the delimiter 3610 3611 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3612 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3613 3614 concat_coalesce = ( 3615 self.dialect.CONCAT_WS_COALESCE 3616 if isinstance(expression, exp.ConcatWs) 3617 else self.dialect.CONCAT_COALESCE 3618 ) 3619 3620 if not concat_coalesce and expression.args.get("coalesce"): 3621 3622 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3623 if not e.type: 3624 import sqlglot.optimizer.annotate_types 3625 3626 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3627 3628 if e.is_string or e.is_type(exp.DType.ARRAY): 3629 return e 3630 3631 return exp.func("coalesce", e, exp.Literal.string("")) 3632 3633 args = [_wrap_with_coalesce(e) for e in args] 3634 3635 return args 3636 3637 def concat_sql(self, expression: exp.Concat) -> str: 3638 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3639 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3640 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3641 # instead of coalescing them to empty string. 3642 import sqlglot.dialects.dialect 3643 3644 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3645 3646 expressions = self.convert_concat_args(expression) 3647 3648 # Some dialects don't allow a single-argument CONCAT call 3649 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3650 return self.sql(expressions[0]) 3651 3652 return self.func("CONCAT", *expressions) 3653 3654 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3655 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3656 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3657 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3658 all_args = expression.expressions 3659 expression.set("coalesce", True) 3660 return self.sql( 3661 exp.case() 3662 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3663 .else_(expression) 3664 ) 3665 3666 return self.func( 3667 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3668 ) 3669 3670 def check_sql(self, expression: exp.Check) -> str: 3671 this = self.sql(expression, key="this") 3672 return f"CHECK ({this})" 3673 3674 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3675 expressions = self.expressions(expression, flat=True) 3676 expressions = f" ({expressions})" if expressions else "" 3677 reference = self.sql(expression, "reference") 3678 reference = f" {reference}" if reference else "" 3679 delete = self.sql(expression, "delete") 3680 delete = f" ON DELETE {delete}" if delete else "" 3681 update = self.sql(expression, "update") 3682 update = f" ON UPDATE {update}" if update else "" 3683 options = self.expressions(expression, key="options", flat=True, sep=" ") 3684 options = f" {options}" if options else "" 3685 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3686 3687 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3688 this = self.sql(expression, "this") 3689 this = f" {this}" if this else "" 3690 expressions = self.expressions(expression, flat=True) 3691 include = self.sql(expression, "include") 3692 options = self.expressions(expression, key="options", flat=True, sep=" ") 3693 options = f" {options}" if options else "" 3694 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3695 3696 def if_sql(self, expression: exp.If) -> str: 3697 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3698 3699 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3700 if self.MATCH_AGAINST_TABLE_PREFIX: 3701 expressions = [] 3702 for expr in expression.expressions: 3703 if isinstance(expr, exp.Table): 3704 expressions.append(f"TABLE {self.sql(expr)}") 3705 else: 3706 expressions.append(expr) 3707 else: 3708 expressions = expression.expressions 3709 3710 modifier = expression.args.get("modifier") 3711 modifier = f" {modifier}" if modifier else "" 3712 return ( 3713 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3714 ) 3715 3716 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3717 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3718 3719 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3720 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3721 3722 if expression.args.get("escape"): 3723 path = self.escape_str(path) 3724 3725 if self.QUOTE_JSON_PATH: 3726 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3727 3728 return path 3729 3730 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3731 if isinstance(expression, exp.JSONPathPart): 3732 transform = self.TRANSFORMS.get(expression.__class__) 3733 if not callable(transform): 3734 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3735 return "" 3736 3737 return transform(self, expression) 3738 3739 if isinstance(expression, int): 3740 return str(expression) 3741 3742 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3743 escaped = expression.replace("'", "\\'") 3744 escaped = f"\\'{expression}\\'" 3745 else: 3746 escaped = expression.replace('"', '\\"') 3747 escaped = f'"{escaped}"' 3748 3749 return escaped 3750 3751 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3752 return f"{self.sql(expression, 'this')} FORMAT JSON" 3753 3754 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3755 # Output the Teradata column FORMAT override. 3756 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3757 this = self.sql(expression, "this") 3758 fmt = self.sql(expression, "format") 3759 return f"{this} (FORMAT {fmt})" 3760 3761 def _jsonobject_sql( 3762 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3763 ) -> str: 3764 null_handling = expression.args.get("null_handling") 3765 null_handling = f" {null_handling}" if null_handling else "" 3766 3767 unique_keys = expression.args.get("unique_keys") 3768 if unique_keys is not None: 3769 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3770 else: 3771 unique_keys = "" 3772 3773 return_type = self.sql(expression, "return_type") 3774 return_type = f" RETURNING {return_type}" if return_type else "" 3775 encoding = self.sql(expression, "encoding") 3776 encoding = f" ENCODING {encoding}" if encoding else "" 3777 3778 if not name: 3779 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3780 3781 return self.func( 3782 name, 3783 *expression.expressions, 3784 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3785 ) 3786 3787 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3788 null_handling = expression.args.get("null_handling") 3789 null_handling = f" {null_handling}" if null_handling else "" 3790 return_type = self.sql(expression, "return_type") 3791 return_type = f" RETURNING {return_type}" if return_type else "" 3792 strict = " STRICT" if expression.args.get("strict") else "" 3793 return self.func( 3794 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3795 ) 3796 3797 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3798 this = self.sql(expression, "this") 3799 order = self.sql(expression, "order") 3800 null_handling = expression.args.get("null_handling") 3801 null_handling = f" {null_handling}" if null_handling else "" 3802 return_type = self.sql(expression, "return_type") 3803 return_type = f" RETURNING {return_type}" if return_type else "" 3804 strict = " STRICT" if expression.args.get("strict") else "" 3805 return self.func( 3806 "JSON_ARRAYAGG", 3807 this, 3808 suffix=f"{order}{null_handling}{return_type}{strict})", 3809 ) 3810 3811 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3812 path = self.sql(expression, "path") 3813 path = f" PATH {path}" if path else "" 3814 nested_schema = self.sql(expression, "nested_schema") 3815 3816 if nested_schema: 3817 return f"NESTED{path} {nested_schema}" 3818 3819 this = self.sql(expression, "this") 3820 kind = self.sql(expression, "kind") 3821 kind = f" {kind}" if kind else "" 3822 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3823 3824 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3825 return f"{this}{kind}{format_json}{path}{ordinality}" 3826 3827 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3828 return self.func("COLUMNS", *expression.expressions) 3829 3830 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3831 this = self.sql(expression, "this") 3832 path = self.sql(expression, "path") 3833 path = f", {path}" if path else "" 3834 error_handling = expression.args.get("error_handling") 3835 error_handling = f" {error_handling}" if error_handling else "" 3836 empty_handling = expression.args.get("empty_handling") 3837 empty_handling = f" {empty_handling}" if empty_handling else "" 3838 schema = self.sql(expression, "schema") 3839 return self.func( 3840 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3841 ) 3842 3843 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3844 this = self.sql(expression, "this") 3845 kind = self.sql(expression, "kind") 3846 path = self.sql(expression, "path") 3847 path = f" {path}" if path else "" 3848 as_json = " AS JSON" if expression.args.get("as_json") else "" 3849 return f"{this} {kind}{path}{as_json}" 3850 3851 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3852 this = self.sql(expression, "this") 3853 path = self.sql(expression, "path") 3854 path = f", {path}" if path else "" 3855 expressions = self.expressions(expression) 3856 with_ = ( 3857 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3858 if expressions 3859 else "" 3860 ) 3861 return f"OPENJSON({this}{path}){with_}" 3862 3863 def in_sql(self, expression: exp.In) -> str: 3864 query = expression.args.get("query") 3865 unnest = expression.args.get("unnest") 3866 field = expression.args.get("field") 3867 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3868 3869 if query: 3870 in_sql = self.sql(query) 3871 elif unnest: 3872 in_sql = self.in_unnest_op(unnest) 3873 elif field: 3874 in_sql = self.sql(field) 3875 else: 3876 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3877 3878 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3879 3880 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3881 return f"(SELECT {self.sql(unnest)})" 3882 3883 def interval_sql(self, expression: exp.Interval) -> str: 3884 unit_expression = expression.args.get("unit") 3885 unit = self.sql(unit_expression) if unit_expression else "" 3886 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3887 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3888 unit = f" {unit}" if unit else "" 3889 3890 if self.SINGLE_STRING_INTERVAL: 3891 this = expression.this.name if expression.this else "" 3892 if this: 3893 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3894 return f"INTERVAL '{this}'{unit}" 3895 return f"INTERVAL '{this}{unit}'" 3896 return f"INTERVAL{unit}" 3897 3898 this = self.sql(expression, "this") 3899 if this: 3900 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3901 this = f" {this}" if unwrapped else f" ({this})" 3902 3903 return f"INTERVAL{this}{unit}" 3904 3905 def return_sql(self, expression: exp.Return) -> str: 3906 return f"RETURN {self.sql(expression, 'this')}" 3907 3908 def reference_sql(self, expression: exp.Reference) -> str: 3909 this = self.sql(expression, "this") 3910 expressions = self.expressions(expression, flat=True) 3911 expressions = f"({expressions})" if expressions else "" 3912 options = self.expressions(expression, key="options", flat=True, sep=" ") 3913 options = f" {options}" if options else "" 3914 return f"REFERENCES {this}{expressions}{options}" 3915 3916 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3917 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3918 parent = expression.parent 3919 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3920 3921 return self.func( 3922 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3923 ) 3924 3925 def paren_sql(self, expression: exp.Paren) -> str: 3926 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3927 return f"({sql}{self.seg(')', sep='')}" 3928 3929 def neg_sql(self, expression: exp.Neg) -> str: 3930 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3931 this_sql = self.sql(expression, "this") 3932 sep = " " if this_sql[0] == "-" else "" 3933 return f"-{sep}{this_sql}" 3934 3935 def not_sql(self, expression: exp.Not) -> str: 3936 return f"NOT {self.sql(expression, 'this')}" 3937 3938 def alias_sql(self, expression: exp.Alias) -> str: 3939 alias = self.sql(expression, "alias") 3940 alias = f" AS {alias}" if alias else "" 3941 return f"{self.sql(expression, 'this')}{alias}" 3942 3943 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3944 alias = expression.args["alias"] 3945 3946 parent = expression.parent 3947 pivot = parent and parent.parent 3948 3949 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3950 identifier_alias = isinstance(alias, exp.Identifier) 3951 literal_alias = isinstance(alias, exp.Literal) 3952 3953 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3954 alias.replace(exp.Literal.string(alias.output_name)) 3955 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3956 alias.replace(exp.to_identifier(alias.output_name)) 3957 3958 return self.alias_sql(expression) 3959 3960 def aliases_sql(self, expression: exp.Aliases) -> str: 3961 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3962 3963 def atindex_sql(self, expression: exp.AtIndex) -> str: 3964 this = self.sql(expression, "this") 3965 index = self.sql(expression, "expression") 3966 return f"{this} AT {index}" 3967 3968 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3969 this = self.sql(expression, "this") 3970 zone = self.sql(expression, "zone") 3971 return f"{this} AT TIME ZONE {zone}" 3972 3973 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3974 this = self.sql(expression, "this") 3975 zone = self.sql(expression, "zone") 3976 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3977 3978 def add_sql(self, expression: exp.Add) -> str: 3979 return self.binary(expression, "+") 3980 3981 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 3982 return self.connector_sql(expression, "AND", stack) 3983 3984 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 3985 return self.connector_sql(expression, "OR", stack) 3986 3987 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 3988 return self.connector_sql(expression, "XOR", stack) 3989 3990 def connector_sql( 3991 self, 3992 expression: exp.Connector, 3993 op: str, 3994 stack: list[str | exp.Expr] | None = None, 3995 ) -> str: 3996 if stack is not None: 3997 if expression.expressions: 3998 stack.append(self.expressions(expression, sep=f" {op} ")) 3999 else: 4000 stack.append(expression.right) 4001 if expression.comments and self.comments: 4002 op = self.maybe_comment(op, comments=expression.comments) 4003 stack.extend((op, expression.left)) 4004 return op 4005 4006 stack = [expression] 4007 sqls: list[str] = [] 4008 ops = set() 4009 4010 while stack: 4011 node = stack.pop() 4012 if isinstance(node, exp.Connector): 4013 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4014 else: 4015 sql = self.sql(node) 4016 if sqls and sqls[-1] in ops: 4017 sqls[-1] += f" {sql}" 4018 else: 4019 sqls.append(sql) 4020 4021 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4022 return sep.join(sqls) 4023 4024 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4025 return self.binary(expression, "&") 4026 4027 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4028 return self.binary(expression, "<<") 4029 4030 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4031 return f"~{self.sql(expression, 'this')}" 4032 4033 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4034 return self.binary(expression, "|") 4035 4036 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4037 return self.binary(expression, ">>") 4038 4039 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4040 return self.binary(expression, "^") 4041 4042 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4043 format_sql = self.sql(expression, "format") 4044 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4045 to_sql = self.sql(expression, "to") 4046 to_sql = f" {to_sql}" if to_sql else "" 4047 action = self.sql(expression, "action") 4048 action = f" {action}" if action else "" 4049 default = self.sql(expression, "default") 4050 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4051 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4052 4053 # Base implementation that excludes safe, zone, and target_type metadata args 4054 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4055 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4056 4057 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4058 zone = self.sql(expression, "this") 4059 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4060 4061 def collate_sql(self, expression: exp.Collate) -> str: 4062 if self.COLLATE_IS_FUNC: 4063 return self.function_fallback_sql(expression) 4064 return self.binary(expression, "COLLATE") 4065 4066 def command_sql(self, expression: exp.Command) -> str: 4067 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4068 4069 def comment_sql(self, expression: exp.Comment) -> str: 4070 this = self.sql(expression, "this") 4071 kind = expression.args["kind"] 4072 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4073 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4074 expression_sql = self.sql(expression, "expression") 4075 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4076 4077 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4078 this = self.sql(expression, "this") 4079 delete = " DELETE" if expression.args.get("delete") else "" 4080 recompress = self.sql(expression, "recompress") 4081 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4082 to_disk = self.sql(expression, "to_disk") 4083 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4084 to_volume = self.sql(expression, "to_volume") 4085 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4086 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4087 4088 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4089 where = self.sql(expression, "where") 4090 group = self.sql(expression, "group") 4091 aggregates = self.expressions(expression, key="aggregates") 4092 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4093 4094 if not (where or group or aggregates) and len(expression.expressions) == 1: 4095 return f"TTL {self.expressions(expression, flat=True)}" 4096 4097 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4098 4099 def transaction_sql(self, expression: exp.Transaction) -> str: 4100 modes = self.expressions(expression, key="modes") 4101 modes = f" {modes}" if modes else "" 4102 return f"BEGIN{modes}" 4103 4104 def commit_sql(self, expression: exp.Commit) -> str: 4105 chain = expression.args.get("chain") 4106 if chain is not None: 4107 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4108 4109 return f"COMMIT{chain or ''}" 4110 4111 def rollback_sql(self, expression: exp.Rollback) -> str: 4112 savepoint = expression.args.get("savepoint") 4113 savepoint = f" TO {savepoint}" if savepoint else "" 4114 return f"ROLLBACK{savepoint}" 4115 4116 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4117 this = self.sql(expression, "this") 4118 4119 dtype = self.sql(expression, "dtype") 4120 if dtype: 4121 collate = self.sql(expression, "collate") 4122 collate = f" COLLATE {collate}" if collate else "" 4123 using = self.sql(expression, "using") 4124 using = f" USING {using}" if using else "" 4125 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4126 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 4127 4128 default = self.sql(expression, "default") 4129 if default: 4130 return f"ALTER COLUMN {this} SET DEFAULT {default}" 4131 4132 comment = self.sql(expression, "comment") 4133 if comment: 4134 return f"ALTER COLUMN {this} COMMENT {comment}" 4135 4136 visible = expression.args.get("visible") 4137 if visible: 4138 return f"ALTER COLUMN {this} SET {visible}" 4139 4140 allow_null = expression.args.get("allow_null") 4141 drop = expression.args.get("drop") 4142 4143 if not drop and not allow_null: 4144 self.unsupported("Unsupported ALTER COLUMN syntax") 4145 4146 if allow_null is not None: 4147 keyword = "DROP" if drop else "SET" 4148 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4149 4150 return f"ALTER COLUMN {this} DROP DEFAULT" 4151 4152 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4153 this = self.sql(expression, "this") 4154 rename_from = self.sql(expression, "rename_from") 4155 if rename_from: 4156 if not self.SUPPORTS_CHANGE_COLUMN: 4157 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4158 return f"CHANGE COLUMN {rename_from} {this}" 4159 if not self.SUPPORTS_MODIFY_COLUMN: 4160 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4161 return f"MODIFY COLUMN {this}" 4162 4163 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4164 this = self.sql(expression, "this") 4165 4166 visible = expression.args.get("visible") 4167 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4168 4169 return f"ALTER INDEX {this} {visible_sql}" 4170 4171 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4172 this = self.sql(expression, "this") 4173 if not isinstance(expression.this, exp.Var): 4174 this = f"KEY DISTKEY {this}" 4175 return f"ALTER DISTSTYLE {this}" 4176 4177 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4178 compound = " COMPOUND" if expression.args.get("compound") else "" 4179 this = self.sql(expression, "this") 4180 expressions = self.expressions(expression, flat=True) 4181 expressions = f"({expressions})" if expressions else "" 4182 return f"ALTER{compound} SORTKEY {this or expressions}" 4183 4184 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4185 if not self.RENAME_TABLE_WITH_DB: 4186 # Remove db from tables 4187 expression = expression.transform( 4188 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4189 ).assert_is(exp.AlterRename) 4190 this = self.sql(expression, "this") 4191 to_kw = " TO" if include_to else "" 4192 return f"RENAME{to_kw} {this}" 4193 4194 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4195 exists = " IF EXISTS" if expression.args.get("exists") else "" 4196 old_column = self.sql(expression, "this") 4197 new_column = self.sql(expression, "to") 4198 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4199 4200 def alterset_sql(self, expression: exp.AlterSet) -> str: 4201 exprs = self.expressions(expression, flat=True) 4202 if self.ALTER_SET_WRAPPED: 4203 exprs = f"({exprs})" 4204 4205 return f"SET {exprs}" 4206 4207 def alter_sql(self, expression: exp.Alter) -> str: 4208 actions = expression.args["actions"] 4209 4210 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4211 actions[0], exp.ColumnDef 4212 ): 4213 actions_sql = self.expressions(expression, key="actions", flat=True) 4214 actions_sql = f"ADD {actions_sql}" 4215 else: 4216 actions_list = [] 4217 for action in actions: 4218 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4219 action_sql = self.add_column_sql(action) 4220 else: 4221 action_sql = self.sql(action) 4222 if isinstance(action, exp.Query): 4223 action_sql = f"AS {action_sql}" 4224 4225 actions_list.append(action_sql) 4226 4227 actions_sql = self.format_args(*actions_list).lstrip("\n") 4228 4229 iceberg = ( 4230 "ICEBERG " 4231 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4232 else "" 4233 ) 4234 exists = " IF EXISTS" if expression.args.get("exists") else "" 4235 on_cluster = self.sql(expression, "cluster") 4236 on_cluster = f" {on_cluster}" if on_cluster else "" 4237 only = " ONLY" if expression.args.get("only") else "" 4238 options = self.expressions(expression, key="options") 4239 options = f", {options}" if options else "" 4240 kind = self.sql(expression, "kind") 4241 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4242 check = " WITH CHECK" if expression.args.get("check") else "" 4243 cascade = ( 4244 " CASCADE" 4245 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4246 else "" 4247 ) 4248 this = self.sql(expression, "this") 4249 this = f" {this}" if this else "" 4250 4251 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4252 4253 def altersession_sql(self, expression: exp.AlterSession) -> str: 4254 items_sql = self.expressions(expression, flat=True) 4255 keyword = "UNSET" if expression.args.get("unset") else "SET" 4256 return f"{keyword} {items_sql}" 4257 4258 def add_column_sql(self, expression: exp.Expr) -> str: 4259 sql = self.sql(expression) 4260 if isinstance(expression, exp.Schema): 4261 column_text = " COLUMNS" 4262 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4263 column_text = " COLUMN" 4264 else: 4265 column_text = "" 4266 4267 return f"ADD{column_text} {sql}" 4268 4269 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4270 expressions = self.expressions(expression) 4271 exists = " IF EXISTS " if expression.args.get("exists") else " " 4272 return f"DROP{exists}{expressions}" 4273 4274 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4275 return "DROP PRIMARY KEY" 4276 4277 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4278 return f"ADD {self.expressions(expression, indent=False)}" 4279 4280 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4281 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4282 location = self.sql(expression, "location") 4283 location = f" {location}" if location else "" 4284 return f"ADD {exists}{self.sql(expression.this)}{location}" 4285 4286 def distinct_sql(self, expression: exp.Distinct) -> str: 4287 this = self.expressions(expression, flat=True) 4288 4289 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4290 case = exp.case() 4291 for arg in expression.expressions: 4292 case = case.when(arg.is_(exp.null()), exp.null()) 4293 this = self.sql(case.else_(f"({this})")) 4294 4295 this = f" {this}" if this else "" 4296 4297 on = self.sql(expression, "on") 4298 on = f" ON {on}" if on else "" 4299 return f"DISTINCT{this}{on}" 4300 4301 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4302 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4303 4304 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4305 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4306 4307 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4308 this_sql = self.sql(expression, "this") 4309 expression_sql = self.sql(expression, "expression") 4310 kind = "MAX" if expression.args.get("max") else "MIN" 4311 return f"{this_sql} HAVING {kind} {expression_sql}" 4312 4313 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4314 return self.sql( 4315 exp.Cast( 4316 this=exp.Div(this=expression.this, expression=expression.expression), 4317 to=exp.DataType(this=exp.DType.INT), 4318 ) 4319 ) 4320 4321 def dpipe_sql(self, expression: exp.DPipe) -> str: 4322 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4323 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4324 return self.binary(expression, "||") 4325 4326 def div_sql(self, expression: exp.Div) -> str: 4327 l, r = expression.left, expression.right 4328 4329 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4330 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4331 4332 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4333 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4334 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4335 4336 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4337 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4338 return self.sql( 4339 exp.cast( 4340 l / r, 4341 to=exp.DType.BIGINT, 4342 ) 4343 ) 4344 4345 return self.binary(expression, "/") 4346 4347 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4348 n = exp._wrap(expression.this, exp.Binary) 4349 d = exp._wrap(expression.expression, exp.Binary) 4350 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4351 4352 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4353 return self.binary(expression, "OVERLAPS") 4354 4355 def distance_sql(self, expression: exp.Distance) -> str: 4356 return self.binary(expression, "<->") 4357 4358 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4359 return self.binary(expression, "<<->>") 4360 4361 def dot_sql(self, expression: exp.Dot) -> str: 4362 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4363 4364 def eq_sql(self, expression: exp.EQ) -> str: 4365 return self.binary(expression, "=") 4366 4367 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4368 return self.binary(expression, ":=") 4369 4370 def escape_sql(self, expression: exp.Escape) -> str: 4371 this = expression.this 4372 if ( 4373 isinstance(this, (exp.Like, exp.ILike)) 4374 and isinstance(this.expression, (exp.All, exp.Any)) 4375 and not self.SUPPORTS_LIKE_QUANTIFIERS 4376 ): 4377 return self._like_sql(this, escape=expression) 4378 return self.binary(expression, "ESCAPE") 4379 4380 def glob_sql(self, expression: exp.Glob) -> str: 4381 return self.binary(expression, "GLOB") 4382 4383 def gt_sql(self, expression: exp.GT) -> str: 4384 return self.binary(expression, ">") 4385 4386 def gte_sql(self, expression: exp.GTE) -> str: 4387 return self.binary(expression, ">=") 4388 4389 def is_sql(self, expression: exp.Is) -> str: 4390 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4391 return self.sql( 4392 expression.this if expression.expression.this else exp.not_(expression.this) 4393 ) 4394 return self.binary(expression, "IS") 4395 4396 def _like_sql( 4397 self, 4398 expression: exp.Like | exp.ILike, 4399 escape: exp.Escape | None = None, 4400 ) -> str: 4401 this = expression.this 4402 rhs = expression.expression 4403 4404 if isinstance(expression, exp.Like): 4405 exp_class: type[exp.Like | exp.ILike] = exp.Like 4406 op = "LIKE" 4407 else: 4408 exp_class = exp.ILike 4409 op = "ILIKE" 4410 4411 if expression.args.get("negate"): 4412 op = f"NOT {op}" 4413 4414 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4415 exprs = rhs.this.unnest() 4416 4417 if isinstance(exprs, exp.Tuple): 4418 exprs = exprs.expressions 4419 else: 4420 exprs = [exprs] 4421 4422 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4423 4424 def _make_like(expr: exp.Expression) -> exp.Expression: 4425 like: exp.Expression = exp_class( 4426 this=this, expression=expr, negate=expression.args.get("negate") 4427 ) 4428 if escape: 4429 like = exp.Escape(this=like, expression=escape.expression.copy()) 4430 return like 4431 4432 like_expr: exp.Expr = _make_like(exprs[0]) 4433 for expr in exprs[1:]: 4434 like_expr = connective(like_expr, _make_like(expr), copy=False) 4435 4436 parent = escape.parent if escape else expression.parent 4437 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4438 parent, exp.Condition 4439 ): 4440 like_expr = exp.paren(like_expr, copy=False) 4441 4442 return self.sql(like_expr) 4443 4444 return self.binary(expression, op) 4445 4446 def like_sql(self, expression: exp.Like) -> str: 4447 return self._like_sql(expression) 4448 4449 def ilike_sql(self, expression: exp.ILike) -> str: 4450 return self._like_sql(expression) 4451 4452 def match_sql(self, expression: exp.Match) -> str: 4453 return self.binary(expression, "MATCH") 4454 4455 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4456 return self.binary(expression, "SIMILAR TO") 4457 4458 def lt_sql(self, expression: exp.LT) -> str: 4459 return self.binary(expression, "<") 4460 4461 def lte_sql(self, expression: exp.LTE) -> str: 4462 return self.binary(expression, "<=") 4463 4464 def mod_sql(self, expression: exp.Mod) -> str: 4465 return self.binary(expression, "%") 4466 4467 def mul_sql(self, expression: exp.Mul) -> str: 4468 return self.binary(expression, "*") 4469 4470 def neq_sql(self, expression: exp.NEQ) -> str: 4471 return self.binary(expression, "<>") 4472 4473 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4474 return self.binary(expression, "IS NOT DISTINCT FROM") 4475 4476 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4477 return self.binary(expression, "IS DISTINCT FROM") 4478 4479 def sub_sql(self, expression: exp.Sub) -> str: 4480 return self.binary(expression, "-") 4481 4482 def trycast_sql(self, expression: exp.TryCast) -> str: 4483 return self.cast_sql(expression, safe_prefix="TRY_") 4484 4485 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4486 return self.cast_sql(expression) 4487 4488 def try_sql(self, expression: exp.Try) -> str: 4489 if not self.TRY_SUPPORTED: 4490 self.unsupported("Unsupported TRY function") 4491 return self.sql(expression, "this") 4492 4493 return self.func("TRY", expression.this) 4494 4495 def log_sql(self, expression: exp.Log) -> str: 4496 this = expression.this 4497 expr = expression.expression 4498 4499 if self.dialect.LOG_BASE_FIRST is False: 4500 this, expr = expr, this 4501 elif self.dialect.LOG_BASE_FIRST is None and expr: 4502 if this.name in ("2", "10"): 4503 return self.func(f"LOG{this.name}", expr) 4504 4505 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4506 4507 return self.func("LOG", this, expr) 4508 4509 def use_sql(self, expression: exp.Use) -> str: 4510 kind = self.sql(expression, "kind") 4511 kind = f" {kind}" if kind else "" 4512 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4513 this = f" {this}" if this else "" 4514 return f"USE{kind}{this}" 4515 4516 def binary(self, expression: exp.Binary, op: str) -> str: 4517 sqls: list[str] = [] 4518 stack: list[None | str | exp.Expr] = [expression] 4519 binary_type = type(expression) 4520 4521 while stack: 4522 node = stack.pop() 4523 4524 if type(node) is binary_type: 4525 op_func = node.args.get("operator") 4526 if op_func: 4527 op = f"OPERATOR({self.sql(op_func)})" 4528 4529 stack.append(node.args.get("expression")) 4530 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4531 stack.append(node.args.get("this")) 4532 else: 4533 sqls.append(self.sql(node)) 4534 4535 return "".join(sqls) 4536 4537 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4538 to_clause = self.sql(expression, "to") 4539 if to_clause: 4540 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4541 4542 return self.function_fallback_sql(expression) 4543 4544 def function_fallback_sql(self, expression: exp.Func) -> str: 4545 args = [] 4546 4547 for key in expression.arg_types: 4548 arg_value = expression.args.get(key) 4549 4550 if isinstance(arg_value, list): 4551 for value in arg_value: 4552 args.append(value) 4553 elif arg_value is not None: 4554 args.append(arg_value) 4555 4556 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4557 name = expression.meta_get("name") or expression.sql_name() 4558 else: 4559 name = expression.sql_name() 4560 4561 return self.func(name, *args) 4562 4563 def func( 4564 self, 4565 name: str, 4566 *args: t.Any, 4567 prefix: str = "(", 4568 suffix: str = ")", 4569 normalize: bool = True, 4570 ) -> str: 4571 name = self.normalize_func(name) if normalize else name 4572 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4573 4574 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4575 arg_sqls = tuple( 4576 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4577 ) 4578 if self.pretty and self.too_wide(arg_sqls): 4579 return self.indent( 4580 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4581 ) 4582 return sep.join(arg_sqls) 4583 4584 def too_wide(self, args: t.Iterable) -> bool: 4585 return sum(len(arg) for arg in args) > self.max_text_width 4586 4587 def format_time( 4588 self, 4589 expression: exp.Expr, 4590 inverse_time_mapping: dict[str, str] | None = None, 4591 inverse_time_trie: dict | None = None, 4592 ) -> str | None: 4593 return format_time( 4594 self.sql(expression, "format"), 4595 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4596 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4597 ) 4598 4599 def expressions( 4600 self, 4601 expression: exp.Expr | None = None, 4602 key: str | None = None, 4603 sqls: t.Collection[str | exp.Expr] | None = None, 4604 flat: bool = False, 4605 indent: bool = True, 4606 skip_first: bool = False, 4607 skip_last: bool = False, 4608 sep: str = ", ", 4609 prefix: str = "", 4610 dynamic: bool = False, 4611 new_line: bool = False, 4612 ) -> str: 4613 expressions = expression.args.get(key or "expressions") if expression else sqls 4614 4615 if not expressions: 4616 return "" 4617 4618 if flat: 4619 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4620 4621 num_sqls = len(expressions) 4622 result_sqls = [] 4623 4624 for i, e in enumerate(expressions): 4625 sql = self.sql(e, comment=False) 4626 if not sql: 4627 continue 4628 4629 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4630 4631 if self.pretty: 4632 if self.leading_comma: 4633 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4634 else: 4635 result_sqls.append( 4636 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4637 ) 4638 else: 4639 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4640 4641 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4642 if new_line: 4643 result_sqls.insert(0, "") 4644 result_sqls.append("") 4645 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4646 else: 4647 result_sql = "".join(result_sqls) 4648 4649 return ( 4650 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4651 if indent 4652 else result_sql 4653 ) 4654 4655 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4656 flat = flat or isinstance(expression.parent, exp.Properties) 4657 expressions_sql = self.expressions(expression, flat=flat) 4658 if flat: 4659 return f"{op} {expressions_sql}" 4660 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4661 4662 def naked_property(self, expression: exp.Property) -> str: 4663 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4664 if not property_name: 4665 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4666 return f"{property_name} {self.sql(expression, 'this')}" 4667 4668 def tag_sql(self, expression: exp.Tag) -> str: 4669 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4670 4671 def token_sql(self, token_type: TokenType) -> str: 4672 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4673 4674 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4675 this = self.sql(expression, "this") 4676 expressions = self.no_identify(self.expressions, expression) 4677 expressions = ( 4678 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4679 ) 4680 return f"{this}{expressions}" if expressions.strip() != "" else this 4681 4682 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4683 return self.expressions(expression, flat=True) 4684 4685 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4686 params = self.no_identify(self.expressions, expression, flat=True) 4687 body = self.sql(expression, "this") 4688 prefix = "TABLE " if expression.args.get("is_table") else "" 4689 return f"({params}) AS {prefix}{body}" 4690 4691 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4692 this = self.sql(expression, "this") 4693 expressions = self.expressions(expression, flat=True) 4694 return f"{this}({expressions})" 4695 4696 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4697 return self.binary(expression, "=>") 4698 4699 def when_sql(self, expression: exp.When) -> str: 4700 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4701 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4702 condition = self.sql(expression, "condition") 4703 condition = f" AND {condition}" if condition else "" 4704 4705 then_expression = expression.args.get("then") 4706 if isinstance(then_expression, exp.Insert): 4707 this = self.sql(then_expression, "this") 4708 this = f"INSERT {this}" if this else "INSERT" 4709 then = self.sql(then_expression, "expression") 4710 then = f"{this} VALUES {then}" if then else this 4711 elif isinstance(then_expression, exp.Update): 4712 if isinstance(then_expression.args.get("expressions"), exp.Star): 4713 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4714 else: 4715 expressions_sql = self.expressions(then_expression) 4716 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4717 else: 4718 then = self.sql(then_expression) 4719 4720 if isinstance(then_expression, (exp.Insert, exp.Update)): 4721 where = self.sql(then_expression, "where") 4722 if where and not self.SUPPORTS_MERGE_WHERE: 4723 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4724 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4725 where = "" 4726 then = f"{then}{where}" 4727 return f"WHEN {matched}{source}{condition} THEN {then}" 4728 4729 def whens_sql(self, expression: exp.Whens) -> str: 4730 return self.expressions(expression, sep=" ", indent=False) 4731 4732 def merge_sql(self, expression: exp.Merge) -> str: 4733 table = expression.this 4734 table_alias = "" 4735 4736 hints = table.args.get("hints") 4737 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4738 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4739 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4740 4741 this = self.sql(table) 4742 using = f"USING {self.sql(expression, 'using')}" 4743 whens = self.sql(expression, "whens") 4744 4745 on = self.sql(expression, "on") 4746 on = f"ON {on}" if on else "" 4747 4748 if not on: 4749 on = self.expressions(expression, key="using_cond") 4750 on = f"USING ({on})" if on else "" 4751 4752 returning = self.sql(expression, "returning") 4753 if returning: 4754 whens = f"{whens}{returning}" 4755 4756 sep = self.sep() 4757 4758 return self.prepend_ctes( 4759 expression, 4760 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4761 ) 4762 4763 @unsupported_args("format") 4764 def tochar_sql(self, expression: exp.ToChar) -> str: 4765 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4766 4767 @unsupported_args("default") 4768 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4769 if not self.SUPPORTS_TO_NUMBER: 4770 self.unsupported("Unsupported TO_NUMBER function") 4771 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4772 4773 fmt = expression.args.get("format") 4774 if not fmt: 4775 self.unsupported("Conversion format is required for TO_NUMBER") 4776 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4777 4778 return self.func("TO_NUMBER", expression.this, fmt) 4779 4780 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4781 this = self.sql(expression, "this") 4782 kind = self.sql(expression, "kind") 4783 settings_sql = self.expressions(expression, key="settings", sep=" ") 4784 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4785 return f"{this}({kind}{args})" 4786 4787 def dictrange_sql(self, expression: exp.DictRange) -> str: 4788 this = self.sql(expression, "this") 4789 max = self.sql(expression, "max") 4790 min = self.sql(expression, "min") 4791 return f"{this}(MIN {min} MAX {max})" 4792 4793 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4794 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4795 4796 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4797 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4798 4799 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4800 def uniquekeyproperty_sql( 4801 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4802 ) -> str: 4803 return f"{prefix} ({self.expressions(expression, flat=True)})" 4804 4805 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4806 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4807 expressions = self.expressions(expression, flat=True) 4808 expressions = f" {self.wrap(expressions)}" if expressions else "" 4809 buckets = self.sql(expression, "buckets") 4810 kind = self.sql(expression, "kind") 4811 buckets = f" BUCKETS {buckets}" if buckets else "" 4812 order = self.sql(expression, "order") 4813 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4814 4815 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4816 return "" 4817 4818 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4819 expressions = self.expressions(expression, key="expressions", flat=True) 4820 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4821 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4822 buckets = self.sql(expression, "buckets") 4823 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4824 4825 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4826 this = self.sql(expression, "this") 4827 having = self.sql(expression, "having") 4828 4829 if having: 4830 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4831 4832 return self.func("ANY_VALUE", this) 4833 4834 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4835 transform = self.func("TRANSFORM", *expression.expressions) 4836 row_format_before = self.sql(expression, "row_format_before") 4837 row_format_before = f" {row_format_before}" if row_format_before else "" 4838 record_writer = self.sql(expression, "record_writer") 4839 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4840 using = f" USING {self.sql(expression, 'command_script')}" 4841 schema = self.sql(expression, "schema") 4842 schema = f" AS {schema}" if schema else "" 4843 row_format_after = self.sql(expression, "row_format_after") 4844 row_format_after = f" {row_format_after}" if row_format_after else "" 4845 record_reader = self.sql(expression, "record_reader") 4846 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4847 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4848 4849 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4850 key_block_size = self.sql(expression, "key_block_size") 4851 if key_block_size: 4852 return f"KEY_BLOCK_SIZE = {key_block_size}" 4853 4854 using = self.sql(expression, "using") 4855 if using: 4856 return f"USING {using}" 4857 4858 parser = self.sql(expression, "parser") 4859 if parser: 4860 return f"WITH PARSER {parser}" 4861 4862 comment = self.sql(expression, "comment") 4863 if comment: 4864 return f"COMMENT {comment}" 4865 4866 visible = expression.args.get("visible") 4867 if visible is not None: 4868 return "VISIBLE" if visible else "INVISIBLE" 4869 4870 engine_attr = self.sql(expression, "engine_attr") 4871 if engine_attr: 4872 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4873 4874 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4875 if secondary_engine_attr: 4876 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4877 4878 self.unsupported("Unsupported index constraint option.") 4879 return "" 4880 4881 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4882 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4883 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4884 4885 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4886 kind = self.sql(expression, "kind") 4887 kind = f"{kind} INDEX" if kind else "INDEX" 4888 this = self.sql(expression, "this") 4889 this = f" {this}" if this else "" 4890 index_type = self.sql(expression, "index_type") 4891 index_type = f" USING {index_type}" if index_type else "" 4892 expressions = self.expressions(expression, flat=True) 4893 expressions = f" ({expressions})" if expressions else "" 4894 options = self.expressions(expression, key="options", sep=" ") 4895 options = f" {options}" if options else "" 4896 return f"{kind}{this}{index_type}{expressions}{options}" 4897 4898 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4899 if self.NVL2_SUPPORTED: 4900 return self.function_fallback_sql(expression) 4901 4902 case = exp.Case().when( 4903 expression.this.is_(exp.null()).not_(copy=False), 4904 expression.args["true"], 4905 copy=False, 4906 ) 4907 else_cond = expression.args.get("false") 4908 if else_cond: 4909 case.else_(else_cond, copy=False) 4910 4911 return self.sql(case) 4912 4913 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4914 this = self.sql(expression, "this") 4915 expr = self.sql(expression, "expression") 4916 position = self.sql(expression, "position") 4917 position = f", {position}" if position else "" 4918 iterator = self.sql(expression, "iterator") 4919 condition = self.sql(expression, "condition") 4920 condition = f" IF {condition}" if condition else "" 4921 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4922 4923 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4924 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4925 4926 def opclass_sql(self, expression: exp.Opclass) -> str: 4927 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4928 4929 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4930 model = self.sql(expression, "this") 4931 model = f"MODEL {model}" 4932 expr = expression.expression 4933 if expr: 4934 expr_sql = self.sql(expression, "expression") 4935 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 4936 else: 4937 expr_sql = None 4938 4939 parameters = self.sql(expression, "params_struct") or None 4940 4941 return self.func(name, model, expr_sql, parameters) 4942 4943 def predict_sql(self, expression: exp.Predict) -> str: 4944 return self._ml_sql(expression, "PREDICT") 4945 4946 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4947 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4948 return self._ml_sql(expression, name) 4949 4950 def generatetext_sql(self, expression: exp.GenerateText) -> str: 4951 return self._ml_sql(expression, "GENERATE_TEXT") 4952 4953 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 4954 return self._ml_sql(expression, "GENERATE_TABLE") 4955 4956 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 4957 return self._ml_sql(expression, "GENERATE_BOOL") 4958 4959 def generateint_sql(self, expression: exp.GenerateInt) -> str: 4960 return self._ml_sql(expression, "GENERATE_INT") 4961 4962 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 4963 return self._ml_sql(expression, "GENERATE_DOUBLE") 4964 4965 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4966 return self._ml_sql(expression, "TRANSLATE") 4967 4968 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4969 return self._ml_sql(expression, "FORECAST") 4970 4971 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4972 this_sql = self.sql(expression, "this") 4973 if isinstance(expression.this, exp.Table): 4974 this_sql = f"TABLE {this_sql}" 4975 4976 return self.func( 4977 "FORECAST", 4978 this_sql, 4979 expression.args.get("data_col"), 4980 expression.args.get("timestamp_col"), 4981 expression.args.get("model"), 4982 expression.args.get("id_cols"), 4983 expression.args.get("horizon"), 4984 expression.args.get("forecast_end_timestamp"), 4985 expression.args.get("confidence_level"), 4986 expression.args.get("output_historical_time_series"), 4987 expression.args.get("context_window"), 4988 ) 4989 4990 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4991 this_sql = self.sql(expression, "this") 4992 if isinstance(expression.this, exp.Table): 4993 this_sql = f"TABLE {this_sql}" 4994 4995 return self.func( 4996 "FEATURES_AT_TIME", 4997 this_sql, 4998 expression.args.get("time"), 4999 expression.args.get("num_rows"), 5000 expression.args.get("ignore_feature_nulls"), 5001 ) 5002 5003 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5004 this_sql = self.sql(expression, "this") 5005 if isinstance(expression.this, exp.Table): 5006 this_sql = f"TABLE {this_sql}" 5007 5008 query_table = self.sql(expression, "query_table") 5009 if isinstance(expression.args["query_table"], exp.Table): 5010 query_table = f"TABLE {query_table}" 5011 5012 return self.func( 5013 "VECTOR_SEARCH", 5014 this_sql, 5015 expression.args.get("column_to_search"), 5016 query_table, 5017 expression.args.get("query_column_to_search"), 5018 expression.args.get("top_k"), 5019 expression.args.get("distance_type"), 5020 expression.args.get("options"), 5021 ) 5022 5023 def forin_sql(self, expression: exp.ForIn) -> str: 5024 this = self.sql(expression, "this") 5025 expression_sql = self.sql(expression, "expression") 5026 return f"FOR {this} DO {expression_sql}" 5027 5028 def refresh_sql(self, expression: exp.Refresh) -> str: 5029 this = self.sql(expression, "this") 5030 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5031 return f"REFRESH {kind}{this}" 5032 5033 def toarray_sql(self, expression: exp.ToArray) -> str: 5034 arg = expression.this 5035 if not arg.type: 5036 import sqlglot.optimizer.annotate_types 5037 5038 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5039 5040 if arg.is_type(exp.DType.ARRAY): 5041 return self.sql(arg) 5042 5043 cond_for_null = arg.is_(exp.null()) 5044 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5045 5046 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5047 this = expression.this 5048 time_format = self.format_time(expression) 5049 5050 if time_format: 5051 return self.sql( 5052 exp.cast( 5053 exp.StrToTime(this=this, format=expression.args["format"]), 5054 exp.DType.TIME, 5055 ) 5056 ) 5057 5058 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5059 return self.sql(this) 5060 5061 return self.sql(exp.cast(this, exp.DType.TIME)) 5062 5063 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5064 this = expression.this 5065 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5066 return self.sql(this) 5067 5068 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5069 5070 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5071 this = expression.this 5072 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5073 return self.sql(this) 5074 5075 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5076 5077 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5078 this = expression.this 5079 time_format = self.format_time(expression) 5080 safe = expression.args.get("safe") 5081 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5082 return self.sql( 5083 exp.cast( 5084 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5085 exp.DType.DATE, 5086 ) 5087 ) 5088 5089 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5090 return self.sql(this) 5091 5092 if safe: 5093 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5094 5095 return self.sql(exp.cast(this, exp.DType.DATE)) 5096 5097 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5098 return self.sql( 5099 exp.func( 5100 "DATEDIFF", 5101 expression.this, 5102 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5103 "day", 5104 ) 5105 ) 5106 5107 def lastday_sql(self, expression: exp.LastDay) -> str: 5108 if self.LAST_DAY_SUPPORTS_DATE_PART: 5109 return self.function_fallback_sql(expression) 5110 5111 unit = expression.text("unit") 5112 if unit and unit != "MONTH": 5113 self.unsupported("Date parts are not supported in LAST_DAY.") 5114 5115 return self.func("LAST_DAY", expression.this) 5116 5117 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5118 import sqlglot.dialects.dialect 5119 5120 return self.func( 5121 "DATE_ADD", 5122 expression.this, 5123 expression.expression, 5124 sqlglot.dialects.dialect.unit_to_str(expression), 5125 ) 5126 5127 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5128 if self.CAN_IMPLEMENT_ARRAY_ANY: 5129 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5130 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5131 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5132 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5133 5134 import sqlglot.dialects.dialect 5135 5136 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5137 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5138 self.unsupported("ARRAY_ANY is unsupported") 5139 5140 return self.function_fallback_sql(expression) 5141 5142 def struct_sql(self, expression: exp.Struct) -> str: 5143 expression.set( 5144 "expressions", 5145 [ 5146 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5147 if isinstance(e, exp.PropertyEQ) 5148 else e 5149 for e in expression.expressions 5150 ], 5151 ) 5152 5153 return self.function_fallback_sql(expression) 5154 5155 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5156 low = self.sql(expression, "this") 5157 high = self.sql(expression, "expression") 5158 5159 return f"{low} TO {high}" 5160 5161 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5162 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5163 tables = f" {self.expressions(expression)}" 5164 5165 exists = " IF EXISTS" if expression.args.get("exists") else "" 5166 5167 on_cluster = self.sql(expression, "cluster") 5168 on_cluster = f" {on_cluster}" if on_cluster else "" 5169 5170 identity = self.sql(expression, "identity") 5171 identity = f" {identity} IDENTITY" if identity else "" 5172 5173 option = self.sql(expression, "option") 5174 option = f" {option}" if option else "" 5175 5176 partition = self.sql(expression, "partition") 5177 partition = f" {partition}" if partition else "" 5178 5179 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5180 5181 # This transpiles T-SQL's CONVERT function 5182 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5183 def convert_sql(self, expression: exp.Convert) -> str: 5184 to = expression.this 5185 value = expression.expression 5186 style = expression.args.get("style") 5187 safe = expression.args.get("safe") 5188 strict = expression.args.get("strict") 5189 5190 if not to or not value: 5191 return "" 5192 5193 # Retrieve length of datatype and override to default if not specified 5194 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5195 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5196 5197 transformed: exp.Expr | None = None 5198 cast = exp.Cast if strict else exp.TryCast 5199 5200 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5201 if isinstance(style, exp.Literal) and style.is_int: 5202 import sqlglot.dialects.tsql 5203 5204 style_value = style.name 5205 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5206 if not converted_style: 5207 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5208 5209 fmt = exp.Literal.string(converted_style) 5210 5211 if to.this == exp.DType.DATE: 5212 transformed = exp.StrToDate(this=value, format=fmt) 5213 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5214 transformed = exp.StrToTime(this=value, format=fmt) 5215 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5216 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5217 elif to.this == exp.DType.TEXT: 5218 transformed = exp.TimeToStr(this=value, format=fmt) 5219 5220 if not transformed: 5221 transformed = cast(this=value, to=to, safe=safe) 5222 5223 return self.sql(transformed) 5224 5225 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5226 this = expression.this 5227 if isinstance(this, exp.JSONPathWildcard): 5228 this = self.json_path_part(this) 5229 return f".{this}" if this else "" 5230 5231 if self.SAFE_JSON_PATH_KEY_RE.match(this): 5232 return f".{this}" 5233 5234 this = self.json_path_part(this) 5235 return ( 5236 f"[{this}]" 5237 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5238 else f".{this}" 5239 ) 5240 5241 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5242 this = self.json_path_part(expression.this) 5243 return f"[{this}]" if this else "" 5244 5245 def _simplify_unless_literal(self, expression: E) -> E: 5246 if not isinstance(expression, exp.Literal): 5247 import sqlglot.optimizer.simplify 5248 5249 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5250 5251 return expression 5252 5253 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5254 this = expression.this 5255 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5256 self.unsupported( 5257 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5258 ) 5259 return self.sql(this) 5260 5261 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5262 if self.IGNORE_NULLS_BEFORE_ORDER: 5263 # The first modifier here will be the one closest to the AggFunc's arg 5264 mods = sorted( 5265 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5266 key=lambda x: ( 5267 0 5268 if isinstance(x, exp.HavingMax) 5269 else (1 if isinstance(x, exp.Order) else 2) 5270 ), 5271 ) 5272 5273 if mods: 5274 mod = mods[0] 5275 this = expression.__class__(this=mod.this.copy()) 5276 this.meta["inline"] = True 5277 mod.this.replace(this) 5278 return self.sql(expression.this) 5279 5280 agg_func = expression.find(exp.AggFunc) 5281 5282 if agg_func: 5283 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5284 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5285 5286 return f"{self.sql(expression, 'this')} {text}" 5287 5288 def _replace_line_breaks(self, string: str) -> str: 5289 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5290 if self.pretty: 5291 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5292 return string 5293 5294 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5295 option = self.sql(expression, "this") 5296 5297 if expression.expressions: 5298 upper = option.upper() 5299 5300 # Snowflake FILE_FORMAT options are separated by whitespace 5301 sep = " " if upper == "FILE_FORMAT" else ", " 5302 5303 # Databricks copy/format options do not set their list of values with EQ 5304 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5305 values = self.expressions(expression, flat=True, sep=sep) 5306 return f"{option}{op}({values})" 5307 5308 value = self.sql(expression, "expression") 5309 5310 if not value: 5311 return option 5312 5313 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5314 5315 return f"{option}{op}{value}" 5316 5317 def credentials_sql(self, expression: exp.Credentials) -> str: 5318 cred_expr = expression.args.get("credentials") 5319 if isinstance(cred_expr, exp.Literal): 5320 # Redshift case: CREDENTIALS <string> 5321 credentials = self.sql(expression, "credentials") 5322 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5323 else: 5324 # Snowflake case: CREDENTIALS = (...) 5325 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5326 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5327 5328 storage = self.sql(expression, "storage") 5329 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5330 5331 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5332 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5333 5334 iam_role = self.sql(expression, "iam_role") 5335 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5336 5337 region = self.sql(expression, "region") 5338 region = f" REGION {region}" if region else "" 5339 5340 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5341 5342 def copy_sql(self, expression: exp.Copy) -> str: 5343 this = self.sql(expression, "this") 5344 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5345 5346 credentials = self.sql(expression, "credentials") 5347 credentials = self.seg(credentials) if credentials else "" 5348 files = self.expressions(expression, key="files", flat=True) 5349 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5350 5351 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5352 params = self.expressions( 5353 expression, 5354 key="params", 5355 sep=sep, 5356 new_line=True, 5357 skip_last=True, 5358 skip_first=True, 5359 indent=self.COPY_PARAMS_ARE_WRAPPED, 5360 ) 5361 5362 if params: 5363 if self.COPY_PARAMS_ARE_WRAPPED: 5364 params = f" WITH ({params})" 5365 elif not self.pretty and (files or credentials): 5366 params = f" {params}" 5367 5368 return f"COPY{this}{kind} {files}{credentials}{params}" 5369 5370 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5371 return "" 5372 5373 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5374 on_sql = "ON" if expression.args.get("on") else "OFF" 5375 filter_col: str | None = self.sql(expression, "filter_column") 5376 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5377 retention_period: str | None = self.sql(expression, "retention_period") 5378 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5379 5380 if filter_col or retention_period: 5381 on_sql = self.func("ON", filter_col, retention_period) 5382 5383 return f"DATA_DELETION={on_sql}" 5384 5385 def maskingpolicycolumnconstraint_sql( 5386 self, expression: exp.MaskingPolicyColumnConstraint 5387 ) -> str: 5388 this = self.sql(expression, "this") 5389 expressions = self.expressions(expression, flat=True) 5390 expressions = f" USING ({expressions})" if expressions else "" 5391 return f"MASKING POLICY {this}{expressions}" 5392 5393 def gapfill_sql(self, expression: exp.GapFill) -> str: 5394 this = self.sql(expression, "this") 5395 this = f"TABLE {this}" 5396 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5397 5398 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5399 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5400 5401 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5402 this = self.sql(expression, "this") 5403 expr = expression.expression 5404 5405 if isinstance(expr, exp.Func): 5406 # T-SQL's CLR functions are case sensitive 5407 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5408 else: 5409 expr = self.sql(expression, "expression") 5410 5411 return self.scope_resolution(expr, this) 5412 5413 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5414 if self.PARSE_JSON_NAME is None: 5415 return self.sql(expression.this) 5416 5417 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5418 5419 def rand_sql(self, expression: exp.Rand) -> str: 5420 lower = self.sql(expression, "lower") 5421 upper = self.sql(expression, "upper") 5422 5423 if lower and upper: 5424 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5425 return self.func("RAND", expression.this) 5426 5427 def changes_sql(self, expression: exp.Changes) -> str: 5428 information = self.sql(expression, "information") 5429 information = f"INFORMATION => {information}" 5430 at_before = self.sql(expression, "at_before") 5431 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5432 end = self.sql(expression, "end") 5433 end = f"{self.seg('')}{end}" if end else "" 5434 5435 return f"CHANGES ({information}){at_before}{end}" 5436 5437 def pad_sql(self, expression: exp.Pad) -> str: 5438 prefix = "L" if expression.args.get("is_left") else "R" 5439 5440 fill_pattern = self.sql(expression, "fill_pattern") or None 5441 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5442 fill_pattern = "' '" 5443 5444 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5445 5446 def summarize_sql(self, expression: exp.Summarize) -> str: 5447 table = " TABLE" if expression.args.get("table") else "" 5448 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5449 5450 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5451 generate_series = exp.GenerateSeries(**expression.args) 5452 5453 parent = expression.parent 5454 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5455 parent = parent.parent 5456 5457 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5458 return self.sql(exp.Unnest(expressions=[generate_series])) 5459 5460 if isinstance(parent, exp.Select): 5461 self.unsupported("GenerateSeries projection unnesting is not supported.") 5462 5463 return self.sql(generate_series) 5464 5465 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5466 if self.SUPPORTS_CONVERT_TIMEZONE: 5467 return self.function_fallback_sql(expression) 5468 5469 source_tz = expression.args.get("source_tz") 5470 target_tz = expression.args.get("target_tz") 5471 timestamp = expression.args.get("timestamp") 5472 5473 if source_tz and timestamp: 5474 timestamp = exp.AtTimeZone( 5475 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5476 ) 5477 5478 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5479 5480 return self.sql(expr) 5481 5482 def json_sql(self, expression: exp.JSON) -> str: 5483 this = self.sql(expression, "this") 5484 this = f" {this}" if this else "" 5485 5486 _with = expression.args.get("with_") 5487 5488 if _with is None: 5489 with_sql = "" 5490 elif not _with: 5491 with_sql = " WITHOUT" 5492 else: 5493 with_sql = " WITH" 5494 5495 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5496 5497 return f"JSON{this}{with_sql}{unique_sql}" 5498 5499 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5500 path = self.sql(expression, "path") 5501 returning = self.sql(expression, "returning") 5502 returning = f" RETURNING {returning}" if returning else "" 5503 5504 on_condition = self.sql(expression, "on_condition") 5505 on_condition = f" {on_condition}" if on_condition else "" 5506 5507 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5508 5509 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5510 regexp = " REGEXP" if expression.args.get("regexp") else "" 5511 return f"SKIP{regexp} {self.sql(expression.expression)}" 5512 5513 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5514 else_ = "ELSE " if expression.args.get("else_") else "" 5515 condition = self.sql(expression, "expression") 5516 condition = f"WHEN {condition} THEN " if condition else else_ 5517 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5518 return f"{condition}{insert}" 5519 5520 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5521 kind = self.sql(expression, "kind") 5522 expressions = self.seg(self.expressions(expression, sep=" ")) 5523 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5524 return res 5525 5526 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5527 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5528 empty = expression.args.get("empty") 5529 empty = ( 5530 f"DEFAULT {empty} ON EMPTY" 5531 if isinstance(empty, exp.Expr) 5532 else self.sql(expression, "empty") 5533 ) 5534 5535 error = expression.args.get("error") 5536 error = ( 5537 f"DEFAULT {error} ON ERROR" 5538 if isinstance(error, exp.Expr) 5539 else self.sql(expression, "error") 5540 ) 5541 5542 if error and empty: 5543 error = ( 5544 f"{empty} {error}" 5545 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5546 else f"{error} {empty}" 5547 ) 5548 empty = "" 5549 5550 null = self.sql(expression, "null") 5551 5552 return f"{empty}{error}{null}" 5553 5554 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5555 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5556 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5557 5558 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5559 this = self.sql(expression, "this") 5560 path = self.sql(expression, "path") 5561 5562 passing = self.expressions(expression, "passing") 5563 passing = f" PASSING {passing}" if passing else "" 5564 5565 on_condition = self.sql(expression, "on_condition") 5566 on_condition = f" {on_condition}" if on_condition else "" 5567 5568 path = f"{path}{passing}{on_condition}" 5569 5570 return self.func("JSON_EXISTS", this, path) 5571 5572 def _add_arrayagg_null_filter( 5573 self, 5574 array_agg_sql: str, 5575 array_agg_expr: exp.ArrayAgg, 5576 column_expr: exp.Expr, 5577 ) -> str: 5578 """ 5579 Add NULL filter to ARRAY_AGG if dialect requires it. 5580 5581 Args: 5582 array_agg_sql: The generated ARRAY_AGG SQL string 5583 array_agg_expr: The ArrayAgg expression node 5584 column_expr: The column/expression to filter (before ORDER BY wrapping) 5585 5586 Returns: 5587 SQL string with FILTER clause added if needed 5588 """ 5589 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5590 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5591 if not ( 5592 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5593 ): 5594 return array_agg_sql 5595 5596 parent = array_agg_expr.parent 5597 if isinstance(parent, exp.Filter): 5598 parent_cond = parent.expression.this 5599 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5600 elif column_expr.find(exp.Column): 5601 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5602 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5603 this_sql = ( 5604 self.expressions(column_expr) 5605 if isinstance(column_expr, exp.Distinct) 5606 else self.sql(column_expr) 5607 ) 5608 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5609 5610 return array_agg_sql 5611 5612 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5613 array_agg = self.function_fallback_sql(expression) 5614 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5615 5616 def slice_sql(self, expression: exp.Slice) -> str: 5617 step = self.sql(expression, "step") 5618 end = self.sql(expression.expression) 5619 begin = self.sql(expression.this) 5620 5621 sql = f"{end}:{step}" if step else end 5622 return f"{begin}:{sql}" if sql else f"{begin}:" 5623 5624 def apply_sql(self, expression: exp.Apply) -> str: 5625 this = self.sql(expression, "this") 5626 expr = self.sql(expression, "expression") 5627 5628 return f"{this} APPLY({expr})" 5629 5630 def _grant_or_revoke_sql( 5631 self, 5632 expression: exp.Grant | exp.Revoke, 5633 keyword: str, 5634 preposition: str, 5635 grant_option_prefix: str = "", 5636 grant_option_suffix: str = "", 5637 ) -> str: 5638 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5639 5640 kind = self.sql(expression, "kind") 5641 kind = f" {kind}" if kind else "" 5642 5643 securable = self.sql(expression, "securable") 5644 securable = f" {securable}" if securable else "" 5645 5646 principals = self.expressions(expression, key="principals", flat=True) 5647 5648 if not expression.args.get("grant_option"): 5649 grant_option_prefix = grant_option_suffix = "" 5650 5651 # cascade for revoke only 5652 cascade = self.sql(expression, "cascade") 5653 cascade = f" {cascade}" if cascade else "" 5654 5655 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5656 5657 def grant_sql(self, expression: exp.Grant) -> str: 5658 return self._grant_or_revoke_sql( 5659 expression, 5660 keyword="GRANT", 5661 preposition="TO", 5662 grant_option_suffix=" WITH GRANT OPTION", 5663 ) 5664 5665 def revoke_sql(self, expression: exp.Revoke) -> str: 5666 return self._grant_or_revoke_sql( 5667 expression, 5668 keyword="REVOKE", 5669 preposition="FROM", 5670 grant_option_prefix="GRANT OPTION FOR ", 5671 ) 5672 5673 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5674 this = self.sql(expression, "this") 5675 columns = self.expressions(expression, flat=True) 5676 columns = f"({columns})" if columns else "" 5677 5678 return f"{this}{columns}" 5679 5680 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5681 this = self.sql(expression, "this") 5682 5683 kind = self.sql(expression, "kind") 5684 kind = f"{kind} " if kind else "" 5685 5686 return f"{kind}{this}" 5687 5688 def columns_sql(self, expression: exp.Columns) -> str: 5689 func = self.function_fallback_sql(expression) 5690 if expression.args.get("unpack"): 5691 func = f"*{func}" 5692 5693 return func 5694 5695 def overlay_sql(self, expression: exp.Overlay) -> str: 5696 this = self.sql(expression, "this") 5697 expr = self.sql(expression, "expression") 5698 from_sql = self.sql(expression, "from_") 5699 for_sql = self.sql(expression, "for_") 5700 for_sql = f" FOR {for_sql}" if for_sql else "" 5701 5702 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5703 5704 @unsupported_args("format") 5705 def todouble_sql(self, expression: exp.ToDouble) -> str: 5706 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5707 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5708 5709 def string_sql(self, expression: exp.String) -> str: 5710 this = expression.this 5711 zone = expression.args.get("zone") 5712 5713 if zone: 5714 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5715 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5716 # set for source_tz to transpile the time conversion before the STRING cast 5717 this = exp.ConvertTimezone( 5718 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5719 ) 5720 5721 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5722 5723 def median_sql(self, expression: exp.Median) -> str: 5724 if not self.SUPPORTS_MEDIAN: 5725 return self.sql( 5726 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5727 ) 5728 5729 return self.function_fallback_sql(expression) 5730 5731 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5732 filler = self.sql(expression, "this") 5733 filler = f" {filler}" if filler else "" 5734 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5735 return f"TRUNCATE{filler} {with_count}" 5736 5737 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5738 if self.SUPPORTS_UNIX_SECONDS: 5739 return self.function_fallback_sql(expression) 5740 5741 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5742 5743 return self.sql( 5744 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5745 ) 5746 5747 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5748 dim = expression.expression 5749 5750 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5751 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5752 if not (dim.is_int and dim.name == "1"): 5753 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5754 dim = None 5755 5756 # If dimension is required but not specified, default initialize it 5757 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5758 dim = exp.Literal.number(1) 5759 5760 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5761 5762 def attach_sql(self, expression: exp.Attach) -> str: 5763 this = self.sql(expression, "this") 5764 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5765 expressions = self.expressions(expression) 5766 expressions = f" ({expressions})" if expressions else "" 5767 5768 return f"ATTACH{exists_sql} {this}{expressions}" 5769 5770 def detach_sql(self, expression: exp.Detach) -> str: 5771 kind = self.sql(expression, "kind") 5772 kind = f" {kind}" if kind else "" 5773 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5774 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5775 exists = " IF EXISTS" if expression.args.get("exists") else "" 5776 if exists: 5777 kind = kind or " DATABASE" 5778 5779 this = self.sql(expression, "this") 5780 this = f" {this}" if this else "" 5781 cluster = self.sql(expression, "cluster") 5782 cluster = f" {cluster}" if cluster else "" 5783 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5784 sync = " SYNC" if expression.args.get("sync") else "" 5785 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5786 5787 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5788 this = self.sql(expression, "this") 5789 value = self.sql(expression, "expression") 5790 value = f" {value}" if value else "" 5791 return f"{this}{value}" 5792 5793 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5794 return ( 5795 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5796 ) 5797 5798 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5799 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5800 encode = f"{encode} {self.sql(expression, 'this')}" 5801 5802 properties = expression.args.get("properties") 5803 if properties: 5804 encode = f"{encode} {self.properties(properties)}" 5805 5806 return encode 5807 5808 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5809 this = self.sql(expression, "this") 5810 include = f"INCLUDE {this}" 5811 5812 column_def = self.sql(expression, "column_def") 5813 if column_def: 5814 include = f"{include} {column_def}" 5815 5816 alias = self.sql(expression, "alias") 5817 if alias: 5818 include = f"{include} AS {alias}" 5819 5820 return include 5821 5822 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5823 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5824 name = f"{prefix} {self.sql(expression, 'this')}" 5825 return self.func("XMLELEMENT", name, *expression.expressions) 5826 5827 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5828 this = self.sql(expression, "this") 5829 expr = self.sql(expression, "expression") 5830 expr = f"({expr})" if expr else "" 5831 return f"{this}{expr}" 5832 5833 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5834 partitions = self.expressions(expression, "partition_expressions") 5835 create = self.expressions(expression, "create_expressions") 5836 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5837 5838 def partitionbyrangepropertydynamic_sql( 5839 self, expression: exp.PartitionByRangePropertyDynamic 5840 ) -> str: 5841 start = self.sql(expression, "start") 5842 end = self.sql(expression, "end") 5843 5844 every = expression.args["every"] 5845 if isinstance(every, exp.Interval) and every.this.is_string: 5846 every.this.replace(exp.Literal.number(every.name)) 5847 5848 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5849 5850 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5851 name = self.sql(expression, "this") 5852 values = self.expressions(expression, flat=True) 5853 5854 return f"NAME {name} VALUE {values}" 5855 5856 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5857 kind = self.sql(expression, "kind") 5858 sample = self.sql(expression, "sample") 5859 return f"SAMPLE {sample} {kind}" 5860 5861 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5862 kind = self.sql(expression, "kind") 5863 option = self.sql(expression, "option") 5864 option = f" {option}" if option else "" 5865 this = self.sql(expression, "this") 5866 this = f" {this}" if this else "" 5867 columns = self.expressions(expression) 5868 columns = f" {columns}" if columns else "" 5869 return f"{kind}{option} STATISTICS{this}{columns}" 5870 5871 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5872 this = self.sql(expression, "this") 5873 columns = self.expressions(expression) 5874 inner_expression = self.sql(expression, "expression") 5875 inner_expression = f" {inner_expression}" if inner_expression else "" 5876 update_options = self.sql(expression, "update_options") 5877 update_options = f" {update_options} UPDATE" if update_options else "" 5878 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5879 5880 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5881 kind = self.sql(expression, "kind") 5882 kind = f" {kind}" if kind else "" 5883 return f"DELETE{kind} STATISTICS" 5884 5885 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5886 inner_expression = self.sql(expression, "expression") 5887 return f"LIST CHAINED ROWS{inner_expression}" 5888 5889 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5890 kind = self.sql(expression, "kind") 5891 this = self.sql(expression, "this") 5892 this = f" {this}" if this else "" 5893 inner_expression = self.sql(expression, "expression") 5894 return f"VALIDATE {kind}{this}{inner_expression}" 5895 5896 def analyze_sql(self, expression: exp.Analyze) -> str: 5897 options = self.expressions(expression, key="options", sep=" ") 5898 options = f" {options}" if options else "" 5899 kind = self.sql(expression, "kind") 5900 kind = f" {kind}" if kind else "" 5901 this = self.sql(expression, "this") 5902 this = f" {this}" if this else "" 5903 mode = self.sql(expression, "mode") 5904 mode = f" {mode}" if mode else "" 5905 properties = self.sql(expression, "properties") 5906 properties = f" {properties}" if properties else "" 5907 partition = self.sql(expression, "partition") 5908 partition = f" {partition}" if partition else "" 5909 inner_expression = self.sql(expression, "expression") 5910 inner_expression = f" {inner_expression}" if inner_expression else "" 5911 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5912 5913 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5914 this = self.sql(expression, "this") 5915 namespaces = self.expressions(expression, key="namespaces") 5916 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5917 passing = self.expressions(expression, key="passing") 5918 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5919 columns = self.expressions(expression, key="columns") 5920 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5921 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5922 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5923 5924 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5925 this = self.sql(expression, "this") 5926 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5927 5928 def export_sql(self, expression: exp.Export) -> str: 5929 this = self.sql(expression, "this") 5930 connection = self.sql(expression, "connection") 5931 connection = f"WITH CONNECTION {connection} " if connection else "" 5932 options = self.sql(expression, "options") 5933 return f"EXPORT DATA {connection}{options} AS {this}" 5934 5935 def declare_sql(self, expression: exp.Declare) -> str: 5936 replace = "OR REPLACE " if expression.args.get("replace") else "" 5937 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 5938 5939 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5940 variables = self.expressions(expression, "this") 5941 default = self.sql(expression, "default") 5942 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5943 5944 kind = self.sql(expression, "kind") 5945 if isinstance(expression.args.get("kind"), exp.Schema): 5946 kind = f"TABLE {kind}" 5947 5948 kind = f" {kind}" if kind else "" 5949 5950 return f"{variables}{kind}{default}" 5951 5952 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5953 kind = self.sql(expression, "kind") 5954 this = self.sql(expression, "this") 5955 set = self.sql(expression, "expression") 5956 using = self.sql(expression, "using") 5957 using = f" USING {using}" if using else "" 5958 5959 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5960 5961 return f"{kind_sql} {this} SET {set}{using}" 5962 5963 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5964 params = self.expressions(expression, key="params", flat=True) 5965 return self.func(expression.name, *expression.expressions) + f"({params})" 5966 5967 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5968 return self.func(expression.name, *expression.expressions) 5969 5970 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5971 return self.anonymousaggfunc_sql(expression) 5972 5973 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5974 return self.parameterizedagg_sql(expression) 5975 5976 def show_sql(self, expression: exp.Show) -> str: 5977 self.unsupported("Unsupported SHOW statement") 5978 return "" 5979 5980 def install_sql(self, expression: exp.Install) -> str: 5981 self.unsupported("Unsupported INSTALL statement") 5982 return "" 5983 5984 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5985 # Snowflake GET/PUT statements: 5986 # PUT <file> <internalStage> <properties> 5987 # GET <internalStage> <file> <properties> 5988 props = expression.args.get("properties") 5989 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5990 this = self.sql(expression, "this") 5991 target = self.sql(expression, "target") 5992 5993 if isinstance(expression, exp.Put): 5994 return f"PUT {this} {target}{props_sql}" 5995 else: 5996 return f"GET {target} {this}{props_sql}" 5997 5998 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5999 this = self.sql(expression, "this") 6000 expr = self.sql(expression, "expression") 6001 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6002 return f"TRANSLATE({this} USING {expr}{with_error})" 6003 6004 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6005 if self.SUPPORTS_DECODE_CASE: 6006 return self.func("DECODE", *expression.expressions) 6007 6008 decode_expr, *expressions = expression.expressions 6009 6010 ifs = [] 6011 for search, result in zip(expressions[::2], expressions[1::2]): 6012 if isinstance(search, exp.Literal): 6013 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6014 elif isinstance(search, exp.Null): 6015 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6016 else: 6017 if isinstance(search, exp.Binary): 6018 search = exp.paren(search) 6019 6020 cond = exp.or_( 6021 decode_expr.eq(search), 6022 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6023 copy=False, 6024 ) 6025 ifs.append(exp.If(this=cond, true=result)) 6026 6027 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6028 return self.sql(case) 6029 6030 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6031 this = self.sql(expression, "this") 6032 this = self.seg(this, sep="") 6033 dimensions = self.expressions( 6034 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6035 ) 6036 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6037 metrics = self.expressions( 6038 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6039 ) 6040 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6041 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6042 facts = self.seg(f"FACTS {facts}") if facts else "" 6043 where = self.sql(expression, "where") 6044 where = self.seg(f"WHERE {where}") if where else "" 6045 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6046 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6047 6048 def getextract_sql(self, expression: exp.GetExtract) -> str: 6049 this = expression.this 6050 expr = expression.expression 6051 6052 if not this.type or not expression.type: 6053 import sqlglot.optimizer.annotate_types 6054 6055 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6056 6057 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6058 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6059 6060 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6061 6062 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6063 return self.sql( 6064 exp.DateAdd( 6065 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6066 expression=expression.this, 6067 unit=exp.var("DAY"), 6068 ) 6069 ) 6070 6071 def space_sql(self: Generator, expression: exp.Space) -> str: 6072 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6073 6074 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6075 return f"BUILD {self.sql(expression, 'this')}" 6076 6077 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6078 method = self.sql(expression, "method") 6079 kind = expression.args.get("kind") 6080 if not kind: 6081 return f"REFRESH {method}" 6082 6083 every = self.sql(expression, "every") 6084 unit = self.sql(expression, "unit") 6085 every = f" EVERY {every} {unit}" if every else "" 6086 starts = self.sql(expression, "starts") 6087 starts = f" STARTS {starts}" if starts else "" 6088 6089 return f"REFRESH {method} ON {kind}{every}{starts}" 6090 6091 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6092 self.unsupported("The model!attribute syntax is not supported") 6093 return "" 6094 6095 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6096 return self.func("DIRECTORY", expression.this) 6097 6098 def uuid_sql(self, expression: exp.Uuid) -> str: 6099 is_string = expression.args.get("is_string", False) 6100 uuid_func_sql = self.func("UUID") 6101 6102 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6103 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6104 6105 return uuid_func_sql 6106 6107 def initcap_sql(self, expression: exp.Initcap) -> str: 6108 delimiters = expression.expression 6109 6110 if delimiters: 6111 # do not generate delimiters arg if we are round-tripping from default delimiters 6112 if ( 6113 delimiters.is_string 6114 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6115 ): 6116 delimiters = None 6117 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6118 self.unsupported("INITCAP does not support custom delimiters") 6119 delimiters = None 6120 6121 return self.func("INITCAP", expression.this, delimiters) 6122 6123 def localtime_sql(self, expression: exp.Localtime) -> str: 6124 this = expression.this 6125 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6126 6127 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6128 this = expression.this 6129 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6130 6131 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6132 this = expression.this.name.upper() 6133 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 6134 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 6135 return "WEEK" 6136 6137 return self.func("WEEK", expression.this) 6138 6139 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6140 this = self.expressions(expression) 6141 charset = self.sql(expression, "charset") 6142 using = f" USING {charset}" if charset else "" 6143 return self.func(name, this + using) 6144 6145 def block_sql(self, expression: exp.Block) -> str: 6146 expressions = self.expressions(expression, sep="; ", flat=True) 6147 return f"{expressions}" if expressions else "" 6148 6149 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6150 self.unsupported("Unsupported Stored Procedure syntax") 6151 return "" 6152 6153 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6154 self.unsupported("Unsupported If block syntax") 6155 return "" 6156 6157 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6158 self.unsupported("Unsupported While block syntax") 6159 return "" 6160 6161 def execute_sql(self, expression: exp.Execute) -> str: 6162 self.unsupported("Unsupported Execute syntax") 6163 return "" 6164 6165 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6166 self.unsupported("Unsupported Execute syntax") 6167 return "" 6168 6169 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6170 props = self.expressions(expression, sep=" ") 6171 return f"MODIFY {props}" 6172 6173 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6174 kind = expression.args.get("kind") 6175 return f"USING {kind} {self.sql(expression, 'this')}" 6176 6177 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6178 this = self.sql(expression, "this") 6179 to = self.sql(expression, "to") 6180 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]]:
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
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:
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.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 144 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 145 exp.AssumeColumnConstraint: lambda self, e: f"ASSUME ({self.sql(e, 'this')})", 146 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 147 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 148 exp.CaseSpecificColumnConstraint: lambda _, e: ( 149 f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC" 150 ), 151 exp.Ceil: lambda self, e: self.ceil_floor(e), 152 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 153 exp.CharacterSetProperty: lambda self, e: ( 154 f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}" 155 ), 156 exp.ClusteredColumnConstraint: lambda self, e: ( 157 f"CLUSTERED ({self.expressions(e, 'this', indent=False)})" 158 ), 159 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 160 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 161 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 162 exp.ConvertToCharset: lambda self, e: self.func( 163 "CONVERT", e.this, e.args["dest"], e.args.get("source") 164 ), 165 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 166 exp.CredentialsProperty: lambda self, e: ( 167 f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})" 168 ), 169 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 170 exp.SessionUser: lambda *_: "SESSION_USER", 171 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 172 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 173 exp.ApiProperty: lambda *_: "API", 174 exp.ApplicationProperty: lambda *_: "APPLICATION", 175 exp.CatalogProperty: lambda *_: "CATALOG", 176 exp.ComputeProperty: lambda *_: "COMPUTE", 177 exp.DatabaseProperty: lambda *_: "DATABASE", 178 exp.DynamicProperty: lambda *_: "DYNAMIC", 179 exp.EmptyProperty: lambda *_: "EMPTY", 180 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 181 exp.EndStatement: lambda *_: "END", 182 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 183 exp.HandlerProperty: lambda self, e: f"HANDLER {self.sql(e, 'this')}", 184 exp.ParameterStyleProperty: lambda self, e: f"PARAMETER STYLE {self.sql(e, 'this')}", 185 exp.EphemeralColumnConstraint: lambda self, e: ( 186 f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}" 187 ), 188 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 189 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 190 exp.Except: lambda self, e: self.set_operations(e), 191 exp.ExternalProperty: lambda *_: "EXTERNAL", 192 exp.Floor: lambda self, e: self.ceil_floor(e), 193 exp.Get: lambda self, e: self.get_put_sql(e), 194 exp.GlobalProperty: lambda *_: "GLOBAL", 195 exp.HeapProperty: lambda *_: "HEAP", 196 exp.HybridProperty: lambda *_: "HYBRID", 197 exp.IcebergProperty: lambda *_: "ICEBERG", 198 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 199 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 200 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 201 exp.Intersect: lambda self, e: self.set_operations(e), 202 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 203 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)), 204 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 205 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 206 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 207 exp.JSONObject: lambda self, e: self._jsonobject_sql(e), 208 exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e), 209 exp.LanguageProperty: lambda self, e: self.naked_property(e), 210 exp.LocationProperty: lambda self, e: self.naked_property(e), 211 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 212 exp.MaskingProperty: lambda *_: "MASKING", 213 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 214 exp.NetFunc: lambda self, e: f"NET.{self.sql(e, 'this')}", 215 exp.NetworkProperty: lambda *_: "NETWORK", 216 exp.NonClusteredColumnConstraint: lambda self, e: ( 217 f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})" 218 ), 219 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 220 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 221 exp.OnCommitProperty: lambda _, e: ( 222 f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS" 223 ), 224 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 225 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 226 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 227 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 228 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 229 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 230 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 231 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 232 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 233 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 234 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 235 exp.ProjectionPolicyColumnConstraint: lambda self, e: ( 236 f"PROJECTION POLICY {self.sql(e, 'this')}" 237 ), 238 exp.InvisibleColumnConstraint: lambda self, e: "INVISIBLE", 239 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 240 exp.Put: lambda self, e: self.get_put_sql(e), 241 exp.RemoteWithConnectionModelProperty: lambda self, e: ( 242 f"REMOTE WITH CONNECTION {self.sql(e, 'this')}" 243 ), 244 exp.ReturnsProperty: lambda self, e: ( 245 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 246 ), 247 exp.RowAccessProperty: lambda *_: "ROW ACCESS", 248 exp.SafeFunc: lambda self, e: f"SAFE.{self.sql(e, 'this')}", 249 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 250 exp.SecureProperty: lambda *_: "SECURE", 251 exp.SecurityIntegrationProperty: lambda *_: "SECURITY", 252 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 253 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 254 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 255 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 256 exp.SqlReadWriteProperty: lambda _, e: e.name, 257 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 258 exp.StabilityProperty: lambda _, e: e.name, 259 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 260 exp.StreamingTableProperty: lambda *_: "STREAMING", 261 exp.StrictProperty: lambda *_: "STRICT", 262 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 263 exp.TableColumn: lambda self, e: self.sql(e.this), 264 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 265 exp.TemporaryProperty: lambda *_: "TEMPORARY", 266 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 267 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 268 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 269 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 270 exp.TransientProperty: lambda *_: "TRANSIENT", 271 exp.VirtualProperty: lambda *_: "VIRTUAL", 272 exp.TriggerExecute: lambda self, e: f"EXECUTE FUNCTION {self.sql(e, 'this')}", 273 exp.Union: lambda self, e: self.set_operations(e), 274 exp.UnloggedProperty: lambda *_: "UNLOGGED", 275 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 276 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 277 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 278 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 279 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 280 exp.UtcTimestamp: lambda self, e: self.sql( 281 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 282 ), 283 exp.Variadic: lambda self, e: f"VARIADIC {self.sql(e, 'this')}", 284 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 285 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 286 exp.VolatileProperty: lambda *_: "VOLATILE", 287 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 288 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 289 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 290 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 291 exp.ForceProperty: lambda *_: "FORCE", 292 } 293 294 # Whether null ordering is supported in order by 295 # True: Full Support, None: No support, False: No support for certain cases 296 # such as window specifications, aggregate functions etc 297 NULL_ORDERING_SUPPORTED: bool | None = True 298 299 # Window functions that support NULLS FIRST/LAST 300 WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[tuple[type[exp.Expression], ...]] = () 301 302 # Whether ignore nulls is inside the agg or outside. 303 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 304 IGNORE_NULLS_IN_FUNC = False 305 306 # Whether IGNORE NULLS is placed before ORDER BY in the agg. 307 # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS) 308 IGNORE_NULLS_BEFORE_ORDER = True 309 310 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 311 LOCKING_READS_SUPPORTED = False 312 313 # Whether the EXCEPT and INTERSECT operations can return duplicates 314 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 315 316 # Wrap derived values in parens, usually standard but spark doesn't support it 317 WRAP_DERIVED_VALUES = True 318 319 # Whether create function uses an AS before the RETURN 320 CREATE_FUNCTION_RETURN_AS = True 321 322 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 323 MATCHED_BY_SOURCE = True 324 325 # Whether MERGE ... WHEN MATCHED/NOT MATCHED THEN UPDATE/INSERT ... WHERE is supported 326 SUPPORTS_MERGE_WHERE = False 327 328 # Whether the INTERVAL expression works only with values like '1 day' 329 SINGLE_STRING_INTERVAL = False 330 331 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 332 INTERVAL_ALLOWS_PLURAL_FORM = True 333 334 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 335 LIMIT_FETCH = "ALL" 336 337 # Whether limit and fetch allows expresions or just limits 338 LIMIT_ONLY_LITERALS = False 339 340 # Whether a table is allowed to be renamed with a db 341 RENAME_TABLE_WITH_DB = True 342 343 # The separator for grouping sets and rollups 344 GROUPINGS_SEP = "," 345 346 # The string used for creating an index on a table 347 INDEX_ON = "ON" 348 349 # Separator for IN/OUT parameter mode (Oracle uses " " for "IN OUT", PostgreSQL uses "" for "INOUT") 350 INOUT_SEPARATOR = " " 351 352 # Whether join hints should be generated 353 JOIN_HINTS = True 354 355 # Whether directed joins are supported 356 DIRECTED_JOINS = False 357 358 # Whether table hints should be generated 359 TABLE_HINTS = True 360 361 # Whether query hints should be generated 362 QUERY_HINTS = True 363 364 # What kind of separator to use for query hints 365 QUERY_HINT_SEP = ", " 366 367 # Whether comparing against booleans (e.g. x IS TRUE) is supported 368 IS_BOOL_ALLOWED = True 369 370 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 371 DUPLICATE_KEY_UPDATE_WITH_SET = True 372 373 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 374 LIMIT_IS_TOP = False 375 376 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 377 RETURNING_END = True 378 379 # Whether to generate an unquoted value for EXTRACT's date part argument 380 EXTRACT_ALLOWS_QUOTES = True 381 382 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 383 TZ_TO_WITH_TIME_ZONE = False 384 385 # Whether the NVL2 function is supported 386 NVL2_SUPPORTED = True 387 388 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 389 SELECT_KINDS: tuple[str, ...] = ("STRUCT", "VALUE") 390 391 # Whether VALUES statements can be used as derived tables. 392 # MySQL 5 and Redshift do not allow this, so when False, it will convert 393 # SELECT * VALUES into SELECT UNION 394 VALUES_AS_TABLE = True 395 396 # Whether the word COLUMN is included when adding a column with ALTER TABLE 397 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 398 399 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 400 UNNEST_WITH_ORDINALITY = True 401 402 # Whether FILTER (WHERE cond) can be used for conditional aggregation 403 AGGREGATE_FILTER_SUPPORTED = True 404 405 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 406 SEMI_ANTI_JOIN_WITH_SIDE = True 407 408 # Whether to include the type of a computed column in the CREATE DDL 409 COMPUTED_COLUMN_WITH_TYPE = True 410 411 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 412 SUPPORTS_TABLE_COPY = True 413 414 # Whether parentheses are required around the table sample's expression 415 TABLESAMPLE_REQUIRES_PARENS = True 416 417 # Whether a table sample clause's size needs to be followed by the ROWS keyword 418 TABLESAMPLE_SIZE_IS_ROWS = True 419 420 # The keyword(s) to use when generating a sample clause 421 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 422 423 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 424 TABLESAMPLE_WITH_METHOD = True 425 426 # The keyword to use when specifying the seed of a sample clause 427 TABLESAMPLE_SEED_KEYWORD = "SEED" 428 429 # Whether COLLATE is a function instead of a binary operator 430 COLLATE_IS_FUNC = False 431 432 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 433 DATA_TYPE_SPECIFIERS_ALLOWED = False 434 435 # Whether conditions require booleans WHERE x = 0 vs WHERE x 436 ENSURE_BOOLS = False 437 438 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 439 CTE_RECURSIVE_KEYWORD_REQUIRED = True 440 441 # Whether CONCAT requires >1 arguments 442 SUPPORTS_SINGLE_ARG_CONCAT = True 443 444 # Whether LAST_DAY function supports a date part argument 445 LAST_DAY_SUPPORTS_DATE_PART = True 446 447 # Whether named columns are allowed in table aliases 448 SUPPORTS_TABLE_ALIAS_COLUMNS = True 449 450 # Whether named columns are allowed in CTE definitions 451 SUPPORTS_NAMED_CTE_COLUMNS = True 452 453 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 454 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 455 456 # What delimiter to use for separating JSON key/value pairs 457 JSON_KEY_VALUE_PAIR_SEP = ":" 458 459 # INSERT OVERWRITE TABLE x override 460 INSERT_OVERWRITE = " OVERWRITE TABLE" 461 462 # Whether the SELECT .. INTO syntax is used instead of CTAS 463 SUPPORTS_SELECT_INTO = False 464 465 # Whether UNLOGGED tables can be created 466 SUPPORTS_UNLOGGED_TABLES = False 467 468 # Whether the CREATE TABLE LIKE statement is supported 469 SUPPORTS_CREATE_TABLE_LIKE = True 470 471 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 472 SUPPORTS_MODIFY_COLUMN = False 473 474 # Whether ALTER TABLE ... CHANGE COLUMN column-rename-and-redefine syntax is supported 475 SUPPORTS_CHANGE_COLUMN = False 476 477 # Whether the LikeProperty needs to be specified inside of the schema clause 478 LIKE_PROPERTY_INSIDE_SCHEMA = False 479 480 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 481 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 482 MULTI_ARG_DISTINCT = True 483 484 # Whether the JSON extraction operators expect a value of type JSON 485 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 486 487 # Whether bracketed keys like ["foo"] are supported in JSON paths 488 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 489 490 # Whether to escape keys using single quotes in JSON paths 491 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 492 493 # The JSONPathPart expressions supported by this dialect 494 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 495 496 # Whether any(f(x) for x in array) can be implemented by this dialect 497 CAN_IMPLEMENT_ARRAY_ANY = False 498 499 # Whether the function TO_NUMBER is supported 500 SUPPORTS_TO_NUMBER = True 501 502 # Whether EXCLUDE in window specification is supported 503 SUPPORTS_WINDOW_EXCLUDE = False 504 505 # Whether or not set op modifiers apply to the outer set op or select. 506 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 507 # True means limit 1 happens after the set op, False means it it happens on y. 508 SET_OP_MODIFIERS = True 509 510 # Whether parameters from COPY statement are wrapped in parentheses 511 COPY_PARAMS_ARE_WRAPPED = True 512 513 # Whether values of params are set with "=" token or empty space 514 COPY_PARAMS_EQ_REQUIRED = False 515 516 # Whether COPY statement has INTO keyword 517 COPY_HAS_INTO_KEYWORD = True 518 519 # Whether the conditional TRY(expression) function is supported 520 TRY_SUPPORTED = True 521 522 # Whether the UESCAPE syntax in unicode strings is supported 523 SUPPORTS_UESCAPE = True 524 525 # Function used to replace escaped unicode codes in unicode strings 526 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 527 528 # The keyword to use when generating a star projection with excluded columns 529 STAR_EXCEPT = "EXCEPT" 530 531 # The HEX function name 532 HEX_FUNC = "HEX" 533 534 # The keywords to use when prefixing & separating WITH based properties 535 WITH_PROPERTIES_PREFIX = "WITH" 536 537 # Whether to quote the generated expression of exp.JsonPath 538 QUOTE_JSON_PATH = True 539 540 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 541 PAD_FILL_PATTERN_IS_REQUIRED = False 542 543 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 544 SUPPORTS_EXPLODING_PROJECTIONS = True 545 546 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 547 ARRAY_CONCAT_IS_VAR_LEN = True 548 549 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 550 SUPPORTS_CONVERT_TIMEZONE = False 551 552 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 553 SUPPORTS_MEDIAN = True 554 555 # Whether UNIX_SECONDS(timestamp) is supported 556 SUPPORTS_UNIX_SECONDS = False 557 558 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 559 ALTER_SET_WRAPPED = False 560 561 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 562 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 563 # TODO: The normalization should be done by default once we've tested it across all dialects. 564 NORMALIZE_EXTRACT_DATE_PARTS = False 565 566 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 567 PARSE_JSON_NAME: str | None = "PARSE_JSON" 568 569 # The function name of the exp.ArraySize expression 570 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 571 572 # The syntax to use when altering the type of a column 573 ALTER_SET_TYPE = "SET DATA TYPE" 574 575 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 576 # None -> Doesn't support it at all 577 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 578 # True (Postgres) -> Explicitly requires it 579 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 580 581 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 582 SUPPORTS_DECODE_CASE = True 583 584 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 585 SUPPORTS_BETWEEN_FLAGS = False 586 587 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 588 SUPPORTS_LIKE_QUANTIFIERS = True 589 590 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 591 MATCH_AGAINST_TABLE_PREFIX: str | None = None 592 593 # Whether to include the VARIABLE keyword for SET assignments 594 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 595 596 # The keyword to use for default value assignment in DECLARE statements 597 DECLARE_DEFAULT_ASSIGNMENT = "=" 598 599 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 600 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 601 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 602 UPDATE_STATEMENT_SUPPORTS_FROM = True 603 604 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 605 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 606 607 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 608 # - Snowflake: DROP ICEBERG TABLE a.b; 609 # - DuckDB: DROP TABLE a.b; 610 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 611 612 TYPE_MAPPING: t.ClassVar = { 613 exp.DType.DATETIME2: "TIMESTAMP", 614 exp.DType.NCHAR: "CHAR", 615 exp.DType.NVARCHAR: "VARCHAR", 616 exp.DType.MEDIUMTEXT: "TEXT", 617 exp.DType.LONGTEXT: "TEXT", 618 exp.DType.TINYTEXT: "TEXT", 619 exp.DType.BLOB: "VARBINARY", 620 exp.DType.MEDIUMBLOB: "BLOB", 621 exp.DType.LONGBLOB: "BLOB", 622 exp.DType.TINYBLOB: "BLOB", 623 exp.DType.INET: "INET", 624 exp.DType.ROWVERSION: "VARBINARY", 625 exp.DType.SMALLDATETIME: "TIMESTAMP", 626 } 627 628 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 629 630 # mapping of DType to its default parameters, bounds 631 TYPE_PARAM_SETTINGS: t.ClassVar[ 632 dict[exp.DType, tuple[tuple[int, ...], tuple[int | None, ...]]] 633 ] = {} 634 635 TIME_PART_SINGULARS: t.ClassVar = { 636 "MICROSECONDS": "MICROSECOND", 637 "SECONDS": "SECOND", 638 "MINUTES": "MINUTE", 639 "HOURS": "HOUR", 640 "DAYS": "DAY", 641 "WEEKS": "WEEK", 642 "MONTHS": "MONTH", 643 "QUARTERS": "QUARTER", 644 "YEARS": "YEAR", 645 } 646 647 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 648 "cluster": lambda self, e: self.sql(e, "cluster"), 649 "distribute": lambda self, e: self.sql(e, "distribute"), 650 "sort": lambda self, e: self.sql(e, "sort"), 651 **AFTER_HAVING_MODIFIER_TRANSFORMS, 652 } 653 654 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 655 656 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 657 658 PARAMETER_TOKEN = "@" 659 NAMED_PLACEHOLDER_TOKEN = ":" 660 661 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 662 663 PROPERTIES_LOCATION: t.ClassVar = { 664 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 665 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 666 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 667 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 668 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 669 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 671 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 672 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 673 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 674 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 675 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 676 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 677 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 678 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 679 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.ClusterProperty: exp.Properties.Location.POST_SCHEMA, 681 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 682 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 683 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 684 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 685 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 686 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 687 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 688 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 689 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 690 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 691 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 692 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 693 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 694 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 695 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 696 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 697 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 698 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 699 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 700 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 701 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 702 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 703 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 704 exp.HeapProperty: exp.Properties.Location.POST_WITH, 705 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 706 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 707 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 708 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 709 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 710 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 711 exp.JournalProperty: exp.Properties.Location.POST_NAME, 712 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 713 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 714 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 715 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 717 exp.LogProperty: exp.Properties.Location.POST_NAME, 718 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 719 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 720 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 721 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 723 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 724 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 725 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 726 exp.Order: exp.Properties.Location.POST_SCHEMA, 727 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 729 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 730 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 731 exp.Property: exp.Properties.Location.POST_WITH, 732 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 735 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 736 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 737 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 743 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 744 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 745 exp.Set: exp.Properties.Location.POST_SCHEMA, 746 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 747 exp.SetProperty: exp.Properties.Location.POST_CREATE, 748 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 749 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 750 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 751 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 752 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 754 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 758 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 759 exp.Tags: exp.Properties.Location.POST_WITH, 760 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 761 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 762 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 763 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 764 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 765 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 766 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 767 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 768 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 769 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 770 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 771 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 772 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 773 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 774 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 775 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 776 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 777 } 778 779 # Keywords that can't be used as unquoted identifier names 780 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 781 782 # Exprs whose comments are separated from them for better formatting 783 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 784 exp.Command, 785 exp.Create, 786 exp.Describe, 787 exp.Delete, 788 exp.Drop, 789 exp.From, 790 exp.Insert, 791 exp.Join, 792 exp.MultitableInserts, 793 exp.Order, 794 exp.Group, 795 exp.Having, 796 exp.Select, 797 exp.SetOperation, 798 exp.Update, 799 exp.Where, 800 exp.With, 801 ) 802 803 # Exprs that should not have their comments generated in maybe_comment 804 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 805 exp.Binary, 806 exp.SetOperation, 807 ) 808 809 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 810 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 811 exp.Column, 812 exp.Literal, 813 exp.Neg, 814 exp.Paren, 815 ) 816 817 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 818 exp.DType.NVARCHAR, 819 exp.DType.VARCHAR, 820 exp.DType.CHAR, 821 exp.DType.NCHAR, 822 } 823 824 # Exprs that need to have all CTEs under them bubbled up to them 825 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 826 827 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 828 829 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 830 831 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 832 833 __slots__ = ( 834 "pretty", 835 "identify", 836 "normalize", 837 "pad", 838 "_indent", 839 "normalize_functions", 840 "unsupported_level", 841 "max_unsupported", 842 "leading_comma", 843 "max_text_width", 844 "comments", 845 "dialect", 846 "unsupported_messages", 847 "_escaped_quote_end", 848 "_escaped_byte_quote_end", 849 "_escaped_identifier_end", 850 "_next_name", 851 "_identifier_start", 852 "_identifier_end", 853 "_quote_json_path_key_using_brackets", 854 "_dispatch", 855 ) 856 857 def __init__( 858 self, 859 pretty: bool | int | None = None, 860 identify: str | bool = False, 861 normalize: bool = False, 862 pad: int = 2, 863 indent: int = 2, 864 normalize_functions: str | bool | None = None, 865 unsupported_level: ErrorLevel = ErrorLevel.WARN, 866 max_unsupported: int = 3, 867 leading_comma: bool = False, 868 max_text_width: int = 80, 869 comments: bool = True, 870 dialect: DialectType = None, 871 ): 872 import sqlglot 873 import sqlglot.dialects.dialect 874 875 self.pretty = pretty if pretty is not None else sqlglot.pretty 876 self.identify = identify 877 self.normalize = normalize 878 self.pad = pad 879 self._indent = indent 880 self.unsupported_level = unsupported_level 881 self.max_unsupported = max_unsupported 882 self.leading_comma = leading_comma 883 self.max_text_width = max_text_width 884 self.comments = comments 885 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 886 887 # This is both a Dialect property and a Generator argument, so we prioritize the latter 888 self.normalize_functions = ( 889 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 890 ) 891 892 self.unsupported_messages: list[str] = [] 893 self._escaped_quote_end: str = ( 894 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 895 ) 896 self._escaped_byte_quote_end: str = ( 897 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 898 if self.dialect.BYTE_END 899 else "" 900 ) 901 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 902 903 self._next_name = name_sequence("_t") 904 905 self._identifier_start = self.dialect.IDENTIFIER_START 906 self._identifier_end = self.dialect.IDENTIFIER_END 907 908 self._quote_json_path_key_using_brackets = True 909 910 cls = type(self) 911 dispatch = _DISPATCH_CACHE.get(cls) 912 if dispatch is None: 913 dispatch = _build_dispatch(cls) 914 _DISPATCH_CACHE[cls] = dispatch 915 self._dispatch = dispatch 916 917 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 918 """ 919 Generates the SQL string corresponding to the given syntax tree. 920 921 Args: 922 expression: The syntax tree. 923 copy: Whether to copy the expression. The generator performs mutations so 924 it is safer to copy. 925 926 Returns: 927 The SQL string corresponding to `expression`. 928 """ 929 if copy: 930 expression = expression.copy() 931 932 expression = self.preprocess(expression) 933 934 self.unsupported_messages = [] 935 sql = self.sql(expression).strip() 936 937 if self.pretty: 938 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 939 940 if self.unsupported_level == ErrorLevel.IGNORE: 941 return sql 942 943 if self.unsupported_level == ErrorLevel.WARN: 944 for msg in self.unsupported_messages: 945 logger.warning(msg) 946 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 947 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 948 949 return sql 950 951 def preprocess(self, expression: exp.Expr) -> exp.Expr: 952 """Apply generic preprocessing transformations to a given expression.""" 953 expression = self._move_ctes_to_top_level(expression) 954 955 if self.ENSURE_BOOLS: 956 import sqlglot.transforms 957 958 expression = sqlglot.transforms.ensure_bools(expression) 959 960 return expression 961 962 def _move_ctes_to_top_level(self, expression: E) -> E: 963 if ( 964 not expression.parent 965 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 966 and any(node.parent is not expression for node in expression.find_all(exp.With)) 967 ): 968 import sqlglot.transforms 969 970 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 971 return expression 972 973 def unsupported(self, message: str) -> None: 974 if self.unsupported_level == ErrorLevel.IMMEDIATE: 975 raise UnsupportedError(message) 976 self.unsupported_messages.append(message) 977 978 def sep(self, sep: str = " ") -> str: 979 return f"{sep.strip()}\n" if self.pretty else sep 980 981 def seg(self, sql: str, sep: str = " ") -> str: 982 return f"{self.sep(sep)}{sql}" 983 984 def sanitize_comment(self, comment: str) -> str: 985 comment = " " + comment if comment[0].strip() else comment 986 comment = comment + " " if comment[-1].strip() else comment 987 988 # Escape block comment markers to prevent premature closure or unintended nesting. 989 # This is necessary because single-line comments (--) are converted to block comments 990 # (/* */) on output, and any */ in the original text would close the comment early. 991 comment = comment.replace("*/", "* /").replace("/*", "/ *") 992 993 return comment 994 995 def maybe_comment( 996 self, 997 sql: str, 998 expression: exp.Expr | None = None, 999 comments: list[str] | None = None, 1000 separated: bool = False, 1001 ) -> str: 1002 comments = ( 1003 ((expression and expression.comments) if comments is None else comments) # type: ignore 1004 if self.comments 1005 else None 1006 ) 1007 1008 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1009 return sql 1010 1011 comments_sql = " ".join( 1012 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 1013 ) 1014 1015 if not comments_sql: 1016 return sql 1017 1018 comments_sql = self._replace_line_breaks(comments_sql) 1019 1020 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1021 return ( 1022 f"{self.sep()}{comments_sql}{sql}" 1023 if not sql or sql[0].isspace() 1024 else f"{comments_sql}{self.sep()}{sql}" 1025 ) 1026 1027 return f"{sql} {comments_sql}" 1028 1029 def wrap(self, expression: exp.Expr | str) -> str: 1030 this_sql = ( 1031 self.sql(expression) 1032 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1033 else self.sql(expression, "this") 1034 ) 1035 if not this_sql: 1036 return "()" 1037 1038 this_sql = self.indent(this_sql, level=1, pad=0) 1039 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1040 1041 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1042 original = self.identify 1043 self.identify = False 1044 result = func(*args, **kwargs) 1045 self.identify = original 1046 return result 1047 1048 def normalize_func(self, name: str) -> str: 1049 if self.normalize_functions == "upper" or self.normalize_functions is True: 1050 return name.upper() 1051 if self.normalize_functions == "lower": 1052 return name.lower() 1053 return name 1054 1055 def indent( 1056 self, 1057 sql: str, 1058 level: int = 0, 1059 pad: int | None = None, 1060 skip_first: bool = False, 1061 skip_last: bool = False, 1062 ) -> str: 1063 if not self.pretty or not sql: 1064 return sql 1065 1066 pad = self.pad if pad is None else pad 1067 lines = sql.split("\n") 1068 1069 return "\n".join( 1070 ( 1071 line 1072 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1073 else f"{' ' * (level * self._indent + pad)}{line}" 1074 ) 1075 for i, line in enumerate(lines) 1076 ) 1077 1078 def sql( 1079 self, 1080 expression: str | exp.Expr | None, 1081 key: str | None = None, 1082 comment: bool = True, 1083 ) -> str: 1084 if not expression: 1085 return "" 1086 1087 if isinstance(expression, str): 1088 return expression 1089 1090 if key: 1091 value = expression.args.get(key) 1092 if value: 1093 return self.sql(value) 1094 return "" 1095 1096 handler = self._dispatch.get(expression.__class__) 1097 1098 if handler: 1099 sql = handler(self, expression) 1100 elif isinstance(expression, exp.Func): 1101 sql = self.function_fallback_sql(expression) 1102 elif isinstance(expression, exp.Property): 1103 sql = self.property_sql(expression) 1104 else: 1105 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1106 1107 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1108 1109 def uncache_sql(self, expression: exp.Uncache) -> str: 1110 table = self.sql(expression, "this") 1111 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1112 return f"UNCACHE TABLE{exists_sql} {table}" 1113 1114 def cache_sql(self, expression: exp.Cache) -> str: 1115 lazy = " LAZY" if expression.args.get("lazy") else "" 1116 table = self.sql(expression, "this") 1117 options = expression.args.get("options") 1118 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1119 sql = self.sql(expression, "expression") 1120 sql = f" AS{self.sep()}{sql}" if sql else "" 1121 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1122 return self.prepend_ctes(expression, sql) 1123 1124 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1125 default = "DEFAULT " if expression.args.get("default") else "" 1126 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1127 1128 def column_parts(self, expression: exp.Column) -> str: 1129 return ".".join( 1130 self.sql(part) 1131 for part in ( 1132 expression.args.get("catalog"), 1133 expression.args.get("db"), 1134 expression.args.get("table"), 1135 expression.args.get("this"), 1136 ) 1137 if part 1138 ) 1139 1140 def column_sql(self, expression: exp.Column) -> str: 1141 join_mark = " (+)" if expression.args.get("join_mark") else "" 1142 1143 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1144 join_mark = "" 1145 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1146 1147 return f"{self.column_parts(expression)}{join_mark}" 1148 1149 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1150 return self.column_sql(expression) 1151 1152 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1153 this = self.sql(expression, "this") 1154 this = f" {this}" if this else "" 1155 position = self.sql(expression, "position") 1156 return f"{position}{this}" 1157 1158 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1159 column = self.sql(expression, "this") 1160 kind = self.sql(expression, "kind") 1161 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1162 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1163 kind = f"{sep}{kind}" if kind else "" 1164 constraints = f" {constraints}" if constraints else "" 1165 position = self.sql(expression, "position") 1166 position = f" {position}" if position else "" 1167 1168 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1169 kind = "" 1170 1171 return f"{exists}{column}{kind}{constraints}{position}" 1172 1173 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1174 this = self.sql(expression, "this") 1175 kind_sql = self.sql(expression, "kind").strip() 1176 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1177 1178 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1179 this = self.sql(expression, "this") 1180 if expression.args.get("not_null"): 1181 persisted = " PERSISTED NOT NULL" 1182 elif expression.args.get("persisted"): 1183 persisted = " PERSISTED" 1184 else: 1185 persisted = "" 1186 1187 return f"AS {this}{persisted}" 1188 1189 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1190 return self.token_sql(TokenType.AUTO_INCREMENT) 1191 1192 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1193 if isinstance(expression.this, list): 1194 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1195 else: 1196 this = self.sql(expression, "this") 1197 1198 return f"COMPRESS {this}" 1199 1200 def generatedasidentitycolumnconstraint_sql( 1201 self, expression: exp.GeneratedAsIdentityColumnConstraint 1202 ) -> str: 1203 this = "" 1204 if expression.this is not None: 1205 on_null = " ON NULL" if expression.args.get("on_null") else "" 1206 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1207 1208 start = expression.args.get("start") 1209 start = f"START WITH {start}" if start else "" 1210 increment = expression.args.get("increment") 1211 increment = f" INCREMENT BY {increment}" if increment else "" 1212 minvalue = expression.args.get("minvalue") 1213 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1214 maxvalue = expression.args.get("maxvalue") 1215 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1216 cycle = expression.args.get("cycle") 1217 cycle_sql = "" 1218 1219 if cycle is not None: 1220 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1221 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1222 1223 sequence_opts = "" 1224 if start or increment or cycle_sql: 1225 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1226 sequence_opts = f" ({sequence_opts.strip()})" 1227 1228 expr = self.sql(expression, "expression") 1229 expr = f"({expr})" if expr else "IDENTITY" 1230 1231 return f"GENERATED{this} AS {expr}{sequence_opts}" 1232 1233 def generatedasrowcolumnconstraint_sql( 1234 self, expression: exp.GeneratedAsRowColumnConstraint 1235 ) -> str: 1236 start = "START" if expression.args.get("start") else "END" 1237 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1238 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1239 1240 def periodforsystemtimeconstraint_sql( 1241 self, expression: exp.PeriodForSystemTimeConstraint 1242 ) -> str: 1243 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1244 1245 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1246 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1247 1248 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1249 desc = expression.args.get("desc") 1250 if desc is not None: 1251 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1252 options = self.expressions(expression, key="options", flat=True, sep=" ") 1253 options = f" {options}" if options else "" 1254 return f"PRIMARY KEY{options}" 1255 1256 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1257 this = self.sql(expression, "this") 1258 this = f" {this}" if this else "" 1259 index_type = expression.args.get("index_type") 1260 index_type = f" USING {index_type}" if index_type else "" 1261 on_conflict = self.sql(expression, "on_conflict") 1262 on_conflict = f" {on_conflict}" if on_conflict else "" 1263 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1264 options = self.expressions(expression, key="options", flat=True, sep=" ") 1265 options = f" {options}" if options else "" 1266 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1267 1268 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1269 input_ = expression.args.get("input_") 1270 output = expression.args.get("output") 1271 variadic = expression.args.get("variadic") 1272 1273 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1274 if variadic: 1275 return "VARIADIC" 1276 1277 if input_ and output: 1278 return f"IN{self.INOUT_SEPARATOR}OUT" 1279 if input_: 1280 return "IN" 1281 if output: 1282 return "OUT" 1283 1284 return "" 1285 1286 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1287 return self.sql(expression, "this") 1288 1289 def create_sql(self, expression: exp.Create) -> str: 1290 kind = self.sql(expression, "kind") 1291 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1292 1293 properties = expression.args.get("properties") 1294 1295 if ( 1296 kind == "TRIGGER" 1297 and properties 1298 and properties.expressions 1299 and isinstance(properties.expressions[0], exp.TriggerProperties) 1300 and properties.expressions[0].args.get("constraint") 1301 ): 1302 kind = f"CONSTRAINT {kind}" 1303 1304 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1305 1306 this = self.createable_sql(expression, properties_locs) 1307 1308 properties_sql = "" 1309 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1310 exp.Properties.Location.POST_WITH 1311 ): 1312 props_ast = exp.Properties( 1313 expressions=[ 1314 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1315 *properties_locs[exp.Properties.Location.POST_WITH], 1316 ] 1317 ) 1318 props_ast.parent = expression 1319 properties_sql = self.sql(props_ast) 1320 1321 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1322 properties_sql = self.sep() + properties_sql 1323 elif not self.pretty: 1324 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1325 properties_sql = f" {properties_sql}" 1326 1327 begin = " BEGIN" if expression.args.get("begin") else "" 1328 1329 expression_sql = self.sql(expression, "expression") 1330 if expression_sql: 1331 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1332 1333 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1334 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1335 ): 1336 postalias_props_sql = "" 1337 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1338 postalias_props_sql = self.properties( 1339 exp.Properties( 1340 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1341 ), 1342 wrapped=False, 1343 ) 1344 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1345 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1346 1347 postindex_props_sql = "" 1348 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1349 postindex_props_sql = self.properties( 1350 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1351 wrapped=False, 1352 prefix=" ", 1353 ) 1354 1355 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1356 indexes = f" {indexes}" if indexes else "" 1357 index_sql = indexes + postindex_props_sql 1358 1359 replace = " OR REPLACE" if expression.args.get("replace") else "" 1360 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1361 unique = " UNIQUE" if expression.args.get("unique") else "" 1362 1363 clustered = expression.args.get("clustered") 1364 if clustered is None: 1365 clustered_sql = "" 1366 elif clustered: 1367 clustered_sql = " CLUSTERED COLUMNSTORE" 1368 else: 1369 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1370 1371 postcreate_props_sql = "" 1372 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1373 postcreate_props_sql = self.properties( 1374 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1375 sep=" ", 1376 prefix=" ", 1377 wrapped=False, 1378 ) 1379 1380 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1381 1382 postexpression_props_sql = "" 1383 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1384 postexpression_props_sql = self.properties( 1385 exp.Properties( 1386 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1387 ), 1388 sep=" ", 1389 prefix=" ", 1390 wrapped=False, 1391 ) 1392 1393 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1394 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1395 no_schema_binding = ( 1396 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1397 ) 1398 1399 clone = self.sql(expression, "clone") 1400 clone = f" {clone}" if clone else "" 1401 1402 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1403 properties_expression = f"{expression_sql}{properties_sql}" 1404 else: 1405 properties_expression = f"{properties_sql}{expression_sql}" 1406 1407 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1408 return self.prepend_ctes(expression, expression_sql) 1409 1410 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1411 start = self.sql(expression, "start") 1412 start = f"START WITH {start}" if start else "" 1413 increment = self.sql(expression, "increment") 1414 increment = f" INCREMENT BY {increment}" if increment else "" 1415 minvalue = self.sql(expression, "minvalue") 1416 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1417 maxvalue = self.sql(expression, "maxvalue") 1418 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1419 owned = self.sql(expression, "owned") 1420 owned = f" OWNED BY {owned}" if owned else "" 1421 1422 cache = expression.args.get("cache") 1423 if cache is None: 1424 cache_str = "" 1425 elif cache is True: 1426 cache_str = " CACHE" 1427 else: 1428 cache_str = f" CACHE {cache}" 1429 1430 options = self.expressions(expression, key="options", flat=True, sep=" ") 1431 options = f" {options}" if options else "" 1432 1433 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1434 1435 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1436 timing = expression.args.get("timing", "") 1437 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1438 timing_events = f"{timing} {events}".strip() if timing or events else "" 1439 1440 parts = [timing_events, "ON", self.sql(expression, "table")] 1441 1442 if referenced_table := expression.args.get("referenced_table"): 1443 parts.extend(["FROM", self.sql(referenced_table)]) 1444 1445 if deferrable := expression.args.get("deferrable"): 1446 parts.append(deferrable) 1447 1448 if initially := expression.args.get("initially"): 1449 parts.append(f"INITIALLY {initially}") 1450 1451 if referencing := expression.args.get("referencing"): 1452 parts.append(self.sql(referencing)) 1453 1454 if for_each := expression.args.get("for_each"): 1455 parts.append(f"FOR EACH {for_each}") 1456 1457 if when := expression.args.get("when"): 1458 parts.append(f"WHEN ({self.sql(when)})") 1459 1460 parts.append(self.sql(expression, "execute")) 1461 1462 return self.sep().join(parts) 1463 1464 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1465 parts = [] 1466 1467 if old_alias := expression.args.get("old"): 1468 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1469 1470 if new_alias := expression.args.get("new"): 1471 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1472 1473 return f"REFERENCING {' '.join(parts)}" 1474 1475 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1476 columns = expression.args.get("columns") 1477 if columns: 1478 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1479 1480 return self.sql(expression, "this") 1481 1482 def clone_sql(self, expression: exp.Clone) -> str: 1483 this = self.sql(expression, "this") 1484 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1485 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1486 return f"{shallow}{keyword} {this}" 1487 1488 def describe_sql(self, expression: exp.Describe) -> str: 1489 style = expression.args.get("style") 1490 style = f" {style}" if style else "" 1491 partition = self.sql(expression, "partition") 1492 partition = f" {partition}" if partition else "" 1493 format = self.sql(expression, "format") 1494 format = f" {format}" if format else "" 1495 as_json = " AS JSON" if expression.args.get("as_json") else "" 1496 1497 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1498 1499 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1500 tag = self.sql(expression, "tag") 1501 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1502 1503 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1504 with_ = self.sql(expression, "with_") 1505 if with_: 1506 sql = f"{with_}{self.sep()}{sql}" 1507 return sql 1508 1509 def with_sql(self, expression: exp.With) -> str: 1510 sql = self.expressions(expression, flat=True) 1511 recursive = ( 1512 "RECURSIVE " 1513 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1514 else "" 1515 ) 1516 search = self.sql(expression, "search") 1517 search = f" {search}" if search else "" 1518 1519 return f"WITH {recursive}{sql}{search}" 1520 1521 def cte_sql(self, expression: exp.CTE) -> str: 1522 alias = expression.args.get("alias") 1523 if alias: 1524 alias.add_comments(expression.pop_comments()) 1525 1526 alias_sql = self.sql(expression, "alias") 1527 1528 materialized = expression.args.get("materialized") 1529 if materialized is False: 1530 materialized = "NOT MATERIALIZED " 1531 elif materialized: 1532 materialized = "MATERIALIZED " 1533 1534 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1535 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1536 1537 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1538 1539 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1540 alias = self.sql(expression, "this") 1541 columns = self.expressions(expression, key="columns", flat=True) 1542 columns = f"({columns})" if columns else "" 1543 1544 if ( 1545 columns 1546 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1547 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1548 ): 1549 columns = "" 1550 self.unsupported("Named columns are not supported in table alias.") 1551 1552 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1553 alias = self._next_name() 1554 1555 return f"{alias}{columns}" 1556 1557 def bitstring_sql(self, expression: exp.BitString) -> str: 1558 this = self.sql(expression, "this") 1559 if self.dialect.BIT_START: 1560 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1561 return f"{int(this, 2)}" 1562 1563 def hexstring_sql( 1564 self, expression: exp.HexString, binary_function_repr: str | None = None 1565 ) -> str: 1566 this = self.sql(expression, "this") 1567 is_integer_type = expression.args.get("is_integer") 1568 1569 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1570 not self.dialect.HEX_START and not binary_function_repr 1571 ): 1572 # Integer representation will be returned if: 1573 # - The read dialect treats the hex value as integer literal but not the write 1574 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1575 return f"{int(this, 16)}" 1576 1577 if not is_integer_type: 1578 # Read dialect treats the hex value as BINARY/BLOB 1579 if binary_function_repr: 1580 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1581 return self.func(binary_function_repr, exp.Literal.string(this)) 1582 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1583 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1584 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1585 1586 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1587 1588 def bytestring_sql(self, expression: exp.ByteString) -> str: 1589 this = self.sql(expression, "this") 1590 if self.dialect.BYTE_START: 1591 escaped_byte_string = self.escape_str( 1592 this, 1593 escape_backslash=False, 1594 delimiter=self.dialect.BYTE_END, 1595 escaped_delimiter=self._escaped_byte_quote_end, 1596 is_byte_string=True, 1597 ) 1598 is_bytes = expression.args.get("is_bytes", False) 1599 delimited_byte_string = ( 1600 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1601 ) 1602 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1603 return self.sql( 1604 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1605 ) 1606 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1607 return self.sql( 1608 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1609 ) 1610 1611 return delimited_byte_string 1612 1613 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1614 return self.sql(exp.Literal.string(this)) 1615 1616 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1617 return "" 1618 1619 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1620 this = self.sql(expression, "this") 1621 escape = expression.args.get("escape") 1622 1623 if self.dialect.UNICODE_START: 1624 escape_substitute = r"\\\1" 1625 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1626 else: 1627 escape_substitute = r"\\u\1" 1628 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1629 1630 if escape: 1631 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1632 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1633 else: 1634 escape_pattern = ESCAPED_UNICODE_RE 1635 escape_sql = "" 1636 1637 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1638 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1639 1640 return f"{left_quote}{this}{right_quote}{escape_sql}" 1641 1642 def rawstring_sql(self, expression: exp.RawString) -> str: 1643 string = expression.this 1644 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1645 string = string.replace("\\", "\\\\") 1646 1647 string = self.escape_str(string, escape_backslash=False) 1648 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1649 1650 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1651 this = self.sql(expression, "this") 1652 specifier = self.sql(expression, "expression") 1653 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1654 return f"{this}{specifier}" 1655 1656 def datatype_param_bound_limiter( 1657 self, 1658 expression: exp.DataType, 1659 type_value: exp.DType, 1660 defaults: tuple[int, ...], 1661 bounds: tuple[int | None, ...], 1662 ) -> exp.DataType: 1663 params = expression.expressions 1664 1665 if not params: 1666 if defaults: 1667 expression.set( 1668 "expressions", 1669 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1670 ) 1671 return expression 1672 1673 if not bounds: 1674 return expression 1675 1676 for i, param in enumerate(params): 1677 bound = bounds[i] if i < len(bounds) else None 1678 if bound is None: 1679 continue 1680 1681 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1682 if ( 1683 isinstance(param_value, exp.Literal) 1684 and param_value.is_number 1685 and int(param_value.to_py()) > bound 1686 ): 1687 self.unsupported( 1688 f"{type_value.value} parameter {param_value.name} exceeds " 1689 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1690 ) 1691 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1692 1693 return expression 1694 1695 def datatype_sql(self, expression: exp.DataType) -> str: 1696 nested = "" 1697 values = "" 1698 1699 expr_nested = expression.args.get("nested") 1700 type_value = expression.this 1701 1702 if ( 1703 not expr_nested 1704 and isinstance(type_value, exp.DType) 1705 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1706 ): 1707 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1708 1709 interior = ( 1710 self.expressions( 1711 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1712 ) 1713 if expr_nested and self.pretty 1714 else self.expressions(expression, flat=True) 1715 ) 1716 1717 if type_value in self.UNSUPPORTED_TYPES: 1718 self.unsupported( 1719 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1720 ) 1721 1722 type_sql: t.Any = "" 1723 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1724 type_sql = self.sql(expression, "kind") 1725 elif type_value == exp.DType.CHARACTER_SET: 1726 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1727 else: 1728 type_sql = ( 1729 self.TYPE_MAPPING.get(type_value, type_value.value) 1730 if isinstance(type_value, exp.DType) 1731 else type_value 1732 ) 1733 1734 if interior: 1735 if expr_nested: 1736 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1737 if expression.args.get("values") is not None: 1738 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1739 values = self.expressions(expression, key="values", flat=True) 1740 values = f"{delimiters[0]}{values}{delimiters[1]}" 1741 elif type_value == exp.DType.INTERVAL: 1742 nested = f" {interior}" 1743 else: 1744 nested = f"({interior})" 1745 1746 type_sql = f"{type_sql}{nested}{values}" 1747 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1748 exp.DType.TIMETZ, 1749 exp.DType.TIMESTAMPTZ, 1750 ): 1751 type_sql = f"{type_sql} WITH TIME ZONE" 1752 1753 collate = self.sql(expression, "collate") 1754 if collate: 1755 type_sql = f"{type_sql} COLLATE {collate}" 1756 1757 return type_sql 1758 1759 def directory_sql(self, expression: exp.Directory) -> str: 1760 local = "LOCAL " if expression.args.get("local") else "" 1761 row_format = self.sql(expression, "row_format") 1762 row_format = f" {row_format}" if row_format else "" 1763 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1764 1765 def delete_sql(self, expression: exp.Delete) -> str: 1766 hint = self.sql(expression, "hint") 1767 this = self.sql(expression, "this") 1768 this = f" FROM {this}" if this else "" 1769 using = self.expressions(expression, key="using") 1770 using = f" USING {using}" if using else "" 1771 cluster = self.sql(expression, "cluster") 1772 cluster = f" {cluster}" if cluster else "" 1773 where = self.sql(expression, "where") 1774 returning = self.sql(expression, "returning") 1775 order = self.sql(expression, "order") 1776 limit = self.sql(expression, "limit") 1777 tables = self.expressions(expression, key="tables") 1778 tables = f" {tables}" if tables else "" 1779 if self.RETURNING_END: 1780 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1781 else: 1782 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1783 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1784 1785 def drop_sql(self, expression: exp.Drop) -> str: 1786 this = self.sql(expression, "this") 1787 expressions = self.expressions(expression, flat=True) 1788 expressions = f" ({expressions})" if expressions else "" 1789 kind = expression.args["kind"] 1790 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1791 iceberg = ( 1792 " ICEBERG" 1793 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1794 else "" 1795 ) 1796 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1797 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1798 on_cluster = self.sql(expression, "cluster") 1799 on_cluster = f" {on_cluster}" if on_cluster else "" 1800 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1801 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1802 cascade = " CASCADE" if expression.args.get("cascade") else "" 1803 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1804 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1805 purge = " PURGE" if expression.args.get("purge") else "" 1806 sync = " SYNC" if expression.args.get("sync") else "" 1807 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}" 1808 1809 def set_operation(self, expression: exp.SetOperation) -> str: 1810 op_type = type(expression) 1811 op_name = op_type.key.upper() 1812 1813 distinct = expression.args.get("distinct") 1814 if ( 1815 distinct is False 1816 and op_type in (exp.Except, exp.Intersect) 1817 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1818 ): 1819 self.unsupported(f"{op_name} ALL is not supported") 1820 1821 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1822 1823 if distinct is None: 1824 distinct = default_distinct 1825 if distinct is None: 1826 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1827 1828 if distinct is default_distinct: 1829 distinct_or_all = "" 1830 else: 1831 distinct_or_all = " DISTINCT" if distinct else " ALL" 1832 1833 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1834 side_kind = f"{side_kind} " if side_kind else "" 1835 1836 by_name = " BY NAME" if expression.args.get("by_name") else "" 1837 on = self.expressions(expression, key="on", flat=True) 1838 on = f" ON ({on})" if on else "" 1839 1840 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1841 1842 def set_operations(self, expression: exp.SetOperation) -> str: 1843 if not self.SET_OP_MODIFIERS: 1844 limit = expression.args.get("limit") 1845 order = expression.args.get("order") 1846 1847 if limit or order: 1848 select = self._move_ctes_to_top_level( 1849 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1850 ) 1851 1852 if limit: 1853 select = select.limit(limit.pop(), copy=False) 1854 if order: 1855 select = select.order_by(order.pop(), copy=False) 1856 return self.sql(select) 1857 1858 sqls: list[str] = [] 1859 stack: list[str | exp.Expr] = [expression] 1860 1861 while stack: 1862 node = stack.pop() 1863 1864 if isinstance(node, exp.SetOperation): 1865 stack.append(node.expression) 1866 stack.append( 1867 self.maybe_comment( 1868 self.set_operation(node), comments=node.comments, separated=True 1869 ) 1870 ) 1871 stack.append(node.this) 1872 else: 1873 sqls.append(self.sql(node)) 1874 1875 this = self.sep().join(sqls) 1876 this = self.query_modifiers(expression, this) 1877 return self.prepend_ctes(expression, this) 1878 1879 def fetch_sql(self, expression: exp.Fetch) -> str: 1880 direction = expression.args.get("direction") 1881 direction = f" {direction}" if direction else "" 1882 count = self.sql(expression, "count") 1883 count = f" {count}" if count else "" 1884 limit_options = self.sql(expression, "limit_options") 1885 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1886 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1887 1888 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1889 percent = " PERCENT" if expression.args.get("percent") else "" 1890 rows = " ROWS" if expression.args.get("rows") else "" 1891 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1892 if not with_ties and rows: 1893 with_ties = " ONLY" 1894 return f"{percent}{rows}{with_ties}" 1895 1896 def filter_sql(self, expression: exp.Filter) -> str: 1897 if self.AGGREGATE_FILTER_SUPPORTED: 1898 this = self.sql(expression, "this") 1899 where = self.sql(expression, "expression").strip() 1900 return f"{this} FILTER({where})" 1901 1902 agg = expression.this 1903 agg_arg = agg.this 1904 cond = expression.expression.this 1905 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1906 return self.sql(agg) 1907 1908 def hint_sql(self, expression: exp.Hint) -> str: 1909 if not self.QUERY_HINTS: 1910 self.unsupported("Hints are not supported") 1911 return "" 1912 1913 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1914 1915 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1916 using = self.sql(expression, "using") 1917 using = f" USING {using}" if using else "" 1918 columns = self.expressions(expression, key="columns", flat=True) 1919 columns = f"({columns})" if columns else "" 1920 partition_by = self.expressions(expression, key="partition_by", flat=True) 1921 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1922 where = self.sql(expression, "where") 1923 include = self.expressions(expression, key="include", flat=True) 1924 if include: 1925 include = f" INCLUDE ({include})" 1926 with_storage = self.expressions(expression, key="with_storage", flat=True) 1927 with_storage = f" WITH ({with_storage})" if with_storage else "" 1928 tablespace = self.sql(expression, "tablespace") 1929 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1930 on = self.sql(expression, "on") 1931 on = f" ON {on}" if on else "" 1932 1933 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1934 1935 def index_sql(self, expression: exp.Index) -> str: 1936 unique = "UNIQUE " if expression.args.get("unique") else "" 1937 primary = "PRIMARY " if expression.args.get("primary") else "" 1938 amp = "AMP " if expression.args.get("amp") else "" 1939 name = self.sql(expression, "this") 1940 name = f"{name} " if name else "" 1941 table = self.sql(expression, "table") 1942 table = f"{self.INDEX_ON} {table}" if table else "" 1943 1944 index = "INDEX " if not table else "" 1945 1946 params = self.sql(expression, "params") 1947 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1948 1949 def identifier_sql(self, expression: exp.Identifier) -> str: 1950 text = expression.name 1951 lower = text.lower() 1952 quoted = expression.quoted 1953 text = lower if self.normalize and not quoted else text 1954 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1955 if ( 1956 quoted 1957 or self.dialect.can_quote(expression, self.identify) 1958 or lower in self.RESERVED_KEYWORDS 1959 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1960 ): 1961 text = ( 1962 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1963 ) 1964 return text 1965 1966 def hex_sql(self, expression: exp.Hex) -> str: 1967 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1968 if self.dialect.HEX_LOWERCASE: 1969 text = self.func("LOWER", text) 1970 1971 return text 1972 1973 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1974 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1975 if not self.dialect.HEX_LOWERCASE: 1976 text = self.func("LOWER", text) 1977 return text 1978 1979 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1980 input_format = self.sql(expression, "input_format") 1981 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1982 output_format = self.sql(expression, "output_format") 1983 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1984 return self.sep().join((input_format, output_format)) 1985 1986 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1987 string = self.sql(exp.Literal.string(expression.name)) 1988 return f"{prefix}{string}" 1989 1990 def partition_sql(self, expression: exp.Partition) -> str: 1991 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1992 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1993 1994 def properties_sql(self, expression: exp.Properties) -> str: 1995 root_properties = [] 1996 with_properties = [] 1997 1998 for p in expression.expressions: 1999 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2000 if p_loc == exp.Properties.Location.POST_WITH: 2001 with_properties.append(p) 2002 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2003 root_properties.append(p) 2004 2005 root_props_ast = exp.Properties(expressions=root_properties) 2006 root_props_ast.parent = expression.parent 2007 2008 with_props_ast = exp.Properties(expressions=with_properties) 2009 with_props_ast.parent = expression.parent 2010 2011 root_props = self.root_properties(root_props_ast) 2012 with_props = self.with_properties(with_props_ast) 2013 2014 if root_props and with_props and not self.pretty: 2015 with_props = " " + with_props 2016 2017 return root_props + with_props 2018 2019 def root_properties(self, properties: exp.Properties) -> str: 2020 if properties.expressions: 2021 return self.expressions(properties, indent=False, sep=" ") 2022 return "" 2023 2024 def properties( 2025 self, 2026 properties: exp.Properties, 2027 prefix: str = "", 2028 sep: str = ", ", 2029 suffix: str = "", 2030 wrapped: bool = True, 2031 ) -> str: 2032 if properties.expressions: 2033 expressions = self.expressions(properties, sep=sep, indent=False) 2034 if expressions: 2035 expressions = self.wrap(expressions) if wrapped else expressions 2036 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2037 return "" 2038 2039 def with_properties(self, properties: exp.Properties) -> str: 2040 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2041 2042 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2043 properties_locs = defaultdict(list) 2044 for p in properties.expressions: 2045 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2046 if p_loc != exp.Properties.Location.UNSUPPORTED: 2047 properties_locs[p_loc].append(p) 2048 else: 2049 self.unsupported(f"Unsupported property {p.key}") 2050 2051 return properties_locs 2052 2053 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2054 if isinstance(expression.this, exp.Dot): 2055 return self.sql(expression, "this") 2056 return f"'{expression.name}'" if string_key else expression.name 2057 2058 def property_sql(self, expression: exp.Property) -> str: 2059 property_cls = expression.__class__ 2060 if property_cls == exp.Property: 2061 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2062 2063 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2064 if not property_name: 2065 self.unsupported(f"Unsupported property {expression.key}") 2066 2067 return f"{property_name}={self.sql(expression, 'this')}" 2068 2069 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2070 return f"UUID {self.sql(expression, 'this')}" 2071 2072 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2073 if self.SUPPORTS_CREATE_TABLE_LIKE: 2074 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2075 options = f" {options}" if options else "" 2076 2077 like = f"LIKE {self.sql(expression, 'this')}{options}" 2078 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2079 like = f"({like})" 2080 2081 return like 2082 2083 if expression.expressions: 2084 self.unsupported("Transpilation of LIKE property options is unsupported") 2085 2086 select = exp.select("*").from_(expression.this).limit(0) 2087 return f"AS {self.sql(select)}" 2088 2089 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2090 no = "NO " if expression.args.get("no") else "" 2091 protection = " PROTECTION" if expression.args.get("protection") else "" 2092 return f"{no}FALLBACK{protection}" 2093 2094 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2095 no = "NO " if expression.args.get("no") else "" 2096 local = expression.args.get("local") 2097 local = f"{local} " if local else "" 2098 dual = "DUAL " if expression.args.get("dual") else "" 2099 before = "BEFORE " if expression.args.get("before") else "" 2100 after = "AFTER " if expression.args.get("after") else "" 2101 return f"{no}{local}{dual}{before}{after}JOURNAL" 2102 2103 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2104 freespace = self.sql(expression, "this") 2105 percent = " PERCENT" if expression.args.get("percent") else "" 2106 return f"FREESPACE={freespace}{percent}" 2107 2108 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2109 if expression.args.get("default"): 2110 property = "DEFAULT" 2111 elif expression.args.get("on"): 2112 property = "ON" 2113 else: 2114 property = "OFF" 2115 return f"CHECKSUM={property}" 2116 2117 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2118 if expression.args.get("no"): 2119 return "NO MERGEBLOCKRATIO" 2120 if expression.args.get("default"): 2121 return "DEFAULT MERGEBLOCKRATIO" 2122 2123 percent = " PERCENT" if expression.args.get("percent") else "" 2124 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2125 2126 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2127 expressions = self.expressions(expression, flat=True) 2128 expressions = f"({expressions})" if expressions else "" 2129 return f"USING {self.sql(expression, 'this')}{expressions}" 2130 2131 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2132 default = expression.args.get("default") 2133 minimum = expression.args.get("minimum") 2134 maximum = expression.args.get("maximum") 2135 if default or minimum or maximum: 2136 if default: 2137 prop = "DEFAULT" 2138 elif minimum: 2139 prop = "MINIMUM" 2140 else: 2141 prop = "MAXIMUM" 2142 return f"{prop} DATABLOCKSIZE" 2143 units = expression.args.get("units") 2144 units = f" {units}" if units else "" 2145 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2146 2147 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2148 autotemp = expression.args.get("autotemp") 2149 always = expression.args.get("always") 2150 default = expression.args.get("default") 2151 manual = expression.args.get("manual") 2152 never = expression.args.get("never") 2153 2154 if autotemp is not None: 2155 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2156 elif always: 2157 prop = "ALWAYS" 2158 elif default: 2159 prop = "DEFAULT" 2160 elif manual: 2161 prop = "MANUAL" 2162 elif never: 2163 prop = "NEVER" 2164 return f"BLOCKCOMPRESSION={prop}" 2165 2166 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2167 no = expression.args.get("no") 2168 no = " NO" if no else "" 2169 concurrent = expression.args.get("concurrent") 2170 concurrent = " CONCURRENT" if concurrent else "" 2171 target = self.sql(expression, "target") 2172 target = f" {target}" if target else "" 2173 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2174 2175 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2176 if isinstance(expression.this, list): 2177 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2178 if expression.this: 2179 modulus = self.sql(expression, "this") 2180 remainder = self.sql(expression, "expression") 2181 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2182 2183 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2184 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2185 return f"FROM ({from_expressions}) TO ({to_expressions})" 2186 2187 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2188 this = self.sql(expression, "this") 2189 2190 for_values_or_default = expression.expression 2191 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2192 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2193 else: 2194 for_values_or_default = " DEFAULT" 2195 2196 return f"PARTITION OF {this}{for_values_or_default}" 2197 2198 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2199 kind = expression.args.get("kind") 2200 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2201 for_or_in = expression.args.get("for_or_in") 2202 for_or_in = f" {for_or_in}" if for_or_in else "" 2203 lock_type = expression.args.get("lock_type") 2204 override = " OVERRIDE" if expression.args.get("override") else "" 2205 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2206 2207 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2208 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2209 statistics = expression.args.get("statistics") 2210 statistics_sql = "" 2211 if statistics is not None: 2212 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2213 return f"{data_sql}{statistics_sql}" 2214 2215 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2216 this = self.sql(expression, "this") 2217 this = f"HISTORY_TABLE={this}" if this else "" 2218 data_consistency: str | None = self.sql(expression, "data_consistency") 2219 data_consistency = ( 2220 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2221 ) 2222 retention_period: str | None = self.sql(expression, "retention_period") 2223 retention_period = ( 2224 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2225 ) 2226 2227 if this: 2228 on_sql = self.func("ON", this, data_consistency, retention_period) 2229 else: 2230 on_sql = "ON" if expression.args.get("on") else "OFF" 2231 2232 sql = f"SYSTEM_VERSIONING={on_sql}" 2233 2234 return f"WITH({sql})" if expression.args.get("with_") else sql 2235 2236 def insert_sql(self, expression: exp.Insert) -> str: 2237 hint = self.sql(expression, "hint") 2238 overwrite = expression.args.get("overwrite") 2239 2240 if isinstance(expression.this, exp.Directory): 2241 this = " OVERWRITE" if overwrite else " INTO" 2242 else: 2243 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2244 2245 stored = self.sql(expression, "stored") 2246 stored = f" {stored}" if stored else "" 2247 alternative = expression.args.get("alternative") 2248 alternative = f" OR {alternative}" if alternative else "" 2249 ignore = " IGNORE" if expression.args.get("ignore") else "" 2250 is_function = expression.args.get("is_function") 2251 if is_function: 2252 this = f"{this} FUNCTION" 2253 this = f"{this} {self.sql(expression, 'this')}" 2254 2255 exists = " IF EXISTS" if expression.args.get("exists") else "" 2256 where = self.sql(expression, "where") 2257 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2258 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2259 on_conflict = self.sql(expression, "conflict") 2260 on_conflict = f" {on_conflict}" if on_conflict else "" 2261 by_name = " BY NAME" if expression.args.get("by_name") else "" 2262 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2263 returning = self.sql(expression, "returning") 2264 2265 if self.RETURNING_END: 2266 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2267 else: 2268 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2269 2270 partition_by = self.sql(expression, "partition") 2271 partition_by = f" {partition_by}" if partition_by else "" 2272 settings = self.sql(expression, "settings") 2273 settings = f" {settings}" if settings else "" 2274 2275 source = self.sql(expression, "source") 2276 source = f"TABLE {source}" if source else "" 2277 2278 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2279 return self.prepend_ctes(expression, sql) 2280 2281 def introducer_sql(self, expression: exp.Introducer) -> str: 2282 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2283 2284 def kill_sql(self, expression: exp.Kill) -> str: 2285 kind = self.sql(expression, "kind") 2286 kind = f" {kind}" if kind else "" 2287 this = self.sql(expression, "this") 2288 this = f" {this}" if this else "" 2289 return f"KILL{kind}{this}" 2290 2291 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2292 return expression.name 2293 2294 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2295 return expression.name 2296 2297 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2298 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2299 2300 constraint = self.sql(expression, "constraint") 2301 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2302 2303 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2304 if conflict_keys: 2305 conflict_keys = f"({conflict_keys})" 2306 2307 index_predicate = self.sql(expression, "index_predicate") 2308 conflict_keys = f"{conflict_keys}{index_predicate} " 2309 2310 action = self.sql(expression, "action") 2311 2312 expressions = self.expressions(expression, flat=True) 2313 if expressions: 2314 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2315 expressions = f" {set_keyword}{expressions}" 2316 2317 where = self.sql(expression, "where") 2318 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2319 2320 def returning_sql(self, expression: exp.Returning) -> str: 2321 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2322 2323 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2324 fields = self.sql(expression, "fields") 2325 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2326 escaped = self.sql(expression, "escaped") 2327 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2328 items = self.sql(expression, "collection_items") 2329 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2330 keys = self.sql(expression, "map_keys") 2331 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2332 lines = self.sql(expression, "lines") 2333 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2334 null = self.sql(expression, "null") 2335 null = f" NULL DEFINED AS {null}" if null else "" 2336 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2337 2338 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2339 return f"WITH ({self.expressions(expression, flat=True)})" 2340 2341 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2342 this = f"{self.sql(expression, 'this')} INDEX" 2343 target = self.sql(expression, "target") 2344 target = f" FOR {target}" if target else "" 2345 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2346 2347 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2348 this = self.sql(expression, "this") 2349 kind = self.sql(expression, "kind") 2350 expr = self.sql(expression, "expression") 2351 return f"{this} ({kind} => {expr})" 2352 2353 def table_parts(self, expression: exp.Table) -> str: 2354 return ".".join( 2355 self.sql(part) 2356 for part in ( 2357 expression.args.get("catalog"), 2358 expression.args.get("db"), 2359 expression.args.get("this"), 2360 ) 2361 if part is not None 2362 ) 2363 2364 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2365 table = self.table_parts(expression) 2366 only = "ONLY " if expression.args.get("only") else "" 2367 partition = self.sql(expression, "partition") 2368 partition = f" {partition}" if partition else "" 2369 version = self.sql(expression, "version") 2370 version = f" {version}" if version else "" 2371 alias = self.sql(expression, "alias") 2372 alias = f"{sep}{alias}" if alias else "" 2373 2374 sample = self.sql(expression, "sample") 2375 post_alias = "" 2376 pre_alias = "" 2377 2378 if self.dialect.ALIAS_POST_TABLESAMPLE: 2379 pre_alias = sample 2380 else: 2381 post_alias = sample 2382 2383 if self.dialect.ALIAS_POST_VERSION: 2384 pre_alias = f"{pre_alias}{version}" 2385 else: 2386 post_alias = f"{post_alias}{version}" 2387 2388 hints = self.expressions(expression, key="hints", sep=" ") 2389 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2390 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2391 joins = self.indent( 2392 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2393 ) 2394 laterals = self.expressions(expression, key="laterals", sep="") 2395 2396 file_format = self.sql(expression, "format") 2397 if file_format: 2398 pattern = self.sql(expression, "pattern") 2399 pattern = f", PATTERN => {pattern}" if pattern else "" 2400 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2401 2402 ordinality = expression.args.get("ordinality") or "" 2403 if ordinality: 2404 ordinality = f" WITH ORDINALITY{alias}" 2405 alias = "" 2406 2407 when = self.sql(expression, "when") 2408 if when: 2409 table = f"{table} {when}" 2410 2411 changes = self.sql(expression, "changes") 2412 changes = f" {changes}" if changes else "" 2413 2414 rows_from = self.expressions(expression, key="rows_from") 2415 if rows_from: 2416 table = f"ROWS FROM {self.wrap(rows_from)}" 2417 2418 indexed = expression.args.get("indexed") 2419 if indexed is not None: 2420 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2421 else: 2422 indexed = "" 2423 2424 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2425 2426 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2427 table = self.func("TABLE", expression.this) 2428 alias = self.sql(expression, "alias") 2429 alias = f" AS {alias}" if alias else "" 2430 sample = self.sql(expression, "sample") 2431 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2432 joins = self.indent( 2433 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2434 ) 2435 return f"{table}{alias}{pivots}{sample}{joins}" 2436 2437 def tablesample_sql( 2438 self, 2439 expression: exp.TableSample, 2440 tablesample_keyword: str | None = None, 2441 ) -> str: 2442 method = self.sql(expression, "method") 2443 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2444 numerator = self.sql(expression, "bucket_numerator") 2445 denominator = self.sql(expression, "bucket_denominator") 2446 field = self.sql(expression, "bucket_field") 2447 field = f" ON {field}" if field else "" 2448 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2449 seed = self.sql(expression, "seed") 2450 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2451 2452 size = self.sql(expression, "size") 2453 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2454 size = f"{size} ROWS" 2455 2456 percent = self.sql(expression, "percent") 2457 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2458 percent = f"{percent} PERCENT" 2459 2460 expr = f"{bucket}{percent}{size}" 2461 if self.TABLESAMPLE_REQUIRES_PARENS: 2462 expr = f"({expr})" 2463 2464 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2465 2466 def _pivot_in_value_aliases(self, expression: exp.Pivot) -> list[exp.Expression] | None: 2467 # Returns the rewritten field.expressions list with PivotAlias wrappers injected where 2468 # the stored column name differs from the target dialect's natural output. 2469 columns = expression.args.get("columns") 2470 if not columns or len(expression.fields) != 1: 2471 return None 2472 2473 args = expression.args 2474 parser_cls = self.dialect.parser_class 2475 2476 tgt_identify_pivot_strings = parser_cls.IDENTIFY_PIVOT_STRINGS 2477 tgt_prefixed_pivot_columns = parser_cls.PREFIXED_PIVOT_COLUMNS 2478 tgt_pivot_column_naming = parser_cls.PIVOT_COLUMN_NAMING 2479 2480 src_identify_pivot_strings = args.get("identify_pivot_strings", tgt_identify_pivot_strings) 2481 src_prefixed_pivot_columns = args.get("prefixed_pivot_columns", tgt_prefixed_pivot_columns) 2482 src_pivot_column_naming = args.get("pivot_column_naming", tgt_pivot_column_naming) 2483 2484 if ( 2485 src_identify_pivot_strings == tgt_identify_pivot_strings 2486 and src_prefixed_pivot_columns == tgt_prefixed_pivot_columns 2487 and src_pivot_column_naming == tgt_pivot_column_naming 2488 ): 2489 return None 2490 2491 in_exprs = expression.fields[0].expressions 2492 step = len(columns) // len(in_exprs) 2493 2494 # Derive the per-value suffix from the first stored column vs the first IN-list value. 2495 # This correctly handles dialects (e.g. Spark single-agg) that ignore agg aliases. 2496 first_base = in_exprs[0].sql() if src_identify_pivot_strings else in_exprs[0].alias_or_name 2497 first_stored = columns[0].name 2498 2499 # exit if only suffix matches, not prefix. (e.g. BigQuery, which cannot be fixed) 2500 if not first_stored.startswith(first_base): 2501 return None 2502 2503 suffix = first_stored[len(first_base) :] 2504 2505 # Whether the target dialect would append an agg-name suffix for this pivot. 2506 # Spark single-agg uniquely drops the agg alias entirely. 2507 target_has_suffix = ( 2508 len(expression.expressions) > 1 or tgt_pivot_column_naming != "agg_name_if_multiple" 2509 ) and any(a.alias for a in expression.expressions) 2510 source_has_suffix = suffix != "" 2511 2512 new_exprs: list[exp.Expression] = [] 2513 modified = False 2514 for val_idx, e in enumerate(in_exprs): 2515 if isinstance(e, exp.PivotAlias): 2516 new_exprs.append(e) 2517 continue 2518 2519 i = val_idx * step 2520 stored_full = columns[i].name 2521 stored_value = stored_full[: -len(suffix)] if suffix else stored_full 2522 target_value = e.sql() if tgt_identify_pivot_strings else e.alias_or_name 2523 2524 # Source had a suffix, but target won't apply one 2525 if source_has_suffix and not target_has_suffix: 2526 new_exprs.append( 2527 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_full, quoted=True)) 2528 ) 2529 modified = True 2530 # Value-part mismatch (e.g. Snowflake's literal-style values vs others). 2531 elif stored_value != target_value: 2532 new_exprs.append( 2533 exp.PivotAlias(this=e, alias=exp.to_identifier(stored_value, quoted=True)) 2534 ) 2535 modified = True 2536 else: 2537 new_exprs.append(e) 2538 2539 return new_exprs if modified else None 2540 2541 def pivot_sql(self, expression: exp.Pivot) -> str: 2542 expressions = self.expressions(expression, flat=True) 2543 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2544 2545 group = self.sql(expression, "group") 2546 2547 if expression.this: 2548 this = self.sql(expression, "this") 2549 if not expressions: 2550 sql = f"UNPIVOT {this}" 2551 else: 2552 on = f"{self.seg('ON')} {expressions}" 2553 into = self.sql(expression, "into") 2554 into = f"{self.seg('INTO')} {into}" if into else "" 2555 using = self.expressions(expression, key="using", flat=True) 2556 using = f"{self.seg('USING')} {using}" if using else "" 2557 sql = f"{direction} {this}{on}{into}{using}{group}" 2558 return self.prepend_ctes(expression, sql) 2559 2560 if not expression.unpivot: 2561 # Wrap IN-list values with explicit aliases where the target dialect would differ 2562 new_field_exprs = self._pivot_in_value_aliases(expression) 2563 if new_field_exprs is not None: 2564 expression.fields[0].set("expressions", new_field_exprs) 2565 2566 alias = self.sql(expression, "alias") 2567 alias = f" AS {alias}" if alias else "" 2568 2569 fields = self.expressions( 2570 expression, 2571 "fields", 2572 sep=" ", 2573 dynamic=True, 2574 new_line=True, 2575 skip_first=True, 2576 skip_last=True, 2577 ) 2578 2579 include_nulls = expression.args.get("include_nulls") 2580 if include_nulls is not None: 2581 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2582 else: 2583 nulls = "" 2584 2585 default_on_null = self.sql(expression, "default_on_null") 2586 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2587 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2588 return self.prepend_ctes(expression, sql) 2589 2590 def version_sql(self, expression: exp.Version) -> str: 2591 this = f"FOR {expression.name}" 2592 kind = expression.text("kind") 2593 expr = self.sql(expression, "expression") 2594 return f"{this} {kind} {expr}" 2595 2596 def tuple_sql(self, expression: exp.Tuple) -> str: 2597 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2598 2599 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2600 """ 2601 Returns (join_sql, from_sql) for UPDATE statements. 2602 - join_sql: placed after UPDATE table, before SET 2603 - from_sql: placed after SET clause (standard position) 2604 Dialects like MySQL need to convert FROM to JOIN syntax. 2605 """ 2606 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2607 return ("", self.sql(expression, "from_")) 2608 2609 # Qualify unqualified columns in SET clause with the target table 2610 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2611 target_table = expression.this 2612 if isinstance(target_table, exp.Table): 2613 target_name = exp.to_identifier(target_table.alias_or_name) 2614 for eq in expression.expressions: 2615 col = eq.this 2616 if isinstance(col, exp.Column) and not col.table: 2617 col.set("table", target_name) 2618 2619 table = from_expr.this 2620 if nested_joins := table.args.get("joins", []): 2621 table.set("joins", None) 2622 2623 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2624 for nested in nested_joins: 2625 if not nested.args.get("on") and not nested.args.get("using"): 2626 nested.set("on", exp.true()) 2627 join_sql += self.sql(nested) 2628 2629 return (join_sql, "") 2630 2631 def update_sql(self, expression: exp.Update) -> str: 2632 hint = self.sql(expression, "hint") 2633 this = self.sql(expression, "this") 2634 join_sql, from_sql = self._update_from_joins_sql(expression) 2635 set_sql = self.expressions(expression, flat=True) 2636 where_sql = self.sql(expression, "where") 2637 returning = self.sql(expression, "returning") 2638 order = self.sql(expression, "order") 2639 limit = self.sql(expression, "limit") 2640 if self.RETURNING_END: 2641 expression_sql = f"{from_sql}{where_sql}{returning}" 2642 else: 2643 expression_sql = f"{returning}{from_sql}{where_sql}" 2644 options = self.expressions(expression, key="options") 2645 options = f" OPTION({options})" if options else "" 2646 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2647 return self.prepend_ctes(expression, sql) 2648 2649 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2650 values_as_table = values_as_table and self.VALUES_AS_TABLE 2651 2652 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2653 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2654 args = self.expressions(expression) 2655 alias = self.sql(expression, "alias") 2656 values = f"VALUES{self.seg('')}{args}" 2657 values = ( 2658 f"({values})" 2659 if self.WRAP_DERIVED_VALUES 2660 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2661 else values 2662 ) 2663 values = self.query_modifiers(expression, values) 2664 return f"{values} AS {alias}" if alias else values 2665 2666 # Converts `VALUES...` expression into a series of select unions. 2667 alias_node = expression.args.get("alias") 2668 column_names = alias_node and alias_node.columns 2669 2670 selects: list[exp.Query] = [] 2671 2672 for i, tup in enumerate(expression.expressions): 2673 row = tup.expressions 2674 2675 if i == 0 and column_names: 2676 row = [ 2677 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2678 ] 2679 2680 selects.append(exp.Select(expressions=row)) 2681 2682 if self.pretty: 2683 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2684 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2685 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2686 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2687 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2688 2689 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2690 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2691 return f"({unions}){alias}" 2692 2693 def var_sql(self, expression: exp.Var) -> str: 2694 return self.sql(expression, "this") 2695 2696 @unsupported_args("expressions") 2697 def into_sql(self, expression: exp.Into) -> str: 2698 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2699 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2700 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2701 2702 def from_sql(self, expression: exp.From) -> str: 2703 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2704 2705 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2706 grouping_sets = self.expressions(expression, indent=False) 2707 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2708 2709 def rollup_sql(self, expression: exp.Rollup) -> str: 2710 expressions = self.expressions(expression, indent=False) 2711 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2712 2713 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2714 this = self.sql(expression, "this") 2715 2716 columns = self.expressions(expression, flat=True) 2717 2718 from_sql = self.sql(expression, "from_index") 2719 from_sql = f" FROM {from_sql}" if from_sql else "" 2720 2721 properties = expression.args.get("properties") 2722 properties_sql = ( 2723 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2724 ) 2725 2726 return f"{this}({columns}){from_sql}{properties_sql}" 2727 2728 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2729 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2730 2731 def cube_sql(self, expression: exp.Cube) -> str: 2732 expressions = self.expressions(expression, indent=False) 2733 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2734 2735 def group_sql(self, expression: exp.Group) -> str: 2736 group_by_all = expression.args.get("all") 2737 if group_by_all is True: 2738 modifier = " ALL" 2739 elif group_by_all is False: 2740 modifier = " DISTINCT" 2741 else: 2742 modifier = "" 2743 2744 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2745 2746 grouping_sets = self.expressions(expression, key="grouping_sets") 2747 cube = self.expressions(expression, key="cube") 2748 rollup = self.expressions(expression, key="rollup") 2749 2750 groupings = csv( 2751 self.seg(grouping_sets) if grouping_sets else "", 2752 self.seg(cube) if cube else "", 2753 self.seg(rollup) if rollup else "", 2754 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2755 sep=self.GROUPINGS_SEP, 2756 ) 2757 2758 if ( 2759 expression.expressions 2760 and groupings 2761 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2762 ): 2763 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2764 2765 return f"{group_by}{groupings}" 2766 2767 def having_sql(self, expression: exp.Having) -> str: 2768 this = self.indent(self.sql(expression, "this")) 2769 return f"{self.seg('HAVING')}{self.sep()}{this}" 2770 2771 def connect_sql(self, expression: exp.Connect) -> str: 2772 start = self.sql(expression, "start") 2773 start = self.seg(f"START WITH {start}") if start else "" 2774 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2775 connect = self.sql(expression, "connect") 2776 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2777 return start + connect 2778 2779 def prior_sql(self, expression: exp.Prior) -> str: 2780 return f"PRIOR {self.sql(expression, 'this')}" 2781 2782 def join_sql(self, expression: exp.Join) -> str: 2783 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2784 side = None 2785 else: 2786 side = expression.side 2787 2788 op_sql = " ".join( 2789 op 2790 for op in ( 2791 expression.method, 2792 "GLOBAL" if expression.args.get("global_") else None, 2793 side, 2794 expression.kind, 2795 expression.hint if self.JOIN_HINTS else None, 2796 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2797 ) 2798 if op 2799 ) 2800 match_cond = self.sql(expression, "match_condition") 2801 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2802 on_sql = self.sql(expression, "on") 2803 using = expression.args.get("using") 2804 2805 if not on_sql and using: 2806 on_sql = csv(*(self.sql(column) for column in using)) 2807 2808 this = expression.this 2809 this_sql = self.sql(this) 2810 2811 exprs = self.expressions(expression) 2812 if exprs: 2813 this_sql = f"{this_sql},{self.seg(exprs)}" 2814 2815 if on_sql: 2816 on_sql = self.indent(on_sql, skip_first=True) 2817 space = self.seg(" " * self.pad) if self.pretty else " " 2818 if using: 2819 on_sql = f"{space}USING ({on_sql})" 2820 else: 2821 on_sql = f"{space}ON {on_sql}" 2822 elif not op_sql: 2823 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2824 return f" {this_sql}" 2825 2826 return f", {this_sql}" 2827 2828 if op_sql != "STRAIGHT_JOIN": 2829 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2830 2831 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2832 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2833 2834 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2835 args = self.expressions(expression, flat=True) 2836 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2837 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2838 2839 def lateral_op(self, expression: exp.Lateral) -> str: 2840 cross_apply = expression.args.get("cross_apply") 2841 2842 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2843 if cross_apply is True: 2844 op = "INNER JOIN " 2845 elif cross_apply is False: 2846 op = "LEFT JOIN " 2847 else: 2848 op = "" 2849 2850 return f"{op}LATERAL" 2851 2852 def lateral_sql(self, expression: exp.Lateral) -> str: 2853 this = self.sql(expression, "this") 2854 2855 if expression.args.get("view"): 2856 alias = expression.args["alias"] 2857 columns = self.expressions(alias, key="columns", flat=True) 2858 table = f" {alias.name}" if alias.name else "" 2859 columns = f" AS {columns}" if columns else "" 2860 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2861 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2862 2863 alias = self.sql(expression, "alias") 2864 alias = f" AS {alias}" if alias else "" 2865 2866 ordinality = expression.args.get("ordinality") or "" 2867 if ordinality: 2868 ordinality = f" WITH ORDINALITY{alias}" 2869 alias = "" 2870 2871 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2872 2873 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2874 this = self.sql(expression, "this") 2875 2876 args = [ 2877 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2878 for e in (expression.args.get(k) for k in ("offset", "expression")) 2879 if e 2880 ] 2881 2882 args_sql = ", ".join(self.sql(e) for e in args) 2883 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2884 expressions = self.expressions(expression, flat=True) 2885 limit_options = self.sql(expression, "limit_options") 2886 expressions = f" BY {expressions}" if expressions else "" 2887 2888 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2889 2890 def offset_sql(self, expression: exp.Offset) -> str: 2891 this = self.sql(expression, "this") 2892 value = expression.expression 2893 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2894 expressions = self.expressions(expression, flat=True) 2895 expressions = f" BY {expressions}" if expressions else "" 2896 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2897 2898 def setitem_sql(self, expression: exp.SetItem) -> str: 2899 kind = self.sql(expression, "kind") 2900 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2901 kind = "" 2902 else: 2903 kind = f"{kind} " if kind else "" 2904 this = self.sql(expression, "this") 2905 expressions = self.expressions(expression) 2906 collate = self.sql(expression, "collate") 2907 collate = f" COLLATE {collate}" if collate else "" 2908 global_ = "GLOBAL " if expression.args.get("global_") else "" 2909 return f"{global_}{kind}{this}{expressions}{collate}" 2910 2911 def set_sql(self, expression: exp.Set) -> str: 2912 expressions = f" {self.expressions(expression, flat=True)}" 2913 tag = " TAG" if expression.args.get("tag") else "" 2914 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2915 2916 def queryband_sql(self, expression: exp.QueryBand) -> str: 2917 this = self.sql(expression, "this") 2918 update = " UPDATE" if expression.args.get("update") else "" 2919 scope = self.sql(expression, "scope") 2920 scope = f" FOR {scope}" if scope else "" 2921 2922 return f"QUERY_BAND = {this}{update}{scope}" 2923 2924 def pragma_sql(self, expression: exp.Pragma) -> str: 2925 return f"PRAGMA {self.sql(expression, 'this')}" 2926 2927 def lock_sql(self, expression: exp.Lock) -> str: 2928 if not self.LOCKING_READS_SUPPORTED: 2929 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2930 return "" 2931 2932 update = expression.args["update"] 2933 key = expression.args.get("key") 2934 if update: 2935 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2936 else: 2937 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2938 expressions = self.expressions(expression, flat=True) 2939 expressions = f" OF {expressions}" if expressions else "" 2940 wait = expression.args.get("wait") 2941 2942 if wait is not None: 2943 if isinstance(wait, exp.Literal): 2944 wait = f" WAIT {self.sql(wait)}" 2945 else: 2946 wait = " NOWAIT" if wait else " SKIP LOCKED" 2947 2948 return f"{lock_type}{expressions}{wait or ''}" 2949 2950 def literal_sql(self, expression: exp.Literal) -> str: 2951 text = expression.this or "" 2952 if expression.is_string: 2953 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2954 return text 2955 2956 def escape_str( 2957 self, 2958 text: str, 2959 escape_backslash: bool = True, 2960 delimiter: str | None = None, 2961 escaped_delimiter: str | None = None, 2962 is_byte_string: bool = False, 2963 ) -> str: 2964 if is_byte_string: 2965 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2966 else: 2967 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2968 2969 if supports_escape_sequences: 2970 text = "".join( 2971 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2972 for ch in text 2973 ) 2974 2975 delimiter = delimiter or self.dialect.QUOTE_END 2976 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2977 2978 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2979 2980 def loaddata_sql(self, expression: exp.LoadData) -> str: 2981 is_overwrite = expression.args.get("overwrite") 2982 overwrite = " OVERWRITE" if is_overwrite else "" 2983 this = self.sql(expression, "this") 2984 2985 files = expression.args.get("files") 2986 if files: 2987 files_sql = self.expressions(files, flat=True) 2988 files_sql = f"FILES{self.wrap(files_sql)}" 2989 if is_overwrite: 2990 this = f" {this}" 2991 elif expression.args.get("temp"): 2992 this = f" INTO TEMP TABLE {this}" 2993 else: 2994 this = f" INTO TABLE {this}" 2995 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2996 2997 local = " LOCAL" if expression.args.get("local") else "" 2998 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2999 this = f" INTO TABLE {this}" 3000 partition = self.sql(expression, "partition") 3001 partition = f" {partition}" if partition else "" 3002 input_format = self.sql(expression, "input_format") 3003 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3004 serde = self.sql(expression, "serde") 3005 serde = f" SERDE {serde}" if serde else "" 3006 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 3007 3008 def null_sql(self, *_) -> str: 3009 return "NULL" 3010 3011 def boolean_sql(self, expression: exp.Boolean) -> str: 3012 return "TRUE" if expression.this else "FALSE" 3013 3014 def booland_sql(self, expression: exp.Booland) -> str: 3015 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 3016 3017 def boolor_sql(self, expression: exp.Boolor) -> str: 3018 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 3019 3020 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3021 this = self.sql(expression, "this") 3022 this = f"{this} " if this else this 3023 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3024 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 3025 3026 def withfill_sql(self, expression: exp.WithFill) -> str: 3027 from_sql = self.sql(expression, "from_") 3028 from_sql = f" FROM {from_sql}" if from_sql else "" 3029 to_sql = self.sql(expression, "to") 3030 to_sql = f" TO {to_sql}" if to_sql else "" 3031 step_sql = self.sql(expression, "step") 3032 step_sql = f" STEP {step_sql}" if step_sql else "" 3033 interpolated_values = [ 3034 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3035 if isinstance(e, exp.Alias) 3036 else self.sql(e, "this") 3037 for e in expression.args.get("interpolate") or [] 3038 ] 3039 interpolate = ( 3040 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3041 ) 3042 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 3043 3044 def cluster_sql(self, expression: exp.Cluster) -> str: 3045 return self.op_expressions("CLUSTER BY", expression) 3046 3047 def clusterproperty_sql(self, expression: exp.ClusterProperty) -> str: 3048 if expression.this: 3049 self.unsupported(f"Unsupported CLUSTER BY {self.sql(expression, 'this')}") 3050 return "" 3051 expressions = self.expressions(expression, flat=True) 3052 return f"CLUSTER BY ({expressions})" 3053 3054 def distribute_sql(self, expression: exp.Distribute) -> str: 3055 return self.op_expressions("DISTRIBUTE BY", expression) 3056 3057 def sort_sql(self, expression: exp.Sort) -> str: 3058 return self.op_expressions("SORT BY", expression) 3059 3060 def _resolve_ordered_for_null_ordering_simulation( 3061 self, expression: exp.Ordered 3062 ) -> exp.Expr | None: 3063 """Resolve a bare ORDER BY name against the enclosing SELECT projection. 3064 3065 Returns the underlying expression of the uniquely-matching projection 3066 (Alias-stripped) for substitution into the NULLS FIRST/LAST CASE 3067 simulation, since the CASE is evaluated in FROM-clause scope rather 3068 than alias scope (MySQL error 1052). Returns None if no safe 3069 substitution applies, leaving the original behaviour unchanged. 3070 """ 3071 this = expression.this 3072 if not (isinstance(this, exp.Column) and not this.table): 3073 return None 3074 3075 ancestor = expression.find_ancestor(exp.Select, exp.Window) 3076 if not isinstance(ancestor, exp.Select): 3077 return None 3078 3079 column_name = this.name 3080 matched: list[exp.Expr] = [ 3081 p.this if isinstance(p, exp.Alias) else p 3082 for p in ancestor.selects 3083 if p.output_name == column_name 3084 ] 3085 match = matched[0] if len(matched) == 1 else None 3086 3087 # Skip the substitution when it would be identical to the existing 3088 # reference (e.g. ``SELECT col FROM t ORDER BY col``). 3089 if isinstance(match, exp.Column) and not match.table and match.name == column_name: 3090 return None 3091 3092 return match 3093 3094 def ordered_sql(self, expression: exp.Ordered) -> str: 3095 desc = expression.args.get("desc") 3096 asc = not desc 3097 3098 nulls_first = expression.args.get("nulls_first") 3099 nulls_last = not nulls_first 3100 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3101 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3102 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3103 3104 this = self.sql(expression, "this") 3105 3106 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3107 nulls_sort_change = "" 3108 if nulls_first and ( 3109 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3110 ): 3111 nulls_sort_change = " NULLS FIRST" 3112 elif ( 3113 nulls_last 3114 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3115 and not nulls_are_last 3116 ): 3117 nulls_sort_change = " NULLS LAST" 3118 3119 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3120 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3121 window = expression.find_ancestor(exp.Window, exp.Select) 3122 3123 if isinstance(window, exp.Window): 3124 window_this = window.this 3125 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3126 window_this = window_this.this 3127 spec = window.args.get("spec") 3128 else: 3129 window_this = None 3130 spec = None 3131 3132 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3133 # without a spec or with a ROWS spec, but not with RANGE 3134 if not ( 3135 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3136 and (not spec or spec.text("kind").upper() == "ROWS") 3137 ): 3138 if window_this and spec: 3139 self.unsupported( 3140 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3141 ) 3142 nulls_sort_change = "" 3143 elif self.NULL_ORDERING_SUPPORTED is False and ( 3144 (asc and nulls_sort_change == " NULLS LAST") 3145 or (desc and nulls_sort_change == " NULLS FIRST") 3146 ): 3147 # BigQuery does not allow these ordering/nulls combinations when used under 3148 # an aggregation func or under a window containing one 3149 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3150 3151 if isinstance(ancestor, exp.Window): 3152 ancestor = ancestor.this 3153 if isinstance(ancestor, exp.AggFunc): 3154 self.unsupported( 3155 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3156 ) 3157 nulls_sort_change = "" 3158 elif self.NULL_ORDERING_SUPPORTED is None: 3159 if expression.this.is_int: 3160 self.unsupported( 3161 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3162 ) 3163 elif not isinstance(expression.this, exp.Rand): 3164 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3165 target = self.sql(resolved) if resolved is not None else this 3166 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3167 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3168 nulls_sort_change = "" 3169 3170 with_fill = self.sql(expression, "with_fill") 3171 with_fill = f" {with_fill}" if with_fill else "" 3172 3173 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3174 3175 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3176 window_frame = self.sql(expression, "window_frame") 3177 window_frame = f"{window_frame} " if window_frame else "" 3178 3179 this = self.sql(expression, "this") 3180 3181 return f"{window_frame}{this}" 3182 3183 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3184 partition = self.partition_by_sql(expression) 3185 order = self.sql(expression, "order") 3186 measures = self.expressions(expression, key="measures") 3187 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3188 rows = self.sql(expression, "rows") 3189 rows = self.seg(rows) if rows else "" 3190 after = self.sql(expression, "after") 3191 after = self.seg(after) if after else "" 3192 pattern = self.sql(expression, "pattern") 3193 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3194 definition_sqls = [ 3195 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3196 for definition in expression.args.get("define", []) 3197 ] 3198 definitions = self.expressions(sqls=definition_sqls) 3199 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3200 body = "".join( 3201 ( 3202 partition, 3203 order, 3204 measures, 3205 rows, 3206 after, 3207 pattern, 3208 define, 3209 ) 3210 ) 3211 alias = self.sql(expression, "alias") 3212 alias = f" {alias}" if alias else "" 3213 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3214 3215 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3216 limit = expression.args.get("limit") 3217 3218 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3219 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3220 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3221 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3222 3223 return csv( 3224 *sqls, 3225 *[self.sql(join) for join in expression.args.get("joins") or []], 3226 self.sql(expression, "match"), 3227 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3228 self.sql(expression, "prewhere"), 3229 self.sql(expression, "where"), 3230 self.sql(expression, "connect"), 3231 self.sql(expression, "group"), 3232 self.sql(expression, "having"), 3233 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3234 self.sql(expression, "order"), 3235 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3236 *self.after_limit_modifiers(expression), 3237 self.options_modifier(expression), 3238 self.sql(expression, "for_"), 3239 sep="", 3240 ) 3241 3242 def options_modifier(self, expression: exp.Expr) -> str: 3243 options = self.expressions(expression, key="options") 3244 return f" {options}" if options else "" 3245 3246 def forclause_sql(self, expression: exp.ForClause) -> str: 3247 kind = expression.args["kind"] 3248 if kind == "BROWSE": 3249 return f"{self.sep()}FOR BROWSE" 3250 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3251 # the target dialect doesn't support QueryOption, so we drop the clause. 3252 options = self.expressions(expression, key="expressions") 3253 if not options: 3254 return "" 3255 return f"{self.sep()}FOR {kind}{self.seg(options)}" 3256 3257 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3258 self.unsupported("Unsupported query option.") 3259 return "" 3260 3261 def offset_limit_modifiers( 3262 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3263 ) -> list[str]: 3264 return [ 3265 self.sql(expression, "offset") if fetch else self.sql(limit), 3266 self.sql(limit) if fetch else self.sql(expression, "offset"), 3267 ] 3268 3269 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3270 locks = self.expressions(expression, key="locks", sep=" ") 3271 locks = f" {locks}" if locks else "" 3272 return [locks, self.sql(expression, "sample")] 3273 3274 def select_sql(self, expression: exp.Select) -> str: 3275 into = expression.args.get("into") 3276 if not self.SUPPORTS_SELECT_INTO and into: 3277 into.pop() 3278 3279 hint = self.sql(expression, "hint") 3280 distinct = self.sql(expression, "distinct") 3281 distinct = f" {distinct}" if distinct else "" 3282 kind = self.sql(expression, "kind") 3283 3284 limit = expression.args.get("limit") 3285 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3286 top = self.limit_sql(limit, top=True) 3287 limit.pop() 3288 else: 3289 top = "" 3290 3291 expressions = self.expressions(expression) 3292 3293 if kind: 3294 if kind in self.SELECT_KINDS: 3295 kind = f" AS {kind}" 3296 else: 3297 if kind == "STRUCT": 3298 expressions = self.expressions( 3299 sqls=[ 3300 self.sql( 3301 exp.Struct( 3302 expressions=[ 3303 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3304 if isinstance(e, exp.Alias) 3305 else e 3306 for e in expression.expressions 3307 ] 3308 ) 3309 ) 3310 ] 3311 ) 3312 kind = "" 3313 3314 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3315 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3316 3317 exclude = expression.args.get("exclude") 3318 3319 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3320 exclude_sql = self.expressions(sqls=exclude, flat=True) 3321 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3322 3323 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3324 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3325 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3326 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3327 sql = self.query_modifiers( 3328 expression, 3329 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3330 self.sql(expression, "into", comment=False), 3331 self.sql(expression, "from_", comment=False), 3332 ) 3333 3334 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3335 if expression.args.get("with_"): 3336 sql = self.maybe_comment(sql, expression) 3337 expression.pop_comments() 3338 3339 sql = self.prepend_ctes(expression, sql) 3340 3341 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3342 expression.set("exclude", None) 3343 subquery = expression.subquery(copy=False) 3344 star = exp.Star(except_=exclude) 3345 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3346 3347 if not self.SUPPORTS_SELECT_INTO and into: 3348 if into.args.get("temporary"): 3349 table_kind = " TEMPORARY" 3350 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3351 table_kind = " UNLOGGED" 3352 else: 3353 table_kind = "" 3354 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3355 3356 return sql 3357 3358 def schema_sql(self, expression: exp.Schema) -> str: 3359 this = self.sql(expression, "this") 3360 sql = self.schema_columns_sql(expression) 3361 return f"{this} {sql}" if this and sql else this or sql 3362 3363 def schema_columns_sql(self, expression: exp.Expr) -> str: 3364 if expression.expressions: 3365 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3366 return "" 3367 3368 def star_sql(self, expression: exp.Star) -> str: 3369 except_ = self.expressions(expression, key="except_", flat=True) 3370 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3371 replace = self.expressions(expression, key="replace", flat=True) 3372 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3373 rename = self.expressions(expression, key="rename", flat=True) 3374 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3375 return f"*{except_}{replace}{rename}" 3376 3377 def parameter_sql(self, expression: exp.Parameter) -> str: 3378 this = self.sql(expression, "this") 3379 return f"{self.PARAMETER_TOKEN}{this}" 3380 3381 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3382 this = self.sql(expression, "this") 3383 kind = expression.text("kind") 3384 if kind: 3385 kind = f"{kind}." 3386 return f"@@{kind}{this}" 3387 3388 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3389 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3390 3391 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3392 alias = self.sql(expression, "alias") 3393 alias = f"{sep}{alias}" if alias else "" 3394 sample = self.sql(expression, "sample") 3395 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3396 alias = f"{sample}{alias}" 3397 3398 # Set to None so it's not generated again by self.query_modifiers() 3399 expression.set("sample", None) 3400 3401 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3402 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3403 return self.prepend_ctes(expression, sql) 3404 3405 def qualify_sql(self, expression: exp.Qualify) -> str: 3406 this = self.indent(self.sql(expression, "this")) 3407 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3408 3409 def unnest_sql(self, expression: exp.Unnest) -> str: 3410 args = self.expressions(expression, flat=True) 3411 3412 alias = expression.args.get("alias") 3413 offset = expression.args.get("offset") 3414 3415 if self.UNNEST_WITH_ORDINALITY: 3416 if alias and isinstance(offset, exp.Expr): 3417 alias.append("columns", offset) 3418 expression.set("offset", None) 3419 3420 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3421 columns = alias.columns 3422 alias = self.sql(columns[0]) if columns else "" 3423 else: 3424 alias = self.sql(alias) 3425 3426 alias = f" AS {alias}" if alias else alias 3427 if self.UNNEST_WITH_ORDINALITY: 3428 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3429 else: 3430 if isinstance(offset, exp.Expr): 3431 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3432 elif offset: 3433 suffix = f"{alias} WITH OFFSET" 3434 else: 3435 suffix = alias 3436 3437 return f"UNNEST({args}){suffix}" 3438 3439 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3440 return "" 3441 3442 def where_sql(self, expression: exp.Where) -> str: 3443 this = self.indent(self.sql(expression, "this")) 3444 return f"{self.seg('WHERE')}{self.sep()}{this}" 3445 3446 def window_sql(self, expression: exp.Window) -> str: 3447 this = self.sql(expression, "this") 3448 partition = self.partition_by_sql(expression) 3449 order = expression.args.get("order") 3450 order = self.order_sql(order, flat=True) if order else "" 3451 spec = self.sql(expression, "spec") 3452 alias = self.sql(expression, "alias") 3453 over = self.sql(expression, "over") or "OVER" 3454 3455 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3456 3457 first = expression.args.get("first") 3458 if first is None: 3459 first = "" 3460 else: 3461 first = "FIRST" if first else "LAST" 3462 3463 if not partition and not order and not spec and alias: 3464 return f"{this} {alias}" 3465 3466 args = self.format_args( 3467 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3468 ) 3469 return f"{this} ({args})" 3470 3471 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3472 partition = self.expressions(expression, key="partition_by", flat=True) 3473 return f"PARTITION BY {partition}" if partition else "" 3474 3475 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3476 kind = self.sql(expression, "kind") 3477 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3478 end = ( 3479 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3480 or "CURRENT ROW" 3481 ) 3482 3483 window_spec = f"{kind} BETWEEN {start} AND {end}" 3484 3485 exclude = self.sql(expression, "exclude") 3486 if exclude: 3487 if self.SUPPORTS_WINDOW_EXCLUDE: 3488 window_spec += f" EXCLUDE {exclude}" 3489 else: 3490 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3491 3492 return window_spec 3493 3494 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3495 this = self.sql(expression, "this") 3496 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3497 return f"{this} WITHIN GROUP ({expression_sql})" 3498 3499 def between_sql(self, expression: exp.Between) -> str: 3500 this = self.sql(expression, "this") 3501 low = self.sql(expression, "low") 3502 high = self.sql(expression, "high") 3503 symmetric = expression.args.get("symmetric") 3504 3505 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3506 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3507 3508 flag = ( 3509 " SYMMETRIC" 3510 if symmetric 3511 else " ASYMMETRIC" 3512 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3513 else "" # silently drop ASYMMETRIC – semantics identical 3514 ) 3515 return f"{this} BETWEEN{flag} {low} AND {high}" 3516 3517 def bracket_offset_expressions( 3518 self, expression: exp.Bracket, index_offset: int | None = None 3519 ) -> list[exp.Expr]: 3520 if expression.args.get("json_access"): 3521 return expression.expressions 3522 3523 return apply_index_offset( 3524 expression.this, 3525 expression.expressions, 3526 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3527 dialect=self.dialect, 3528 ) 3529 3530 def bracket_sql(self, expression: exp.Bracket) -> str: 3531 expressions = self.bracket_offset_expressions(expression) 3532 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3533 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3534 3535 def all_sql(self, expression: exp.All) -> str: 3536 this = self.sql(expression, "this") 3537 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3538 this = self.wrap(this) 3539 return f"ALL {this}" 3540 3541 def any_sql(self, expression: exp.Any) -> str: 3542 this = self.sql(expression, "this") 3543 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3544 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3545 this = self.wrap(this) 3546 return f"ANY{this}" 3547 return f"ANY {this}" 3548 3549 def exists_sql(self, expression: exp.Exists) -> str: 3550 return f"EXISTS{self.wrap(expression)}" 3551 3552 def case_sql(self, expression: exp.Case) -> str: 3553 this = self.sql(expression, "this") 3554 statements = [f"CASE {this}" if this else "CASE"] 3555 3556 for e in expression.args["ifs"]: 3557 statements.append(f"WHEN {self.sql(e, 'this')}") 3558 statements.append(f"THEN {self.sql(e, 'true')}") 3559 3560 default = self.sql(expression, "default") 3561 3562 if default: 3563 statements.append(f"ELSE {default}") 3564 3565 statements.append("END") 3566 3567 if self.pretty and self.too_wide(statements): 3568 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3569 3570 return " ".join(statements) 3571 3572 def constraint_sql(self, expression: exp.Constraint) -> str: 3573 this = self.sql(expression, "this") 3574 expressions = self.expressions(expression, flat=True) 3575 return f"CONSTRAINT {this} {expressions}" 3576 3577 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3578 order = expression.args.get("order") 3579 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3580 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3581 3582 def extract_sql(self, expression: exp.Extract) -> str: 3583 import sqlglot.dialects.dialect 3584 3585 this = ( 3586 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3587 if self.NORMALIZE_EXTRACT_DATE_PARTS 3588 else expression.this 3589 ) 3590 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3591 expression_sql = self.sql(expression, "expression") 3592 3593 return f"EXTRACT({this_sql} FROM {expression_sql})" 3594 3595 def trim_sql(self, expression: exp.Trim) -> str: 3596 trim_type = self.sql(expression, "position") 3597 3598 if trim_type == "LEADING": 3599 func_name = "LTRIM" 3600 elif trim_type == "TRAILING": 3601 func_name = "RTRIM" 3602 else: 3603 func_name = "TRIM" 3604 3605 return self.func(func_name, expression.this, expression.expression) 3606 3607 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3608 args = expression.expressions 3609 if isinstance(expression, exp.ConcatWs): 3610 args = args[1:] # Skip the delimiter 3611 3612 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3613 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3614 3615 concat_coalesce = ( 3616 self.dialect.CONCAT_WS_COALESCE 3617 if isinstance(expression, exp.ConcatWs) 3618 else self.dialect.CONCAT_COALESCE 3619 ) 3620 3621 if not concat_coalesce and expression.args.get("coalesce"): 3622 3623 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3624 if not e.type: 3625 import sqlglot.optimizer.annotate_types 3626 3627 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3628 3629 if e.is_string or e.is_type(exp.DType.ARRAY): 3630 return e 3631 3632 return exp.func("coalesce", e, exp.Literal.string("")) 3633 3634 args = [_wrap_with_coalesce(e) for e in args] 3635 3636 return args 3637 3638 def concat_sql(self, expression: exp.Concat) -> str: 3639 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3640 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3641 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3642 # instead of coalescing them to empty string. 3643 import sqlglot.dialects.dialect 3644 3645 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3646 3647 expressions = self.convert_concat_args(expression) 3648 3649 # Some dialects don't allow a single-argument CONCAT call 3650 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3651 return self.sql(expressions[0]) 3652 3653 return self.func("CONCAT", *expressions) 3654 3655 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3656 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3657 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3658 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3659 all_args = expression.expressions 3660 expression.set("coalesce", True) 3661 return self.sql( 3662 exp.case() 3663 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3664 .else_(expression) 3665 ) 3666 3667 return self.func( 3668 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3669 ) 3670 3671 def check_sql(self, expression: exp.Check) -> str: 3672 this = self.sql(expression, key="this") 3673 return f"CHECK ({this})" 3674 3675 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3676 expressions = self.expressions(expression, flat=True) 3677 expressions = f" ({expressions})" if expressions else "" 3678 reference = self.sql(expression, "reference") 3679 reference = f" {reference}" if reference else "" 3680 delete = self.sql(expression, "delete") 3681 delete = f" ON DELETE {delete}" if delete else "" 3682 update = self.sql(expression, "update") 3683 update = f" ON UPDATE {update}" if update else "" 3684 options = self.expressions(expression, key="options", flat=True, sep=" ") 3685 options = f" {options}" if options else "" 3686 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3687 3688 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3689 this = self.sql(expression, "this") 3690 this = f" {this}" if this else "" 3691 expressions = self.expressions(expression, flat=True) 3692 include = self.sql(expression, "include") 3693 options = self.expressions(expression, key="options", flat=True, sep=" ") 3694 options = f" {options}" if options else "" 3695 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3696 3697 def if_sql(self, expression: exp.If) -> str: 3698 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3699 3700 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3701 if self.MATCH_AGAINST_TABLE_PREFIX: 3702 expressions = [] 3703 for expr in expression.expressions: 3704 if isinstance(expr, exp.Table): 3705 expressions.append(f"TABLE {self.sql(expr)}") 3706 else: 3707 expressions.append(expr) 3708 else: 3709 expressions = expression.expressions 3710 3711 modifier = expression.args.get("modifier") 3712 modifier = f" {modifier}" if modifier else "" 3713 return ( 3714 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3715 ) 3716 3717 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3718 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3719 3720 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3721 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3722 3723 if expression.args.get("escape"): 3724 path = self.escape_str(path) 3725 3726 if self.QUOTE_JSON_PATH: 3727 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3728 3729 return path 3730 3731 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3732 if isinstance(expression, exp.JSONPathPart): 3733 transform = self.TRANSFORMS.get(expression.__class__) 3734 if not callable(transform): 3735 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3736 return "" 3737 3738 return transform(self, expression) 3739 3740 if isinstance(expression, int): 3741 return str(expression) 3742 3743 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3744 escaped = expression.replace("'", "\\'") 3745 escaped = f"\\'{expression}\\'" 3746 else: 3747 escaped = expression.replace('"', '\\"') 3748 escaped = f'"{escaped}"' 3749 3750 return escaped 3751 3752 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3753 return f"{self.sql(expression, 'this')} FORMAT JSON" 3754 3755 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3756 # Output the Teradata column FORMAT override. 3757 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3758 this = self.sql(expression, "this") 3759 fmt = self.sql(expression, "format") 3760 return f"{this} (FORMAT {fmt})" 3761 3762 def _jsonobject_sql( 3763 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3764 ) -> str: 3765 null_handling = expression.args.get("null_handling") 3766 null_handling = f" {null_handling}" if null_handling else "" 3767 3768 unique_keys = expression.args.get("unique_keys") 3769 if unique_keys is not None: 3770 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3771 else: 3772 unique_keys = "" 3773 3774 return_type = self.sql(expression, "return_type") 3775 return_type = f" RETURNING {return_type}" if return_type else "" 3776 encoding = self.sql(expression, "encoding") 3777 encoding = f" ENCODING {encoding}" if encoding else "" 3778 3779 if not name: 3780 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3781 3782 return self.func( 3783 name, 3784 *expression.expressions, 3785 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3786 ) 3787 3788 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3789 null_handling = expression.args.get("null_handling") 3790 null_handling = f" {null_handling}" if null_handling else "" 3791 return_type = self.sql(expression, "return_type") 3792 return_type = f" RETURNING {return_type}" if return_type else "" 3793 strict = " STRICT" if expression.args.get("strict") else "" 3794 return self.func( 3795 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3796 ) 3797 3798 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3799 this = self.sql(expression, "this") 3800 order = self.sql(expression, "order") 3801 null_handling = expression.args.get("null_handling") 3802 null_handling = f" {null_handling}" if null_handling else "" 3803 return_type = self.sql(expression, "return_type") 3804 return_type = f" RETURNING {return_type}" if return_type else "" 3805 strict = " STRICT" if expression.args.get("strict") else "" 3806 return self.func( 3807 "JSON_ARRAYAGG", 3808 this, 3809 suffix=f"{order}{null_handling}{return_type}{strict})", 3810 ) 3811 3812 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3813 path = self.sql(expression, "path") 3814 path = f" PATH {path}" if path else "" 3815 nested_schema = self.sql(expression, "nested_schema") 3816 3817 if nested_schema: 3818 return f"NESTED{path} {nested_schema}" 3819 3820 this = self.sql(expression, "this") 3821 kind = self.sql(expression, "kind") 3822 kind = f" {kind}" if kind else "" 3823 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3824 3825 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3826 return f"{this}{kind}{format_json}{path}{ordinality}" 3827 3828 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3829 return self.func("COLUMNS", *expression.expressions) 3830 3831 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3832 this = self.sql(expression, "this") 3833 path = self.sql(expression, "path") 3834 path = f", {path}" if path else "" 3835 error_handling = expression.args.get("error_handling") 3836 error_handling = f" {error_handling}" if error_handling else "" 3837 empty_handling = expression.args.get("empty_handling") 3838 empty_handling = f" {empty_handling}" if empty_handling else "" 3839 schema = self.sql(expression, "schema") 3840 return self.func( 3841 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3842 ) 3843 3844 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3845 this = self.sql(expression, "this") 3846 kind = self.sql(expression, "kind") 3847 path = self.sql(expression, "path") 3848 path = f" {path}" if path else "" 3849 as_json = " AS JSON" if expression.args.get("as_json") else "" 3850 return f"{this} {kind}{path}{as_json}" 3851 3852 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3853 this = self.sql(expression, "this") 3854 path = self.sql(expression, "path") 3855 path = f", {path}" if path else "" 3856 expressions = self.expressions(expression) 3857 with_ = ( 3858 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3859 if expressions 3860 else "" 3861 ) 3862 return f"OPENJSON({this}{path}){with_}" 3863 3864 def in_sql(self, expression: exp.In) -> str: 3865 query = expression.args.get("query") 3866 unnest = expression.args.get("unnest") 3867 field = expression.args.get("field") 3868 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3869 3870 if query: 3871 in_sql = self.sql(query) 3872 elif unnest: 3873 in_sql = self.in_unnest_op(unnest) 3874 elif field: 3875 in_sql = self.sql(field) 3876 else: 3877 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3878 3879 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3880 3881 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3882 return f"(SELECT {self.sql(unnest)})" 3883 3884 def interval_sql(self, expression: exp.Interval) -> str: 3885 unit_expression = expression.args.get("unit") 3886 unit = self.sql(unit_expression) if unit_expression else "" 3887 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3888 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3889 unit = f" {unit}" if unit else "" 3890 3891 if self.SINGLE_STRING_INTERVAL: 3892 this = expression.this.name if expression.this else "" 3893 if this: 3894 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3895 return f"INTERVAL '{this}'{unit}" 3896 return f"INTERVAL '{this}{unit}'" 3897 return f"INTERVAL{unit}" 3898 3899 this = self.sql(expression, "this") 3900 if this: 3901 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3902 this = f" {this}" if unwrapped else f" ({this})" 3903 3904 return f"INTERVAL{this}{unit}" 3905 3906 def return_sql(self, expression: exp.Return) -> str: 3907 return f"RETURN {self.sql(expression, 'this')}" 3908 3909 def reference_sql(self, expression: exp.Reference) -> str: 3910 this = self.sql(expression, "this") 3911 expressions = self.expressions(expression, flat=True) 3912 expressions = f"({expressions})" if expressions else "" 3913 options = self.expressions(expression, key="options", flat=True, sep=" ") 3914 options = f" {options}" if options else "" 3915 return f"REFERENCES {this}{expressions}{options}" 3916 3917 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3918 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3919 parent = expression.parent 3920 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3921 3922 return self.func( 3923 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3924 ) 3925 3926 def paren_sql(self, expression: exp.Paren) -> str: 3927 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3928 return f"({sql}{self.seg(')', sep='')}" 3929 3930 def neg_sql(self, expression: exp.Neg) -> str: 3931 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3932 this_sql = self.sql(expression, "this") 3933 sep = " " if this_sql[0] == "-" else "" 3934 return f"-{sep}{this_sql}" 3935 3936 def not_sql(self, expression: exp.Not) -> str: 3937 return f"NOT {self.sql(expression, 'this')}" 3938 3939 def alias_sql(self, expression: exp.Alias) -> str: 3940 alias = self.sql(expression, "alias") 3941 alias = f" AS {alias}" if alias else "" 3942 return f"{self.sql(expression, 'this')}{alias}" 3943 3944 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3945 alias = expression.args["alias"] 3946 3947 parent = expression.parent 3948 pivot = parent and parent.parent 3949 3950 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3951 identifier_alias = isinstance(alias, exp.Identifier) 3952 literal_alias = isinstance(alias, exp.Literal) 3953 3954 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3955 alias.replace(exp.Literal.string(alias.output_name)) 3956 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3957 alias.replace(exp.to_identifier(alias.output_name)) 3958 3959 return self.alias_sql(expression) 3960 3961 def aliases_sql(self, expression: exp.Aliases) -> str: 3962 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3963 3964 def atindex_sql(self, expression: exp.AtIndex) -> str: 3965 this = self.sql(expression, "this") 3966 index = self.sql(expression, "expression") 3967 return f"{this} AT {index}" 3968 3969 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3970 this = self.sql(expression, "this") 3971 zone = self.sql(expression, "zone") 3972 return f"{this} AT TIME ZONE {zone}" 3973 3974 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3975 this = self.sql(expression, "this") 3976 zone = self.sql(expression, "zone") 3977 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3978 3979 def add_sql(self, expression: exp.Add) -> str: 3980 return self.binary(expression, "+") 3981 3982 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 3983 return self.connector_sql(expression, "AND", stack) 3984 3985 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 3986 return self.connector_sql(expression, "OR", stack) 3987 3988 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 3989 return self.connector_sql(expression, "XOR", stack) 3990 3991 def connector_sql( 3992 self, 3993 expression: exp.Connector, 3994 op: str, 3995 stack: list[str | exp.Expr] | None = None, 3996 ) -> str: 3997 if stack is not None: 3998 if expression.expressions: 3999 stack.append(self.expressions(expression, sep=f" {op} ")) 4000 else: 4001 stack.append(expression.right) 4002 if expression.comments and self.comments: 4003 op = self.maybe_comment(op, comments=expression.comments) 4004 stack.extend((op, expression.left)) 4005 return op 4006 4007 stack = [expression] 4008 sqls: list[str] = [] 4009 ops = set() 4010 4011 while stack: 4012 node = stack.pop() 4013 if isinstance(node, exp.Connector): 4014 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4015 else: 4016 sql = self.sql(node) 4017 if sqls and sqls[-1] in ops: 4018 sqls[-1] += f" {sql}" 4019 else: 4020 sqls.append(sql) 4021 4022 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4023 return sep.join(sqls) 4024 4025 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 4026 return self.binary(expression, "&") 4027 4028 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 4029 return self.binary(expression, "<<") 4030 4031 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 4032 return f"~{self.sql(expression, 'this')}" 4033 4034 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 4035 return self.binary(expression, "|") 4036 4037 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 4038 return self.binary(expression, ">>") 4039 4040 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 4041 return self.binary(expression, "^") 4042 4043 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4044 format_sql = self.sql(expression, "format") 4045 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4046 to_sql = self.sql(expression, "to") 4047 to_sql = f" {to_sql}" if to_sql else "" 4048 action = self.sql(expression, "action") 4049 action = f" {action}" if action else "" 4050 default = self.sql(expression, "default") 4051 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4052 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 4053 4054 # Base implementation that excludes safe, zone, and target_type metadata args 4055 def strtotime_sql(self, expression: exp.StrToTime) -> str: 4056 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 4057 4058 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 4059 zone = self.sql(expression, "this") 4060 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 4061 4062 def collate_sql(self, expression: exp.Collate) -> str: 4063 if self.COLLATE_IS_FUNC: 4064 return self.function_fallback_sql(expression) 4065 return self.binary(expression, "COLLATE") 4066 4067 def command_sql(self, expression: exp.Command) -> str: 4068 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 4069 4070 def comment_sql(self, expression: exp.Comment) -> str: 4071 this = self.sql(expression, "this") 4072 kind = expression.args["kind"] 4073 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4074 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4075 expression_sql = self.sql(expression, "expression") 4076 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 4077 4078 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4079 this = self.sql(expression, "this") 4080 delete = " DELETE" if expression.args.get("delete") else "" 4081 recompress = self.sql(expression, "recompress") 4082 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4083 to_disk = self.sql(expression, "to_disk") 4084 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4085 to_volume = self.sql(expression, "to_volume") 4086 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4087 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 4088 4089 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4090 where = self.sql(expression, "where") 4091 group = self.sql(expression, "group") 4092 aggregates = self.expressions(expression, key="aggregates") 4093 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4094 4095 if not (where or group or aggregates) and len(expression.expressions) == 1: 4096 return f"TTL {self.expressions(expression, flat=True)}" 4097 4098 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 4099 4100 def transaction_sql(self, expression: exp.Transaction) -> str: 4101 modes = self.expressions(expression, key="modes") 4102 modes = f" {modes}" if modes else "" 4103 return f"BEGIN{modes}" 4104 4105 def commit_sql(self, expression: exp.Commit) -> str: 4106 chain = expression.args.get("chain") 4107 if chain is not None: 4108 chain = " AND CHAIN" if chain else " AND NO CHAIN" 4109 4110 return f"COMMIT{chain or ''}" 4111 4112 def rollback_sql(self, expression: exp.Rollback) -> str: 4113 savepoint = expression.args.get("savepoint") 4114 savepoint = f" TO {savepoint}" if savepoint else "" 4115 return f"ROLLBACK{savepoint}" 4116 4117 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4118 this = self.sql(expression, "this") 4119 4120 dtype = self.sql(expression, "dtype") 4121 if dtype: 4122 collate = self.sql(expression, "collate") 4123 collate = f" COLLATE {collate}" if collate else "" 4124 using = self.sql(expression, "using") 4125 using = f" USING {using}" if using else "" 4126 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4127 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 4128 4129 default = self.sql(expression, "default") 4130 if default: 4131 return f"ALTER COLUMN {this} SET DEFAULT {default}" 4132 4133 comment = self.sql(expression, "comment") 4134 if comment: 4135 return f"ALTER COLUMN {this} COMMENT {comment}" 4136 4137 visible = expression.args.get("visible") 4138 if visible: 4139 return f"ALTER COLUMN {this} SET {visible}" 4140 4141 allow_null = expression.args.get("allow_null") 4142 drop = expression.args.get("drop") 4143 4144 if not drop and not allow_null: 4145 self.unsupported("Unsupported ALTER COLUMN syntax") 4146 4147 if allow_null is not None: 4148 keyword = "DROP" if drop else "SET" 4149 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4150 4151 return f"ALTER COLUMN {this} DROP DEFAULT" 4152 4153 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4154 this = self.sql(expression, "this") 4155 rename_from = self.sql(expression, "rename_from") 4156 if rename_from: 4157 if not self.SUPPORTS_CHANGE_COLUMN: 4158 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4159 return f"CHANGE COLUMN {rename_from} {this}" 4160 if not self.SUPPORTS_MODIFY_COLUMN: 4161 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4162 return f"MODIFY COLUMN {this}" 4163 4164 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4165 this = self.sql(expression, "this") 4166 4167 visible = expression.args.get("visible") 4168 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4169 4170 return f"ALTER INDEX {this} {visible_sql}" 4171 4172 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4173 this = self.sql(expression, "this") 4174 if not isinstance(expression.this, exp.Var): 4175 this = f"KEY DISTKEY {this}" 4176 return f"ALTER DISTSTYLE {this}" 4177 4178 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4179 compound = " COMPOUND" if expression.args.get("compound") else "" 4180 this = self.sql(expression, "this") 4181 expressions = self.expressions(expression, flat=True) 4182 expressions = f"({expressions})" if expressions else "" 4183 return f"ALTER{compound} SORTKEY {this or expressions}" 4184 4185 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4186 if not self.RENAME_TABLE_WITH_DB: 4187 # Remove db from tables 4188 expression = expression.transform( 4189 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4190 ).assert_is(exp.AlterRename) 4191 this = self.sql(expression, "this") 4192 to_kw = " TO" if include_to else "" 4193 return f"RENAME{to_kw} {this}" 4194 4195 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4196 exists = " IF EXISTS" if expression.args.get("exists") else "" 4197 old_column = self.sql(expression, "this") 4198 new_column = self.sql(expression, "to") 4199 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4200 4201 def alterset_sql(self, expression: exp.AlterSet) -> str: 4202 exprs = self.expressions(expression, flat=True) 4203 if self.ALTER_SET_WRAPPED: 4204 exprs = f"({exprs})" 4205 4206 return f"SET {exprs}" 4207 4208 def alter_sql(self, expression: exp.Alter) -> str: 4209 actions = expression.args["actions"] 4210 4211 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4212 actions[0], exp.ColumnDef 4213 ): 4214 actions_sql = self.expressions(expression, key="actions", flat=True) 4215 actions_sql = f"ADD {actions_sql}" 4216 else: 4217 actions_list = [] 4218 for action in actions: 4219 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4220 action_sql = self.add_column_sql(action) 4221 else: 4222 action_sql = self.sql(action) 4223 if isinstance(action, exp.Query): 4224 action_sql = f"AS {action_sql}" 4225 4226 actions_list.append(action_sql) 4227 4228 actions_sql = self.format_args(*actions_list).lstrip("\n") 4229 4230 iceberg = ( 4231 "ICEBERG " 4232 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4233 else "" 4234 ) 4235 exists = " IF EXISTS" if expression.args.get("exists") else "" 4236 on_cluster = self.sql(expression, "cluster") 4237 on_cluster = f" {on_cluster}" if on_cluster else "" 4238 only = " ONLY" if expression.args.get("only") else "" 4239 options = self.expressions(expression, key="options") 4240 options = f", {options}" if options else "" 4241 kind = self.sql(expression, "kind") 4242 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4243 check = " WITH CHECK" if expression.args.get("check") else "" 4244 cascade = ( 4245 " CASCADE" 4246 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4247 else "" 4248 ) 4249 this = self.sql(expression, "this") 4250 this = f" {this}" if this else "" 4251 4252 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4253 4254 def altersession_sql(self, expression: exp.AlterSession) -> str: 4255 items_sql = self.expressions(expression, flat=True) 4256 keyword = "UNSET" if expression.args.get("unset") else "SET" 4257 return f"{keyword} {items_sql}" 4258 4259 def add_column_sql(self, expression: exp.Expr) -> str: 4260 sql = self.sql(expression) 4261 if isinstance(expression, exp.Schema): 4262 column_text = " COLUMNS" 4263 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4264 column_text = " COLUMN" 4265 else: 4266 column_text = "" 4267 4268 return f"ADD{column_text} {sql}" 4269 4270 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4271 expressions = self.expressions(expression) 4272 exists = " IF EXISTS " if expression.args.get("exists") else " " 4273 return f"DROP{exists}{expressions}" 4274 4275 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4276 return "DROP PRIMARY KEY" 4277 4278 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4279 return f"ADD {self.expressions(expression, indent=False)}" 4280 4281 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4282 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4283 location = self.sql(expression, "location") 4284 location = f" {location}" if location else "" 4285 return f"ADD {exists}{self.sql(expression.this)}{location}" 4286 4287 def distinct_sql(self, expression: exp.Distinct) -> str: 4288 this = self.expressions(expression, flat=True) 4289 4290 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4291 case = exp.case() 4292 for arg in expression.expressions: 4293 case = case.when(arg.is_(exp.null()), exp.null()) 4294 this = self.sql(case.else_(f"({this})")) 4295 4296 this = f" {this}" if this else "" 4297 4298 on = self.sql(expression, "on") 4299 on = f" ON {on}" if on else "" 4300 return f"DISTINCT{this}{on}" 4301 4302 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4303 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4304 4305 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4306 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4307 4308 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4309 this_sql = self.sql(expression, "this") 4310 expression_sql = self.sql(expression, "expression") 4311 kind = "MAX" if expression.args.get("max") else "MIN" 4312 return f"{this_sql} HAVING {kind} {expression_sql}" 4313 4314 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4315 return self.sql( 4316 exp.Cast( 4317 this=exp.Div(this=expression.this, expression=expression.expression), 4318 to=exp.DataType(this=exp.DType.INT), 4319 ) 4320 ) 4321 4322 def dpipe_sql(self, expression: exp.DPipe) -> str: 4323 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4324 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4325 return self.binary(expression, "||") 4326 4327 def div_sql(self, expression: exp.Div) -> str: 4328 l, r = expression.left, expression.right 4329 4330 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4331 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4332 4333 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4334 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4335 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4336 4337 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4338 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4339 return self.sql( 4340 exp.cast( 4341 l / r, 4342 to=exp.DType.BIGINT, 4343 ) 4344 ) 4345 4346 return self.binary(expression, "/") 4347 4348 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4349 n = exp._wrap(expression.this, exp.Binary) 4350 d = exp._wrap(expression.expression, exp.Binary) 4351 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4352 4353 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4354 return self.binary(expression, "OVERLAPS") 4355 4356 def distance_sql(self, expression: exp.Distance) -> str: 4357 return self.binary(expression, "<->") 4358 4359 def distancend_sql(self, expression: exp.DistanceNd) -> str: 4360 return self.binary(expression, "<<->>") 4361 4362 def dot_sql(self, expression: exp.Dot) -> str: 4363 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4364 4365 def eq_sql(self, expression: exp.EQ) -> str: 4366 return self.binary(expression, "=") 4367 4368 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4369 return self.binary(expression, ":=") 4370 4371 def escape_sql(self, expression: exp.Escape) -> str: 4372 this = expression.this 4373 if ( 4374 isinstance(this, (exp.Like, exp.ILike)) 4375 and isinstance(this.expression, (exp.All, exp.Any)) 4376 and not self.SUPPORTS_LIKE_QUANTIFIERS 4377 ): 4378 return self._like_sql(this, escape=expression) 4379 return self.binary(expression, "ESCAPE") 4380 4381 def glob_sql(self, expression: exp.Glob) -> str: 4382 return self.binary(expression, "GLOB") 4383 4384 def gt_sql(self, expression: exp.GT) -> str: 4385 return self.binary(expression, ">") 4386 4387 def gte_sql(self, expression: exp.GTE) -> str: 4388 return self.binary(expression, ">=") 4389 4390 def is_sql(self, expression: exp.Is) -> str: 4391 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4392 return self.sql( 4393 expression.this if expression.expression.this else exp.not_(expression.this) 4394 ) 4395 return self.binary(expression, "IS") 4396 4397 def _like_sql( 4398 self, 4399 expression: exp.Like | exp.ILike, 4400 escape: exp.Escape | None = None, 4401 ) -> str: 4402 this = expression.this 4403 rhs = expression.expression 4404 4405 if isinstance(expression, exp.Like): 4406 exp_class: type[exp.Like | exp.ILike] = exp.Like 4407 op = "LIKE" 4408 else: 4409 exp_class = exp.ILike 4410 op = "ILIKE" 4411 4412 if expression.args.get("negate"): 4413 op = f"NOT {op}" 4414 4415 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4416 exprs = rhs.this.unnest() 4417 4418 if isinstance(exprs, exp.Tuple): 4419 exprs = exprs.expressions 4420 else: 4421 exprs = [exprs] 4422 4423 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4424 4425 def _make_like(expr: exp.Expression) -> exp.Expression: 4426 like: exp.Expression = exp_class( 4427 this=this, expression=expr, negate=expression.args.get("negate") 4428 ) 4429 if escape: 4430 like = exp.Escape(this=like, expression=escape.expression.copy()) 4431 return like 4432 4433 like_expr: exp.Expr = _make_like(exprs[0]) 4434 for expr in exprs[1:]: 4435 like_expr = connective(like_expr, _make_like(expr), copy=False) 4436 4437 parent = escape.parent if escape else expression.parent 4438 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4439 parent, exp.Condition 4440 ): 4441 like_expr = exp.paren(like_expr, copy=False) 4442 4443 return self.sql(like_expr) 4444 4445 return self.binary(expression, op) 4446 4447 def like_sql(self, expression: exp.Like) -> str: 4448 return self._like_sql(expression) 4449 4450 def ilike_sql(self, expression: exp.ILike) -> str: 4451 return self._like_sql(expression) 4452 4453 def match_sql(self, expression: exp.Match) -> str: 4454 return self.binary(expression, "MATCH") 4455 4456 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4457 return self.binary(expression, "SIMILAR TO") 4458 4459 def lt_sql(self, expression: exp.LT) -> str: 4460 return self.binary(expression, "<") 4461 4462 def lte_sql(self, expression: exp.LTE) -> str: 4463 return self.binary(expression, "<=") 4464 4465 def mod_sql(self, expression: exp.Mod) -> str: 4466 return self.binary(expression, "%") 4467 4468 def mul_sql(self, expression: exp.Mul) -> str: 4469 return self.binary(expression, "*") 4470 4471 def neq_sql(self, expression: exp.NEQ) -> str: 4472 return self.binary(expression, "<>") 4473 4474 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4475 return self.binary(expression, "IS NOT DISTINCT FROM") 4476 4477 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4478 return self.binary(expression, "IS DISTINCT FROM") 4479 4480 def sub_sql(self, expression: exp.Sub) -> str: 4481 return self.binary(expression, "-") 4482 4483 def trycast_sql(self, expression: exp.TryCast) -> str: 4484 return self.cast_sql(expression, safe_prefix="TRY_") 4485 4486 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4487 return self.cast_sql(expression) 4488 4489 def try_sql(self, expression: exp.Try) -> str: 4490 if not self.TRY_SUPPORTED: 4491 self.unsupported("Unsupported TRY function") 4492 return self.sql(expression, "this") 4493 4494 return self.func("TRY", expression.this) 4495 4496 def log_sql(self, expression: exp.Log) -> str: 4497 this = expression.this 4498 expr = expression.expression 4499 4500 if self.dialect.LOG_BASE_FIRST is False: 4501 this, expr = expr, this 4502 elif self.dialect.LOG_BASE_FIRST is None and expr: 4503 if this.name in ("2", "10"): 4504 return self.func(f"LOG{this.name}", expr) 4505 4506 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4507 4508 return self.func("LOG", this, expr) 4509 4510 def use_sql(self, expression: exp.Use) -> str: 4511 kind = self.sql(expression, "kind") 4512 kind = f" {kind}" if kind else "" 4513 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4514 this = f" {this}" if this else "" 4515 return f"USE{kind}{this}" 4516 4517 def binary(self, expression: exp.Binary, op: str) -> str: 4518 sqls: list[str] = [] 4519 stack: list[None | str | exp.Expr] = [expression] 4520 binary_type = type(expression) 4521 4522 while stack: 4523 node = stack.pop() 4524 4525 if type(node) is binary_type: 4526 op_func = node.args.get("operator") 4527 if op_func: 4528 op = f"OPERATOR({self.sql(op_func)})" 4529 4530 stack.append(node.args.get("expression")) 4531 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4532 stack.append(node.args.get("this")) 4533 else: 4534 sqls.append(self.sql(node)) 4535 4536 return "".join(sqls) 4537 4538 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4539 to_clause = self.sql(expression, "to") 4540 if to_clause: 4541 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4542 4543 return self.function_fallback_sql(expression) 4544 4545 def function_fallback_sql(self, expression: exp.Func) -> str: 4546 args = [] 4547 4548 for key in expression.arg_types: 4549 arg_value = expression.args.get(key) 4550 4551 if isinstance(arg_value, list): 4552 for value in arg_value: 4553 args.append(value) 4554 elif arg_value is not None: 4555 args.append(arg_value) 4556 4557 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4558 name = expression.meta_get("name") or expression.sql_name() 4559 else: 4560 name = expression.sql_name() 4561 4562 return self.func(name, *args) 4563 4564 def func( 4565 self, 4566 name: str, 4567 *args: t.Any, 4568 prefix: str = "(", 4569 suffix: str = ")", 4570 normalize: bool = True, 4571 ) -> str: 4572 name = self.normalize_func(name) if normalize else name 4573 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4574 4575 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4576 arg_sqls = tuple( 4577 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4578 ) 4579 if self.pretty and self.too_wide(arg_sqls): 4580 return self.indent( 4581 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4582 ) 4583 return sep.join(arg_sqls) 4584 4585 def too_wide(self, args: t.Iterable) -> bool: 4586 return sum(len(arg) for arg in args) > self.max_text_width 4587 4588 def format_time( 4589 self, 4590 expression: exp.Expr, 4591 inverse_time_mapping: dict[str, str] | None = None, 4592 inverse_time_trie: dict | None = None, 4593 ) -> str | None: 4594 return format_time( 4595 self.sql(expression, "format"), 4596 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4597 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4598 ) 4599 4600 def expressions( 4601 self, 4602 expression: exp.Expr | None = None, 4603 key: str | None = None, 4604 sqls: t.Collection[str | exp.Expr] | None = None, 4605 flat: bool = False, 4606 indent: bool = True, 4607 skip_first: bool = False, 4608 skip_last: bool = False, 4609 sep: str = ", ", 4610 prefix: str = "", 4611 dynamic: bool = False, 4612 new_line: bool = False, 4613 ) -> str: 4614 expressions = expression.args.get(key or "expressions") if expression else sqls 4615 4616 if not expressions: 4617 return "" 4618 4619 if flat: 4620 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4621 4622 num_sqls = len(expressions) 4623 result_sqls = [] 4624 4625 for i, e in enumerate(expressions): 4626 sql = self.sql(e, comment=False) 4627 if not sql: 4628 continue 4629 4630 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4631 4632 if self.pretty: 4633 if self.leading_comma: 4634 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4635 else: 4636 result_sqls.append( 4637 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4638 ) 4639 else: 4640 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4641 4642 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4643 if new_line: 4644 result_sqls.insert(0, "") 4645 result_sqls.append("") 4646 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4647 else: 4648 result_sql = "".join(result_sqls) 4649 4650 return ( 4651 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4652 if indent 4653 else result_sql 4654 ) 4655 4656 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4657 flat = flat or isinstance(expression.parent, exp.Properties) 4658 expressions_sql = self.expressions(expression, flat=flat) 4659 if flat: 4660 return f"{op} {expressions_sql}" 4661 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4662 4663 def naked_property(self, expression: exp.Property) -> str: 4664 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4665 if not property_name: 4666 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4667 return f"{property_name} {self.sql(expression, 'this')}" 4668 4669 def tag_sql(self, expression: exp.Tag) -> str: 4670 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4671 4672 def token_sql(self, token_type: TokenType) -> str: 4673 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4674 4675 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4676 this = self.sql(expression, "this") 4677 expressions = self.no_identify(self.expressions, expression) 4678 expressions = ( 4679 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4680 ) 4681 return f"{this}{expressions}" if expressions.strip() != "" else this 4682 4683 def macrooverloads_sql(self, expression: exp.MacroOverloads) -> str: 4684 return self.expressions(expression, flat=True) 4685 4686 def macrooverload_sql(self, expression: exp.MacroOverload) -> str: 4687 params = self.no_identify(self.expressions, expression, flat=True) 4688 body = self.sql(expression, "this") 4689 prefix = "TABLE " if expression.args.get("is_table") else "" 4690 return f"({params}) AS {prefix}{body}" 4691 4692 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4693 this = self.sql(expression, "this") 4694 expressions = self.expressions(expression, flat=True) 4695 return f"{this}({expressions})" 4696 4697 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4698 return self.binary(expression, "=>") 4699 4700 def when_sql(self, expression: exp.When) -> str: 4701 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4702 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4703 condition = self.sql(expression, "condition") 4704 condition = f" AND {condition}" if condition else "" 4705 4706 then_expression = expression.args.get("then") 4707 if isinstance(then_expression, exp.Insert): 4708 this = self.sql(then_expression, "this") 4709 this = f"INSERT {this}" if this else "INSERT" 4710 then = self.sql(then_expression, "expression") 4711 then = f"{this} VALUES {then}" if then else this 4712 elif isinstance(then_expression, exp.Update): 4713 if isinstance(then_expression.args.get("expressions"), exp.Star): 4714 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4715 else: 4716 expressions_sql = self.expressions(then_expression) 4717 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4718 else: 4719 then = self.sql(then_expression) 4720 4721 if isinstance(then_expression, (exp.Insert, exp.Update)): 4722 where = self.sql(then_expression, "where") 4723 if where and not self.SUPPORTS_MERGE_WHERE: 4724 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4725 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4726 where = "" 4727 then = f"{then}{where}" 4728 return f"WHEN {matched}{source}{condition} THEN {then}" 4729 4730 def whens_sql(self, expression: exp.Whens) -> str: 4731 return self.expressions(expression, sep=" ", indent=False) 4732 4733 def merge_sql(self, expression: exp.Merge) -> str: 4734 table = expression.this 4735 table_alias = "" 4736 4737 hints = table.args.get("hints") 4738 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4739 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4740 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4741 4742 this = self.sql(table) 4743 using = f"USING {self.sql(expression, 'using')}" 4744 whens = self.sql(expression, "whens") 4745 4746 on = self.sql(expression, "on") 4747 on = f"ON {on}" if on else "" 4748 4749 if not on: 4750 on = self.expressions(expression, key="using_cond") 4751 on = f"USING ({on})" if on else "" 4752 4753 returning = self.sql(expression, "returning") 4754 if returning: 4755 whens = f"{whens}{returning}" 4756 4757 sep = self.sep() 4758 4759 return self.prepend_ctes( 4760 expression, 4761 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4762 ) 4763 4764 @unsupported_args("format") 4765 def tochar_sql(self, expression: exp.ToChar) -> str: 4766 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4767 4768 @unsupported_args("default") 4769 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4770 if not self.SUPPORTS_TO_NUMBER: 4771 self.unsupported("Unsupported TO_NUMBER function") 4772 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4773 4774 fmt = expression.args.get("format") 4775 if not fmt: 4776 self.unsupported("Conversion format is required for TO_NUMBER") 4777 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4778 4779 return self.func("TO_NUMBER", expression.this, fmt) 4780 4781 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4782 this = self.sql(expression, "this") 4783 kind = self.sql(expression, "kind") 4784 settings_sql = self.expressions(expression, key="settings", sep=" ") 4785 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4786 return f"{this}({kind}{args})" 4787 4788 def dictrange_sql(self, expression: exp.DictRange) -> str: 4789 this = self.sql(expression, "this") 4790 max = self.sql(expression, "max") 4791 min = self.sql(expression, "min") 4792 return f"{this}(MIN {min} MAX {max})" 4793 4794 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4795 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4796 4797 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4798 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4799 4800 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4801 def uniquekeyproperty_sql( 4802 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4803 ) -> str: 4804 return f"{prefix} ({self.expressions(expression, flat=True)})" 4805 4806 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4807 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4808 expressions = self.expressions(expression, flat=True) 4809 expressions = f" {self.wrap(expressions)}" if expressions else "" 4810 buckets = self.sql(expression, "buckets") 4811 kind = self.sql(expression, "kind") 4812 buckets = f" BUCKETS {buckets}" if buckets else "" 4813 order = self.sql(expression, "order") 4814 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4815 4816 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4817 return "" 4818 4819 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4820 expressions = self.expressions(expression, key="expressions", flat=True) 4821 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4822 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4823 buckets = self.sql(expression, "buckets") 4824 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4825 4826 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4827 this = self.sql(expression, "this") 4828 having = self.sql(expression, "having") 4829 4830 if having: 4831 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4832 4833 return self.func("ANY_VALUE", this) 4834 4835 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4836 transform = self.func("TRANSFORM", *expression.expressions) 4837 row_format_before = self.sql(expression, "row_format_before") 4838 row_format_before = f" {row_format_before}" if row_format_before else "" 4839 record_writer = self.sql(expression, "record_writer") 4840 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4841 using = f" USING {self.sql(expression, 'command_script')}" 4842 schema = self.sql(expression, "schema") 4843 schema = f" AS {schema}" if schema else "" 4844 row_format_after = self.sql(expression, "row_format_after") 4845 row_format_after = f" {row_format_after}" if row_format_after else "" 4846 record_reader = self.sql(expression, "record_reader") 4847 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4848 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4849 4850 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4851 key_block_size = self.sql(expression, "key_block_size") 4852 if key_block_size: 4853 return f"KEY_BLOCK_SIZE = {key_block_size}" 4854 4855 using = self.sql(expression, "using") 4856 if using: 4857 return f"USING {using}" 4858 4859 parser = self.sql(expression, "parser") 4860 if parser: 4861 return f"WITH PARSER {parser}" 4862 4863 comment = self.sql(expression, "comment") 4864 if comment: 4865 return f"COMMENT {comment}" 4866 4867 visible = expression.args.get("visible") 4868 if visible is not None: 4869 return "VISIBLE" if visible else "INVISIBLE" 4870 4871 engine_attr = self.sql(expression, "engine_attr") 4872 if engine_attr: 4873 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4874 4875 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4876 if secondary_engine_attr: 4877 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4878 4879 self.unsupported("Unsupported index constraint option.") 4880 return "" 4881 4882 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4883 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4884 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4885 4886 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4887 kind = self.sql(expression, "kind") 4888 kind = f"{kind} INDEX" if kind else "INDEX" 4889 this = self.sql(expression, "this") 4890 this = f" {this}" if this else "" 4891 index_type = self.sql(expression, "index_type") 4892 index_type = f" USING {index_type}" if index_type else "" 4893 expressions = self.expressions(expression, flat=True) 4894 expressions = f" ({expressions})" if expressions else "" 4895 options = self.expressions(expression, key="options", sep=" ") 4896 options = f" {options}" if options else "" 4897 return f"{kind}{this}{index_type}{expressions}{options}" 4898 4899 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4900 if self.NVL2_SUPPORTED: 4901 return self.function_fallback_sql(expression) 4902 4903 case = exp.Case().when( 4904 expression.this.is_(exp.null()).not_(copy=False), 4905 expression.args["true"], 4906 copy=False, 4907 ) 4908 else_cond = expression.args.get("false") 4909 if else_cond: 4910 case.else_(else_cond, copy=False) 4911 4912 return self.sql(case) 4913 4914 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4915 this = self.sql(expression, "this") 4916 expr = self.sql(expression, "expression") 4917 position = self.sql(expression, "position") 4918 position = f", {position}" if position else "" 4919 iterator = self.sql(expression, "iterator") 4920 condition = self.sql(expression, "condition") 4921 condition = f" IF {condition}" if condition else "" 4922 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4923 4924 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4925 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4926 4927 def opclass_sql(self, expression: exp.Opclass) -> str: 4928 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4929 4930 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4931 model = self.sql(expression, "this") 4932 model = f"MODEL {model}" 4933 expr = expression.expression 4934 if expr: 4935 expr_sql = self.sql(expression, "expression") 4936 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 4937 else: 4938 expr_sql = None 4939 4940 parameters = self.sql(expression, "params_struct") or None 4941 4942 return self.func(name, model, expr_sql, parameters) 4943 4944 def predict_sql(self, expression: exp.Predict) -> str: 4945 return self._ml_sql(expression, "PREDICT") 4946 4947 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4948 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4949 return self._ml_sql(expression, name) 4950 4951 def generatetext_sql(self, expression: exp.GenerateText) -> str: 4952 return self._ml_sql(expression, "GENERATE_TEXT") 4953 4954 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 4955 return self._ml_sql(expression, "GENERATE_TABLE") 4956 4957 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 4958 return self._ml_sql(expression, "GENERATE_BOOL") 4959 4960 def generateint_sql(self, expression: exp.GenerateInt) -> str: 4961 return self._ml_sql(expression, "GENERATE_INT") 4962 4963 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 4964 return self._ml_sql(expression, "GENERATE_DOUBLE") 4965 4966 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4967 return self._ml_sql(expression, "TRANSLATE") 4968 4969 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4970 return self._ml_sql(expression, "FORECAST") 4971 4972 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4973 this_sql = self.sql(expression, "this") 4974 if isinstance(expression.this, exp.Table): 4975 this_sql = f"TABLE {this_sql}" 4976 4977 return self.func( 4978 "FORECAST", 4979 this_sql, 4980 expression.args.get("data_col"), 4981 expression.args.get("timestamp_col"), 4982 expression.args.get("model"), 4983 expression.args.get("id_cols"), 4984 expression.args.get("horizon"), 4985 expression.args.get("forecast_end_timestamp"), 4986 expression.args.get("confidence_level"), 4987 expression.args.get("output_historical_time_series"), 4988 expression.args.get("context_window"), 4989 ) 4990 4991 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4992 this_sql = self.sql(expression, "this") 4993 if isinstance(expression.this, exp.Table): 4994 this_sql = f"TABLE {this_sql}" 4995 4996 return self.func( 4997 "FEATURES_AT_TIME", 4998 this_sql, 4999 expression.args.get("time"), 5000 expression.args.get("num_rows"), 5001 expression.args.get("ignore_feature_nulls"), 5002 ) 5003 5004 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5005 this_sql = self.sql(expression, "this") 5006 if isinstance(expression.this, exp.Table): 5007 this_sql = f"TABLE {this_sql}" 5008 5009 query_table = self.sql(expression, "query_table") 5010 if isinstance(expression.args["query_table"], exp.Table): 5011 query_table = f"TABLE {query_table}" 5012 5013 return self.func( 5014 "VECTOR_SEARCH", 5015 this_sql, 5016 expression.args.get("column_to_search"), 5017 query_table, 5018 expression.args.get("query_column_to_search"), 5019 expression.args.get("top_k"), 5020 expression.args.get("distance_type"), 5021 expression.args.get("options"), 5022 ) 5023 5024 def forin_sql(self, expression: exp.ForIn) -> str: 5025 this = self.sql(expression, "this") 5026 expression_sql = self.sql(expression, "expression") 5027 return f"FOR {this} DO {expression_sql}" 5028 5029 def refresh_sql(self, expression: exp.Refresh) -> str: 5030 this = self.sql(expression, "this") 5031 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 5032 return f"REFRESH {kind}{this}" 5033 5034 def toarray_sql(self, expression: exp.ToArray) -> str: 5035 arg = expression.this 5036 if not arg.type: 5037 import sqlglot.optimizer.annotate_types 5038 5039 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5040 5041 if arg.is_type(exp.DType.ARRAY): 5042 return self.sql(arg) 5043 5044 cond_for_null = arg.is_(exp.null()) 5045 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 5046 5047 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5048 this = expression.this 5049 time_format = self.format_time(expression) 5050 5051 if time_format: 5052 return self.sql( 5053 exp.cast( 5054 exp.StrToTime(this=this, format=expression.args["format"]), 5055 exp.DType.TIME, 5056 ) 5057 ) 5058 5059 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5060 return self.sql(this) 5061 5062 return self.sql(exp.cast(this, exp.DType.TIME)) 5063 5064 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5065 this = expression.this 5066 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5067 return self.sql(this) 5068 5069 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 5070 5071 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5072 this = expression.this 5073 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5074 return self.sql(this) 5075 5076 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 5077 5078 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5079 this = expression.this 5080 time_format = self.format_time(expression) 5081 safe = expression.args.get("safe") 5082 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5083 return self.sql( 5084 exp.cast( 5085 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5086 exp.DType.DATE, 5087 ) 5088 ) 5089 5090 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5091 return self.sql(this) 5092 5093 if safe: 5094 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5095 5096 return self.sql(exp.cast(this, exp.DType.DATE)) 5097 5098 def unixdate_sql(self, expression: exp.UnixDate) -> str: 5099 return self.sql( 5100 exp.func( 5101 "DATEDIFF", 5102 expression.this, 5103 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5104 "day", 5105 ) 5106 ) 5107 5108 def lastday_sql(self, expression: exp.LastDay) -> str: 5109 if self.LAST_DAY_SUPPORTS_DATE_PART: 5110 return self.function_fallback_sql(expression) 5111 5112 unit = expression.text("unit") 5113 if unit and unit != "MONTH": 5114 self.unsupported("Date parts are not supported in LAST_DAY.") 5115 5116 return self.func("LAST_DAY", expression.this) 5117 5118 def dateadd_sql(self, expression: exp.DateAdd) -> str: 5119 import sqlglot.dialects.dialect 5120 5121 return self.func( 5122 "DATE_ADD", 5123 expression.this, 5124 expression.expression, 5125 sqlglot.dialects.dialect.unit_to_str(expression), 5126 ) 5127 5128 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5129 if self.CAN_IMPLEMENT_ARRAY_ANY: 5130 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5131 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5132 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5133 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5134 5135 import sqlglot.dialects.dialect 5136 5137 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5138 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5139 self.unsupported("ARRAY_ANY is unsupported") 5140 5141 return self.function_fallback_sql(expression) 5142 5143 def struct_sql(self, expression: exp.Struct) -> str: 5144 expression.set( 5145 "expressions", 5146 [ 5147 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5148 if isinstance(e, exp.PropertyEQ) 5149 else e 5150 for e in expression.expressions 5151 ], 5152 ) 5153 5154 return self.function_fallback_sql(expression) 5155 5156 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5157 low = self.sql(expression, "this") 5158 high = self.sql(expression, "expression") 5159 5160 return f"{low} TO {high}" 5161 5162 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5163 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5164 tables = f" {self.expressions(expression)}" 5165 5166 exists = " IF EXISTS" if expression.args.get("exists") else "" 5167 5168 on_cluster = self.sql(expression, "cluster") 5169 on_cluster = f" {on_cluster}" if on_cluster else "" 5170 5171 identity = self.sql(expression, "identity") 5172 identity = f" {identity} IDENTITY" if identity else "" 5173 5174 option = self.sql(expression, "option") 5175 option = f" {option}" if option else "" 5176 5177 partition = self.sql(expression, "partition") 5178 partition = f" {partition}" if partition else "" 5179 5180 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5181 5182 # This transpiles T-SQL's CONVERT function 5183 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5184 def convert_sql(self, expression: exp.Convert) -> str: 5185 to = expression.this 5186 value = expression.expression 5187 style = expression.args.get("style") 5188 safe = expression.args.get("safe") 5189 strict = expression.args.get("strict") 5190 5191 if not to or not value: 5192 return "" 5193 5194 # Retrieve length of datatype and override to default if not specified 5195 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5196 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5197 5198 transformed: exp.Expr | None = None 5199 cast = exp.Cast if strict else exp.TryCast 5200 5201 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5202 if isinstance(style, exp.Literal) and style.is_int: 5203 import sqlglot.dialects.tsql 5204 5205 style_value = style.name 5206 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5207 if not converted_style: 5208 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5209 5210 fmt = exp.Literal.string(converted_style) 5211 5212 if to.this == exp.DType.DATE: 5213 transformed = exp.StrToDate(this=value, format=fmt) 5214 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5215 transformed = exp.StrToTime(this=value, format=fmt) 5216 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5217 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5218 elif to.this == exp.DType.TEXT: 5219 transformed = exp.TimeToStr(this=value, format=fmt) 5220 5221 if not transformed: 5222 transformed = cast(this=value, to=to, safe=safe) 5223 5224 return self.sql(transformed) 5225 5226 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5227 this = expression.this 5228 if isinstance(this, exp.JSONPathWildcard): 5229 this = self.json_path_part(this) 5230 return f".{this}" if this else "" 5231 5232 if self.SAFE_JSON_PATH_KEY_RE.match(this): 5233 return f".{this}" 5234 5235 this = self.json_path_part(this) 5236 return ( 5237 f"[{this}]" 5238 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5239 else f".{this}" 5240 ) 5241 5242 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5243 this = self.json_path_part(expression.this) 5244 return f"[{this}]" if this else "" 5245 5246 def _simplify_unless_literal(self, expression: E) -> E: 5247 if not isinstance(expression, exp.Literal): 5248 import sqlglot.optimizer.simplify 5249 5250 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5251 5252 return expression 5253 5254 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5255 this = expression.this 5256 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5257 self.unsupported( 5258 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5259 ) 5260 return self.sql(this) 5261 5262 if self.IGNORE_NULLS_IN_FUNC and not expression.meta_get("inline"): 5263 if self.IGNORE_NULLS_BEFORE_ORDER: 5264 # The first modifier here will be the one closest to the AggFunc's arg 5265 mods = sorted( 5266 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5267 key=lambda x: ( 5268 0 5269 if isinstance(x, exp.HavingMax) 5270 else (1 if isinstance(x, exp.Order) else 2) 5271 ), 5272 ) 5273 5274 if mods: 5275 mod = mods[0] 5276 this = expression.__class__(this=mod.this.copy()) 5277 this.meta["inline"] = True 5278 mod.this.replace(this) 5279 return self.sql(expression.this) 5280 5281 agg_func = expression.find(exp.AggFunc) 5282 5283 if agg_func: 5284 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5285 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5286 5287 return f"{self.sql(expression, 'this')} {text}" 5288 5289 def _replace_line_breaks(self, string: str) -> str: 5290 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5291 if self.pretty: 5292 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5293 return string 5294 5295 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5296 option = self.sql(expression, "this") 5297 5298 if expression.expressions: 5299 upper = option.upper() 5300 5301 # Snowflake FILE_FORMAT options are separated by whitespace 5302 sep = " " if upper == "FILE_FORMAT" else ", " 5303 5304 # Databricks copy/format options do not set their list of values with EQ 5305 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5306 values = self.expressions(expression, flat=True, sep=sep) 5307 return f"{option}{op}({values})" 5308 5309 value = self.sql(expression, "expression") 5310 5311 if not value: 5312 return option 5313 5314 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5315 5316 return f"{option}{op}{value}" 5317 5318 def credentials_sql(self, expression: exp.Credentials) -> str: 5319 cred_expr = expression.args.get("credentials") 5320 if isinstance(cred_expr, exp.Literal): 5321 # Redshift case: CREDENTIALS <string> 5322 credentials = self.sql(expression, "credentials") 5323 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5324 else: 5325 # Snowflake case: CREDENTIALS = (...) 5326 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5327 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5328 5329 storage = self.sql(expression, "storage") 5330 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5331 5332 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5333 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5334 5335 iam_role = self.sql(expression, "iam_role") 5336 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5337 5338 region = self.sql(expression, "region") 5339 region = f" REGION {region}" if region else "" 5340 5341 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5342 5343 def copy_sql(self, expression: exp.Copy) -> str: 5344 this = self.sql(expression, "this") 5345 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5346 5347 credentials = self.sql(expression, "credentials") 5348 credentials = self.seg(credentials) if credentials else "" 5349 files = self.expressions(expression, key="files", flat=True) 5350 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5351 5352 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5353 params = self.expressions( 5354 expression, 5355 key="params", 5356 sep=sep, 5357 new_line=True, 5358 skip_last=True, 5359 skip_first=True, 5360 indent=self.COPY_PARAMS_ARE_WRAPPED, 5361 ) 5362 5363 if params: 5364 if self.COPY_PARAMS_ARE_WRAPPED: 5365 params = f" WITH ({params})" 5366 elif not self.pretty and (files or credentials): 5367 params = f" {params}" 5368 5369 return f"COPY{this}{kind} {files}{credentials}{params}" 5370 5371 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5372 return "" 5373 5374 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5375 on_sql = "ON" if expression.args.get("on") else "OFF" 5376 filter_col: str | None = self.sql(expression, "filter_column") 5377 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5378 retention_period: str | None = self.sql(expression, "retention_period") 5379 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5380 5381 if filter_col or retention_period: 5382 on_sql = self.func("ON", filter_col, retention_period) 5383 5384 return f"DATA_DELETION={on_sql}" 5385 5386 def maskingpolicycolumnconstraint_sql( 5387 self, expression: exp.MaskingPolicyColumnConstraint 5388 ) -> str: 5389 this = self.sql(expression, "this") 5390 expressions = self.expressions(expression, flat=True) 5391 expressions = f" USING ({expressions})" if expressions else "" 5392 return f"MASKING POLICY {this}{expressions}" 5393 5394 def gapfill_sql(self, expression: exp.GapFill) -> str: 5395 this = self.sql(expression, "this") 5396 this = f"TABLE {this}" 5397 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5398 5399 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5400 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5401 5402 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5403 this = self.sql(expression, "this") 5404 expr = expression.expression 5405 5406 if isinstance(expr, exp.Func): 5407 # T-SQL's CLR functions are case sensitive 5408 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5409 else: 5410 expr = self.sql(expression, "expression") 5411 5412 return self.scope_resolution(expr, this) 5413 5414 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5415 if self.PARSE_JSON_NAME is None: 5416 return self.sql(expression.this) 5417 5418 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5419 5420 def rand_sql(self, expression: exp.Rand) -> str: 5421 lower = self.sql(expression, "lower") 5422 upper = self.sql(expression, "upper") 5423 5424 if lower and upper: 5425 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5426 return self.func("RAND", expression.this) 5427 5428 def changes_sql(self, expression: exp.Changes) -> str: 5429 information = self.sql(expression, "information") 5430 information = f"INFORMATION => {information}" 5431 at_before = self.sql(expression, "at_before") 5432 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5433 end = self.sql(expression, "end") 5434 end = f"{self.seg('')}{end}" if end else "" 5435 5436 return f"CHANGES ({information}){at_before}{end}" 5437 5438 def pad_sql(self, expression: exp.Pad) -> str: 5439 prefix = "L" if expression.args.get("is_left") else "R" 5440 5441 fill_pattern = self.sql(expression, "fill_pattern") or None 5442 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5443 fill_pattern = "' '" 5444 5445 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5446 5447 def summarize_sql(self, expression: exp.Summarize) -> str: 5448 table = " TABLE" if expression.args.get("table") else "" 5449 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5450 5451 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5452 generate_series = exp.GenerateSeries(**expression.args) 5453 5454 parent = expression.parent 5455 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5456 parent = parent.parent 5457 5458 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5459 return self.sql(exp.Unnest(expressions=[generate_series])) 5460 5461 if isinstance(parent, exp.Select): 5462 self.unsupported("GenerateSeries projection unnesting is not supported.") 5463 5464 return self.sql(generate_series) 5465 5466 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5467 if self.SUPPORTS_CONVERT_TIMEZONE: 5468 return self.function_fallback_sql(expression) 5469 5470 source_tz = expression.args.get("source_tz") 5471 target_tz = expression.args.get("target_tz") 5472 timestamp = expression.args.get("timestamp") 5473 5474 if source_tz and timestamp: 5475 timestamp = exp.AtTimeZone( 5476 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5477 ) 5478 5479 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5480 5481 return self.sql(expr) 5482 5483 def json_sql(self, expression: exp.JSON) -> str: 5484 this = self.sql(expression, "this") 5485 this = f" {this}" if this else "" 5486 5487 _with = expression.args.get("with_") 5488 5489 if _with is None: 5490 with_sql = "" 5491 elif not _with: 5492 with_sql = " WITHOUT" 5493 else: 5494 with_sql = " WITH" 5495 5496 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5497 5498 return f"JSON{this}{with_sql}{unique_sql}" 5499 5500 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5501 path = self.sql(expression, "path") 5502 returning = self.sql(expression, "returning") 5503 returning = f" RETURNING {returning}" if returning else "" 5504 5505 on_condition = self.sql(expression, "on_condition") 5506 on_condition = f" {on_condition}" if on_condition else "" 5507 5508 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5509 5510 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5511 regexp = " REGEXP" if expression.args.get("regexp") else "" 5512 return f"SKIP{regexp} {self.sql(expression.expression)}" 5513 5514 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5515 else_ = "ELSE " if expression.args.get("else_") else "" 5516 condition = self.sql(expression, "expression") 5517 condition = f"WHEN {condition} THEN " if condition else else_ 5518 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5519 return f"{condition}{insert}" 5520 5521 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5522 kind = self.sql(expression, "kind") 5523 expressions = self.seg(self.expressions(expression, sep=" ")) 5524 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5525 return res 5526 5527 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5528 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5529 empty = expression.args.get("empty") 5530 empty = ( 5531 f"DEFAULT {empty} ON EMPTY" 5532 if isinstance(empty, exp.Expr) 5533 else self.sql(expression, "empty") 5534 ) 5535 5536 error = expression.args.get("error") 5537 error = ( 5538 f"DEFAULT {error} ON ERROR" 5539 if isinstance(error, exp.Expr) 5540 else self.sql(expression, "error") 5541 ) 5542 5543 if error and empty: 5544 error = ( 5545 f"{empty} {error}" 5546 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5547 else f"{error} {empty}" 5548 ) 5549 empty = "" 5550 5551 null = self.sql(expression, "null") 5552 5553 return f"{empty}{error}{null}" 5554 5555 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5556 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5557 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5558 5559 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5560 this = self.sql(expression, "this") 5561 path = self.sql(expression, "path") 5562 5563 passing = self.expressions(expression, "passing") 5564 passing = f" PASSING {passing}" if passing else "" 5565 5566 on_condition = self.sql(expression, "on_condition") 5567 on_condition = f" {on_condition}" if on_condition else "" 5568 5569 path = f"{path}{passing}{on_condition}" 5570 5571 return self.func("JSON_EXISTS", this, path) 5572 5573 def _add_arrayagg_null_filter( 5574 self, 5575 array_agg_sql: str, 5576 array_agg_expr: exp.ArrayAgg, 5577 column_expr: exp.Expr, 5578 ) -> str: 5579 """ 5580 Add NULL filter to ARRAY_AGG if dialect requires it. 5581 5582 Args: 5583 array_agg_sql: The generated ARRAY_AGG SQL string 5584 array_agg_expr: The ArrayAgg expression node 5585 column_expr: The column/expression to filter (before ORDER BY wrapping) 5586 5587 Returns: 5588 SQL string with FILTER clause added if needed 5589 """ 5590 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5591 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5592 if not ( 5593 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5594 ): 5595 return array_agg_sql 5596 5597 parent = array_agg_expr.parent 5598 if isinstance(parent, exp.Filter): 5599 parent_cond = parent.expression.this 5600 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5601 elif column_expr.find(exp.Column): 5602 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5603 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5604 this_sql = ( 5605 self.expressions(column_expr) 5606 if isinstance(column_expr, exp.Distinct) 5607 else self.sql(column_expr) 5608 ) 5609 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5610 5611 return array_agg_sql 5612 5613 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5614 array_agg = self.function_fallback_sql(expression) 5615 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5616 5617 def slice_sql(self, expression: exp.Slice) -> str: 5618 step = self.sql(expression, "step") 5619 end = self.sql(expression.expression) 5620 begin = self.sql(expression.this) 5621 5622 sql = f"{end}:{step}" if step else end 5623 return f"{begin}:{sql}" if sql else f"{begin}:" 5624 5625 def apply_sql(self, expression: exp.Apply) -> str: 5626 this = self.sql(expression, "this") 5627 expr = self.sql(expression, "expression") 5628 5629 return f"{this} APPLY({expr})" 5630 5631 def _grant_or_revoke_sql( 5632 self, 5633 expression: exp.Grant | exp.Revoke, 5634 keyword: str, 5635 preposition: str, 5636 grant_option_prefix: str = "", 5637 grant_option_suffix: str = "", 5638 ) -> str: 5639 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5640 5641 kind = self.sql(expression, "kind") 5642 kind = f" {kind}" if kind else "" 5643 5644 securable = self.sql(expression, "securable") 5645 securable = f" {securable}" if securable else "" 5646 5647 principals = self.expressions(expression, key="principals", flat=True) 5648 5649 if not expression.args.get("grant_option"): 5650 grant_option_prefix = grant_option_suffix = "" 5651 5652 # cascade for revoke only 5653 cascade = self.sql(expression, "cascade") 5654 cascade = f" {cascade}" if cascade else "" 5655 5656 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5657 5658 def grant_sql(self, expression: exp.Grant) -> str: 5659 return self._grant_or_revoke_sql( 5660 expression, 5661 keyword="GRANT", 5662 preposition="TO", 5663 grant_option_suffix=" WITH GRANT OPTION", 5664 ) 5665 5666 def revoke_sql(self, expression: exp.Revoke) -> str: 5667 return self._grant_or_revoke_sql( 5668 expression, 5669 keyword="REVOKE", 5670 preposition="FROM", 5671 grant_option_prefix="GRANT OPTION FOR ", 5672 ) 5673 5674 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5675 this = self.sql(expression, "this") 5676 columns = self.expressions(expression, flat=True) 5677 columns = f"({columns})" if columns else "" 5678 5679 return f"{this}{columns}" 5680 5681 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5682 this = self.sql(expression, "this") 5683 5684 kind = self.sql(expression, "kind") 5685 kind = f"{kind} " if kind else "" 5686 5687 return f"{kind}{this}" 5688 5689 def columns_sql(self, expression: exp.Columns) -> str: 5690 func = self.function_fallback_sql(expression) 5691 if expression.args.get("unpack"): 5692 func = f"*{func}" 5693 5694 return func 5695 5696 def overlay_sql(self, expression: exp.Overlay) -> str: 5697 this = self.sql(expression, "this") 5698 expr = self.sql(expression, "expression") 5699 from_sql = self.sql(expression, "from_") 5700 for_sql = self.sql(expression, "for_") 5701 for_sql = f" FOR {for_sql}" if for_sql else "" 5702 5703 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5704 5705 @unsupported_args("format") 5706 def todouble_sql(self, expression: exp.ToDouble) -> str: 5707 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5708 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5709 5710 def string_sql(self, expression: exp.String) -> str: 5711 this = expression.this 5712 zone = expression.args.get("zone") 5713 5714 if zone: 5715 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5716 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5717 # set for source_tz to transpile the time conversion before the STRING cast 5718 this = exp.ConvertTimezone( 5719 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5720 ) 5721 5722 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5723 5724 def median_sql(self, expression: exp.Median) -> str: 5725 if not self.SUPPORTS_MEDIAN: 5726 return self.sql( 5727 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5728 ) 5729 5730 return self.function_fallback_sql(expression) 5731 5732 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5733 filler = self.sql(expression, "this") 5734 filler = f" {filler}" if filler else "" 5735 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5736 return f"TRUNCATE{filler} {with_count}" 5737 5738 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5739 if self.SUPPORTS_UNIX_SECONDS: 5740 return self.function_fallback_sql(expression) 5741 5742 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5743 5744 return self.sql( 5745 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5746 ) 5747 5748 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5749 dim = expression.expression 5750 5751 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5752 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5753 if not (dim.is_int and dim.name == "1"): 5754 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5755 dim = None 5756 5757 # If dimension is required but not specified, default initialize it 5758 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5759 dim = exp.Literal.number(1) 5760 5761 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5762 5763 def attach_sql(self, expression: exp.Attach) -> str: 5764 this = self.sql(expression, "this") 5765 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5766 expressions = self.expressions(expression) 5767 expressions = f" ({expressions})" if expressions else "" 5768 5769 return f"ATTACH{exists_sql} {this}{expressions}" 5770 5771 def detach_sql(self, expression: exp.Detach) -> str: 5772 kind = self.sql(expression, "kind") 5773 kind = f" {kind}" if kind else "" 5774 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5775 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5776 exists = " IF EXISTS" if expression.args.get("exists") else "" 5777 if exists: 5778 kind = kind or " DATABASE" 5779 5780 this = self.sql(expression, "this") 5781 this = f" {this}" if this else "" 5782 cluster = self.sql(expression, "cluster") 5783 cluster = f" {cluster}" if cluster else "" 5784 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5785 sync = " SYNC" if expression.args.get("sync") else "" 5786 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5787 5788 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5789 this = self.sql(expression, "this") 5790 value = self.sql(expression, "expression") 5791 value = f" {value}" if value else "" 5792 return f"{this}{value}" 5793 5794 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5795 return ( 5796 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5797 ) 5798 5799 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5800 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5801 encode = f"{encode} {self.sql(expression, 'this')}" 5802 5803 properties = expression.args.get("properties") 5804 if properties: 5805 encode = f"{encode} {self.properties(properties)}" 5806 5807 return encode 5808 5809 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5810 this = self.sql(expression, "this") 5811 include = f"INCLUDE {this}" 5812 5813 column_def = self.sql(expression, "column_def") 5814 if column_def: 5815 include = f"{include} {column_def}" 5816 5817 alias = self.sql(expression, "alias") 5818 if alias: 5819 include = f"{include} AS {alias}" 5820 5821 return include 5822 5823 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5824 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5825 name = f"{prefix} {self.sql(expression, 'this')}" 5826 return self.func("XMLELEMENT", name, *expression.expressions) 5827 5828 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5829 this = self.sql(expression, "this") 5830 expr = self.sql(expression, "expression") 5831 expr = f"({expr})" if expr else "" 5832 return f"{this}{expr}" 5833 5834 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5835 partitions = self.expressions(expression, "partition_expressions") 5836 create = self.expressions(expression, "create_expressions") 5837 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5838 5839 def partitionbyrangepropertydynamic_sql( 5840 self, expression: exp.PartitionByRangePropertyDynamic 5841 ) -> str: 5842 start = self.sql(expression, "start") 5843 end = self.sql(expression, "end") 5844 5845 every = expression.args["every"] 5846 if isinstance(every, exp.Interval) and every.this.is_string: 5847 every.this.replace(exp.Literal.number(every.name)) 5848 5849 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5850 5851 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5852 name = self.sql(expression, "this") 5853 values = self.expressions(expression, flat=True) 5854 5855 return f"NAME {name} VALUE {values}" 5856 5857 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5858 kind = self.sql(expression, "kind") 5859 sample = self.sql(expression, "sample") 5860 return f"SAMPLE {sample} {kind}" 5861 5862 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5863 kind = self.sql(expression, "kind") 5864 option = self.sql(expression, "option") 5865 option = f" {option}" if option else "" 5866 this = self.sql(expression, "this") 5867 this = f" {this}" if this else "" 5868 columns = self.expressions(expression) 5869 columns = f" {columns}" if columns else "" 5870 return f"{kind}{option} STATISTICS{this}{columns}" 5871 5872 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5873 this = self.sql(expression, "this") 5874 columns = self.expressions(expression) 5875 inner_expression = self.sql(expression, "expression") 5876 inner_expression = f" {inner_expression}" if inner_expression else "" 5877 update_options = self.sql(expression, "update_options") 5878 update_options = f" {update_options} UPDATE" if update_options else "" 5879 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5880 5881 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5882 kind = self.sql(expression, "kind") 5883 kind = f" {kind}" if kind else "" 5884 return f"DELETE{kind} STATISTICS" 5885 5886 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5887 inner_expression = self.sql(expression, "expression") 5888 return f"LIST CHAINED ROWS{inner_expression}" 5889 5890 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5891 kind = self.sql(expression, "kind") 5892 this = self.sql(expression, "this") 5893 this = f" {this}" if this else "" 5894 inner_expression = self.sql(expression, "expression") 5895 return f"VALIDATE {kind}{this}{inner_expression}" 5896 5897 def analyze_sql(self, expression: exp.Analyze) -> str: 5898 options = self.expressions(expression, key="options", sep=" ") 5899 options = f" {options}" if options else "" 5900 kind = self.sql(expression, "kind") 5901 kind = f" {kind}" if kind else "" 5902 this = self.sql(expression, "this") 5903 this = f" {this}" if this else "" 5904 mode = self.sql(expression, "mode") 5905 mode = f" {mode}" if mode else "" 5906 properties = self.sql(expression, "properties") 5907 properties = f" {properties}" if properties else "" 5908 partition = self.sql(expression, "partition") 5909 partition = f" {partition}" if partition else "" 5910 inner_expression = self.sql(expression, "expression") 5911 inner_expression = f" {inner_expression}" if inner_expression else "" 5912 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5913 5914 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5915 this = self.sql(expression, "this") 5916 namespaces = self.expressions(expression, key="namespaces") 5917 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5918 passing = self.expressions(expression, key="passing") 5919 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5920 columns = self.expressions(expression, key="columns") 5921 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5922 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5923 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5924 5925 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5926 this = self.sql(expression, "this") 5927 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5928 5929 def export_sql(self, expression: exp.Export) -> str: 5930 this = self.sql(expression, "this") 5931 connection = self.sql(expression, "connection") 5932 connection = f"WITH CONNECTION {connection} " if connection else "" 5933 options = self.sql(expression, "options") 5934 return f"EXPORT DATA {connection}{options} AS {this}" 5935 5936 def declare_sql(self, expression: exp.Declare) -> str: 5937 replace = "OR REPLACE " if expression.args.get("replace") else "" 5938 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 5939 5940 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5941 variables = self.expressions(expression, "this") 5942 default = self.sql(expression, "default") 5943 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5944 5945 kind = self.sql(expression, "kind") 5946 if isinstance(expression.args.get("kind"), exp.Schema): 5947 kind = f"TABLE {kind}" 5948 5949 kind = f" {kind}" if kind else "" 5950 5951 return f"{variables}{kind}{default}" 5952 5953 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5954 kind = self.sql(expression, "kind") 5955 this = self.sql(expression, "this") 5956 set = self.sql(expression, "expression") 5957 using = self.sql(expression, "using") 5958 using = f" USING {using}" if using else "" 5959 5960 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5961 5962 return f"{kind_sql} {this} SET {set}{using}" 5963 5964 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5965 params = self.expressions(expression, key="params", flat=True) 5966 return self.func(expression.name, *expression.expressions) + f"({params})" 5967 5968 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5969 return self.func(expression.name, *expression.expressions) 5970 5971 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5972 return self.anonymousaggfunc_sql(expression) 5973 5974 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5975 return self.parameterizedagg_sql(expression) 5976 5977 def show_sql(self, expression: exp.Show) -> str: 5978 self.unsupported("Unsupported SHOW statement") 5979 return "" 5980 5981 def install_sql(self, expression: exp.Install) -> str: 5982 self.unsupported("Unsupported INSTALL statement") 5983 return "" 5984 5985 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5986 # Snowflake GET/PUT statements: 5987 # PUT <file> <internalStage> <properties> 5988 # GET <internalStage> <file> <properties> 5989 props = expression.args.get("properties") 5990 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5991 this = self.sql(expression, "this") 5992 target = self.sql(expression, "target") 5993 5994 if isinstance(expression, exp.Put): 5995 return f"PUT {this} {target}{props_sql}" 5996 else: 5997 return f"GET {target} {this}{props_sql}" 5998 5999 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6000 this = self.sql(expression, "this") 6001 expr = self.sql(expression, "expression") 6002 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6003 return f"TRANSLATE({this} USING {expr}{with_error})" 6004 6005 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6006 if self.SUPPORTS_DECODE_CASE: 6007 return self.func("DECODE", *expression.expressions) 6008 6009 decode_expr, *expressions = expression.expressions 6010 6011 ifs = [] 6012 for search, result in zip(expressions[::2], expressions[1::2]): 6013 if isinstance(search, exp.Literal): 6014 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6015 elif isinstance(search, exp.Null): 6016 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6017 else: 6018 if isinstance(search, exp.Binary): 6019 search = exp.paren(search) 6020 6021 cond = exp.or_( 6022 decode_expr.eq(search), 6023 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6024 copy=False, 6025 ) 6026 ifs.append(exp.If(this=cond, true=result)) 6027 6028 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6029 return self.sql(case) 6030 6031 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6032 this = self.sql(expression, "this") 6033 this = self.seg(this, sep="") 6034 dimensions = self.expressions( 6035 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6036 ) 6037 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6038 metrics = self.expressions( 6039 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6040 ) 6041 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6042 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6043 facts = self.seg(f"FACTS {facts}") if facts else "" 6044 where = self.sql(expression, "where") 6045 where = self.seg(f"WHERE {where}") if where else "" 6046 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6047 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 6048 6049 def getextract_sql(self, expression: exp.GetExtract) -> str: 6050 this = expression.this 6051 expr = expression.expression 6052 6053 if not this.type or not expression.type: 6054 import sqlglot.optimizer.annotate_types 6055 6056 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6057 6058 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6059 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6060 6061 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 6062 6063 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 6064 return self.sql( 6065 exp.DateAdd( 6066 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 6067 expression=expression.this, 6068 unit=exp.var("DAY"), 6069 ) 6070 ) 6071 6072 def space_sql(self: Generator, expression: exp.Space) -> str: 6073 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 6074 6075 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 6076 return f"BUILD {self.sql(expression, 'this')}" 6077 6078 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6079 method = self.sql(expression, "method") 6080 kind = expression.args.get("kind") 6081 if not kind: 6082 return f"REFRESH {method}" 6083 6084 every = self.sql(expression, "every") 6085 unit = self.sql(expression, "unit") 6086 every = f" EVERY {every} {unit}" if every else "" 6087 starts = self.sql(expression, "starts") 6088 starts = f" STARTS {starts}" if starts else "" 6089 6090 return f"REFRESH {method} ON {kind}{every}{starts}" 6091 6092 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 6093 self.unsupported("The model!attribute syntax is not supported") 6094 return "" 6095 6096 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 6097 return self.func("DIRECTORY", expression.this) 6098 6099 def uuid_sql(self, expression: exp.Uuid) -> str: 6100 is_string = expression.args.get("is_string", False) 6101 uuid_func_sql = self.func("UUID") 6102 6103 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6104 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6105 6106 return uuid_func_sql 6107 6108 def initcap_sql(self, expression: exp.Initcap) -> str: 6109 delimiters = expression.expression 6110 6111 if delimiters: 6112 # do not generate delimiters arg if we are round-tripping from default delimiters 6113 if ( 6114 delimiters.is_string 6115 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6116 ): 6117 delimiters = None 6118 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6119 self.unsupported("INITCAP does not support custom delimiters") 6120 delimiters = None 6121 6122 return self.func("INITCAP", expression.this, delimiters) 6123 6124 def localtime_sql(self, expression: exp.Localtime) -> str: 6125 this = expression.this 6126 return self.func("LOCALTIME", this) if this else "LOCALTIME" 6127 6128 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 6129 this = expression.this 6130 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 6131 6132 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6133 this = expression.this.name.upper() 6134 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 6135 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 6136 return "WEEK" 6137 6138 return self.func("WEEK", expression.this) 6139 6140 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 6141 this = self.expressions(expression) 6142 charset = self.sql(expression, "charset") 6143 using = f" USING {charset}" if charset else "" 6144 return self.func(name, this + using) 6145 6146 def block_sql(self, expression: exp.Block) -> str: 6147 expressions = self.expressions(expression, sep="; ", flat=True) 6148 return f"{expressions}" if expressions else "" 6149 6150 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 6151 self.unsupported("Unsupported Stored Procedure syntax") 6152 return "" 6153 6154 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6155 self.unsupported("Unsupported If block syntax") 6156 return "" 6157 6158 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6159 self.unsupported("Unsupported While block syntax") 6160 return "" 6161 6162 def execute_sql(self, expression: exp.Execute) -> str: 6163 self.unsupported("Unsupported Execute syntax") 6164 return "" 6165 6166 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6167 self.unsupported("Unsupported Execute syntax") 6168 return "" 6169 6170 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6171 props = self.expressions(expression, sep=" ") 6172 return f"MODIFY {props}" 6173 6174 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6175 kind = expression.args.get("kind") 6176 return f"USING {kind} {self.sql(expression, 'this')}" 6177 6178 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6179 this = self.sql(expression, "this") 6180 to = self.sql(expression, "to") 6181 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)
857 def __init__( 858 self, 859 pretty: bool | int | None = None, 860 identify: str | bool = False, 861 normalize: bool = False, 862 pad: int = 2, 863 indent: int = 2, 864 normalize_functions: str | bool | None = None, 865 unsupported_level: ErrorLevel = ErrorLevel.WARN, 866 max_unsupported: int = 3, 867 leading_comma: bool = False, 868 max_text_width: int = 80, 869 comments: bool = True, 870 dialect: DialectType = None, 871 ): 872 import sqlglot 873 import sqlglot.dialects.dialect 874 875 self.pretty = pretty if pretty is not None else sqlglot.pretty 876 self.identify = identify 877 self.normalize = normalize 878 self.pad = pad 879 self._indent = indent 880 self.unsupported_level = unsupported_level 881 self.max_unsupported = max_unsupported 882 self.leading_comma = leading_comma 883 self.max_text_width = max_text_width 884 self.comments = comments 885 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 886 887 # This is both a Dialect property and a Generator argument, so we prioritize the latter 888 self.normalize_functions = ( 889 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 890 ) 891 892 self.unsupported_messages: list[str] = [] 893 self._escaped_quote_end: str = ( 894 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 895 ) 896 self._escaped_byte_quote_end: str = ( 897 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 898 if self.dialect.BYTE_END 899 else "" 900 ) 901 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 902 903 self._next_name = name_sequence("_t") 904 905 self._identifier_start = self.dialect.IDENTIFIER_START 906 self._identifier_end = self.dialect.IDENTIFIER_END 907 908 self._quote_json_path_key_using_brackets = True 909 910 cls = type(self) 911 dispatch = _DISPATCH_CACHE.get(cls) 912 if dispatch is None: 913 dispatch = _build_dispatch(cls) 914 _DISPATCH_CACHE[cls] = dispatch 915 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.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.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.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.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'>, <class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathWildcard'>}
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.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.NVARCHAR: 'NVARCHAR'>, <DType.VARCHAR: 'VARCHAR'>, <DType.NCHAR: 'NCHAR'>, <DType.CHAR: 'CHAR'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
()
917 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 918 """ 919 Generates the SQL string corresponding to the given syntax tree. 920 921 Args: 922 expression: The syntax tree. 923 copy: Whether to copy the expression. The generator performs mutations so 924 it is safer to copy. 925 926 Returns: 927 The SQL string corresponding to `expression`. 928 """ 929 if copy: 930 expression = expression.copy() 931 932 expression = self.preprocess(expression) 933 934 self.unsupported_messages = [] 935 sql = self.sql(expression).strip() 936 937 if self.pretty: 938 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 939 940 if self.unsupported_level == ErrorLevel.IGNORE: 941 return sql 942 943 if self.unsupported_level == ErrorLevel.WARN: 944 for msg in self.unsupported_messages: 945 logger.warning(msg) 946 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 947 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 948 949 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.
951 def preprocess(self, expression: exp.Expr) -> exp.Expr: 952 """Apply generic preprocessing transformations to a given expression.""" 953 expression = self._move_ctes_to_top_level(expression) 954 955 if self.ENSURE_BOOLS: 956 import sqlglot.transforms 957 958 expression = sqlglot.transforms.ensure_bools(expression) 959 960 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
984 def sanitize_comment(self, comment: str) -> str: 985 comment = " " + comment if comment[0].strip() else comment 986 comment = comment + " " if comment[-1].strip() else comment 987 988 # Escape block comment markers to prevent premature closure or unintended nesting. 989 # This is necessary because single-line comments (--) are converted to block comments 990 # (/* */) on output, and any */ in the original text would close the comment early. 991 comment = comment.replace("*/", "* /").replace("/*", "/ *") 992 993 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
995 def maybe_comment( 996 self, 997 sql: str, 998 expression: exp.Expr | None = None, 999 comments: list[str] | None = None, 1000 separated: bool = False, 1001 ) -> str: 1002 comments = ( 1003 ((expression and expression.comments) if comments is None else comments) # type: ignore 1004 if self.comments 1005 else None 1006 ) 1007 1008 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1009 return sql 1010 1011 comments_sql = " ".join( 1012 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 1013 ) 1014 1015 if not comments_sql: 1016 return sql 1017 1018 comments_sql = self._replace_line_breaks(comments_sql) 1019 1020 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1021 return ( 1022 f"{self.sep()}{comments_sql}{sql}" 1023 if not sql or sql[0].isspace() 1024 else f"{comments_sql}{self.sep()}{sql}" 1025 ) 1026 1027 return f"{sql} {comments_sql}"
1029 def wrap(self, expression: exp.Expr | str) -> str: 1030 this_sql = ( 1031 self.sql(expression) 1032 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1033 else self.sql(expression, "this") 1034 ) 1035 if not this_sql: 1036 return "()" 1037 1038 this_sql = self.indent(this_sql, level=1, pad=0) 1039 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:
1055 def indent( 1056 self, 1057 sql: str, 1058 level: int = 0, 1059 pad: int | None = None, 1060 skip_first: bool = False, 1061 skip_last: bool = False, 1062 ) -> str: 1063 if not self.pretty or not sql: 1064 return sql 1065 1066 pad = self.pad if pad is None else pad 1067 lines = sql.split("\n") 1068 1069 return "\n".join( 1070 ( 1071 line 1072 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1073 else f"{' ' * (level * self._indent + pad)}{line}" 1074 ) 1075 for i, line in enumerate(lines) 1076 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1078 def sql( 1079 self, 1080 expression: str | exp.Expr | None, 1081 key: str | None = None, 1082 comment: bool = True, 1083 ) -> str: 1084 if not expression: 1085 return "" 1086 1087 if isinstance(expression, str): 1088 return expression 1089 1090 if key: 1091 value = expression.args.get(key) 1092 if value: 1093 return self.sql(value) 1094 return "" 1095 1096 handler = self._dispatch.get(expression.__class__) 1097 1098 if handler: 1099 sql = handler(self, expression) 1100 elif isinstance(expression, exp.Func): 1101 sql = self.function_fallback_sql(expression) 1102 elif isinstance(expression, exp.Property): 1103 sql = self.property_sql(expression) 1104 else: 1105 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1106 1107 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1114 def cache_sql(self, expression: exp.Cache) -> str: 1115 lazy = " LAZY" if expression.args.get("lazy") else "" 1116 table = self.sql(expression, "this") 1117 options = expression.args.get("options") 1118 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1119 sql = self.sql(expression, "expression") 1120 sql = f" AS{self.sep()}{sql}" if sql else "" 1121 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1122 return self.prepend_ctes(expression, sql)
1140 def column_sql(self, expression: exp.Column) -> str: 1141 join_mark = " (+)" if expression.args.get("join_mark") else "" 1142 1143 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1144 join_mark = "" 1145 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1146 1147 return f"{self.column_parts(expression)}{join_mark}"
1158 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1159 column = self.sql(expression, "this") 1160 kind = self.sql(expression, "kind") 1161 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1162 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1163 kind = f"{sep}{kind}" if kind else "" 1164 constraints = f" {constraints}" if constraints else "" 1165 position = self.sql(expression, "position") 1166 position = f" {position}" if position else "" 1167 1168 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1169 kind = "" 1170 1171 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:
1178 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1179 this = self.sql(expression, "this") 1180 if expression.args.get("not_null"): 1181 persisted = " PERSISTED NOT NULL" 1182 elif expression.args.get("persisted"): 1183 persisted = " PERSISTED" 1184 else: 1185 persisted = "" 1186 1187 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:
1200 def generatedasidentitycolumnconstraint_sql( 1201 self, expression: exp.GeneratedAsIdentityColumnConstraint 1202 ) -> str: 1203 this = "" 1204 if expression.this is not None: 1205 on_null = " ON NULL" if expression.args.get("on_null") else "" 1206 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1207 1208 start = expression.args.get("start") 1209 start = f"START WITH {start}" if start else "" 1210 increment = expression.args.get("increment") 1211 increment = f" INCREMENT BY {increment}" if increment else "" 1212 minvalue = expression.args.get("minvalue") 1213 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1214 maxvalue = expression.args.get("maxvalue") 1215 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1216 cycle = expression.args.get("cycle") 1217 cycle_sql = "" 1218 1219 if cycle is not None: 1220 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1221 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1222 1223 sequence_opts = "" 1224 if start or increment or cycle_sql: 1225 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1226 sequence_opts = f" ({sequence_opts.strip()})" 1227 1228 expr = self.sql(expression, "expression") 1229 expr = f"({expr})" if expr else "IDENTITY" 1230 1231 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1233 def generatedasrowcolumnconstraint_sql( 1234 self, expression: exp.GeneratedAsRowColumnConstraint 1235 ) -> str: 1236 start = "START" if expression.args.get("start") else "END" 1237 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1238 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:
1248 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1249 desc = expression.args.get("desc") 1250 if desc is not None: 1251 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1252 options = self.expressions(expression, key="options", flat=True, sep=" ") 1253 options = f" {options}" if options else "" 1254 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1256 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1257 this = self.sql(expression, "this") 1258 this = f" {this}" if this else "" 1259 index_type = expression.args.get("index_type") 1260 index_type = f" USING {index_type}" if index_type else "" 1261 on_conflict = self.sql(expression, "on_conflict") 1262 on_conflict = f" {on_conflict}" if on_conflict else "" 1263 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1264 options = self.expressions(expression, key="options", flat=True, sep=" ") 1265 options = f" {options}" if options else "" 1266 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1268 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1269 input_ = expression.args.get("input_") 1270 output = expression.args.get("output") 1271 variadic = expression.args.get("variadic") 1272 1273 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1274 if variadic: 1275 return "VARIADIC" 1276 1277 if input_ and output: 1278 return f"IN{self.INOUT_SEPARATOR}OUT" 1279 if input_: 1280 return "IN" 1281 if output: 1282 return "OUT" 1283 1284 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1289 def create_sql(self, expression: exp.Create) -> str: 1290 kind = self.sql(expression, "kind") 1291 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1292 1293 properties = expression.args.get("properties") 1294 1295 if ( 1296 kind == "TRIGGER" 1297 and properties 1298 and properties.expressions 1299 and isinstance(properties.expressions[0], exp.TriggerProperties) 1300 and properties.expressions[0].args.get("constraint") 1301 ): 1302 kind = f"CONSTRAINT {kind}" 1303 1304 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1305 1306 this = self.createable_sql(expression, properties_locs) 1307 1308 properties_sql = "" 1309 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1310 exp.Properties.Location.POST_WITH 1311 ): 1312 props_ast = exp.Properties( 1313 expressions=[ 1314 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1315 *properties_locs[exp.Properties.Location.POST_WITH], 1316 ] 1317 ) 1318 props_ast.parent = expression 1319 properties_sql = self.sql(props_ast) 1320 1321 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1322 properties_sql = self.sep() + properties_sql 1323 elif not self.pretty: 1324 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1325 properties_sql = f" {properties_sql}" 1326 1327 begin = " BEGIN" if expression.args.get("begin") else "" 1328 1329 expression_sql = self.sql(expression, "expression") 1330 if expression_sql: 1331 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1332 1333 if not isinstance(expression.expression, exp.MacroOverloads) and ( 1334 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return) 1335 ): 1336 postalias_props_sql = "" 1337 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1338 postalias_props_sql = self.properties( 1339 exp.Properties( 1340 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1341 ), 1342 wrapped=False, 1343 ) 1344 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1345 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1346 1347 postindex_props_sql = "" 1348 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1349 postindex_props_sql = self.properties( 1350 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1351 wrapped=False, 1352 prefix=" ", 1353 ) 1354 1355 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1356 indexes = f" {indexes}" if indexes else "" 1357 index_sql = indexes + postindex_props_sql 1358 1359 replace = " OR REPLACE" if expression.args.get("replace") else "" 1360 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1361 unique = " UNIQUE" if expression.args.get("unique") else "" 1362 1363 clustered = expression.args.get("clustered") 1364 if clustered is None: 1365 clustered_sql = "" 1366 elif clustered: 1367 clustered_sql = " CLUSTERED COLUMNSTORE" 1368 else: 1369 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1370 1371 postcreate_props_sql = "" 1372 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1373 postcreate_props_sql = self.properties( 1374 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1375 sep=" ", 1376 prefix=" ", 1377 wrapped=False, 1378 ) 1379 1380 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1381 1382 postexpression_props_sql = "" 1383 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1384 postexpression_props_sql = self.properties( 1385 exp.Properties( 1386 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1387 ), 1388 sep=" ", 1389 prefix=" ", 1390 wrapped=False, 1391 ) 1392 1393 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1394 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1395 no_schema_binding = ( 1396 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1397 ) 1398 1399 clone = self.sql(expression, "clone") 1400 clone = f" {clone}" if clone else "" 1401 1402 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1403 properties_expression = f"{expression_sql}{properties_sql}" 1404 else: 1405 properties_expression = f"{properties_sql}{expression_sql}" 1406 1407 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1408 return self.prepend_ctes(expression, expression_sql)
1410 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1411 start = self.sql(expression, "start") 1412 start = f"START WITH {start}" if start else "" 1413 increment = self.sql(expression, "increment") 1414 increment = f" INCREMENT BY {increment}" if increment else "" 1415 minvalue = self.sql(expression, "minvalue") 1416 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1417 maxvalue = self.sql(expression, "maxvalue") 1418 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1419 owned = self.sql(expression, "owned") 1420 owned = f" OWNED BY {owned}" if owned else "" 1421 1422 cache = expression.args.get("cache") 1423 if cache is None: 1424 cache_str = "" 1425 elif cache is True: 1426 cache_str = " CACHE" 1427 else: 1428 cache_str = f" CACHE {cache}" 1429 1430 options = self.expressions(expression, key="options", flat=True, sep=" ") 1431 options = f" {options}" if options else "" 1432 1433 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1435 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1436 timing = expression.args.get("timing", "") 1437 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1438 timing_events = f"{timing} {events}".strip() if timing or events else "" 1439 1440 parts = [timing_events, "ON", self.sql(expression, "table")] 1441 1442 if referenced_table := expression.args.get("referenced_table"): 1443 parts.extend(["FROM", self.sql(referenced_table)]) 1444 1445 if deferrable := expression.args.get("deferrable"): 1446 parts.append(deferrable) 1447 1448 if initially := expression.args.get("initially"): 1449 parts.append(f"INITIALLY {initially}") 1450 1451 if referencing := expression.args.get("referencing"): 1452 parts.append(self.sql(referencing)) 1453 1454 if for_each := expression.args.get("for_each"): 1455 parts.append(f"FOR EACH {for_each}") 1456 1457 if when := expression.args.get("when"): 1458 parts.append(f"WHEN ({self.sql(when)})") 1459 1460 parts.append(self.sql(expression, "execute")) 1461 1462 return self.sep().join(parts)
1464 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1465 parts = [] 1466 1467 if old_alias := expression.args.get("old"): 1468 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1469 1470 if new_alias := expression.args.get("new"): 1471 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1472 1473 return f"REFERENCING {' '.join(parts)}"
1482 def clone_sql(self, expression: exp.Clone) -> str: 1483 this = self.sql(expression, "this") 1484 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1485 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1486 return f"{shallow}{keyword} {this}"
1488 def describe_sql(self, expression: exp.Describe) -> str: 1489 style = expression.args.get("style") 1490 style = f" {style}" if style else "" 1491 partition = self.sql(expression, "partition") 1492 partition = f" {partition}" if partition else "" 1493 format = self.sql(expression, "format") 1494 format = f" {format}" if format else "" 1495 as_json = " AS JSON" if expression.args.get("as_json") else "" 1496 1497 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1509 def with_sql(self, expression: exp.With) -> str: 1510 sql = self.expressions(expression, flat=True) 1511 recursive = ( 1512 "RECURSIVE " 1513 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1514 else "" 1515 ) 1516 search = self.sql(expression, "search") 1517 search = f" {search}" if search else "" 1518 1519 return f"WITH {recursive}{sql}{search}"
1521 def cte_sql(self, expression: exp.CTE) -> str: 1522 alias = expression.args.get("alias") 1523 if alias: 1524 alias.add_comments(expression.pop_comments()) 1525 1526 alias_sql = self.sql(expression, "alias") 1527 1528 materialized = expression.args.get("materialized") 1529 if materialized is False: 1530 materialized = "NOT MATERIALIZED " 1531 elif materialized: 1532 materialized = "MATERIALIZED " 1533 1534 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1535 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1536 1537 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1539 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1540 alias = self.sql(expression, "this") 1541 columns = self.expressions(expression, key="columns", flat=True) 1542 columns = f"({columns})" if columns else "" 1543 1544 if ( 1545 columns 1546 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1547 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1548 ): 1549 columns = "" 1550 self.unsupported("Named columns are not supported in table alias.") 1551 1552 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1553 alias = self._next_name() 1554 1555 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1563 def hexstring_sql( 1564 self, expression: exp.HexString, binary_function_repr: str | None = None 1565 ) -> str: 1566 this = self.sql(expression, "this") 1567 is_integer_type = expression.args.get("is_integer") 1568 1569 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1570 not self.dialect.HEX_START and not binary_function_repr 1571 ): 1572 # Integer representation will be returned if: 1573 # - The read dialect treats the hex value as integer literal but not the write 1574 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1575 return f"{int(this, 16)}" 1576 1577 if not is_integer_type: 1578 # Read dialect treats the hex value as BINARY/BLOB 1579 if binary_function_repr: 1580 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1581 return self.func(binary_function_repr, exp.Literal.string(this)) 1582 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1583 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1584 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1585 1586 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1588 def bytestring_sql(self, expression: exp.ByteString) -> str: 1589 this = self.sql(expression, "this") 1590 if self.dialect.BYTE_START: 1591 escaped_byte_string = self.escape_str( 1592 this, 1593 escape_backslash=False, 1594 delimiter=self.dialect.BYTE_END, 1595 escaped_delimiter=self._escaped_byte_quote_end, 1596 is_byte_string=True, 1597 ) 1598 is_bytes = expression.args.get("is_bytes", False) 1599 delimited_byte_string = ( 1600 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1601 ) 1602 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1603 return self.sql( 1604 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1605 ) 1606 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1607 return self.sql( 1608 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1609 ) 1610 1611 return delimited_byte_string 1612 1613 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1614 return self.sql(exp.Literal.string(this)) 1615 1616 self.unsupported(f"Byte strings are not supported for {self.dialect.__class__.__name__}") 1617 return ""
1619 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1620 this = self.sql(expression, "this") 1621 escape = expression.args.get("escape") 1622 1623 if self.dialect.UNICODE_START: 1624 escape_substitute = r"\\\1" 1625 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1626 else: 1627 escape_substitute = r"\\u\1" 1628 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1629 1630 if escape: 1631 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1632 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1633 else: 1634 escape_pattern = ESCAPED_UNICODE_RE 1635 escape_sql = "" 1636 1637 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1638 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1639 1640 return f"{left_quote}{this}{right_quote}{escape_sql}"
1642 def rawstring_sql(self, expression: exp.RawString) -> str: 1643 string = expression.this 1644 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1645 string = string.replace("\\", "\\\\") 1646 1647 string = self.escape_str(string, escape_backslash=False) 1648 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:
1656 def datatype_param_bound_limiter( 1657 self, 1658 expression: exp.DataType, 1659 type_value: exp.DType, 1660 defaults: tuple[int, ...], 1661 bounds: tuple[int | None, ...], 1662 ) -> exp.DataType: 1663 params = expression.expressions 1664 1665 if not params: 1666 if defaults: 1667 expression.set( 1668 "expressions", 1669 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1670 ) 1671 return expression 1672 1673 if not bounds: 1674 return expression 1675 1676 for i, param in enumerate(params): 1677 bound = bounds[i] if i < len(bounds) else None 1678 if bound is None: 1679 continue 1680 1681 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1682 if ( 1683 isinstance(param_value, exp.Literal) 1684 and param_value.is_number 1685 and int(param_value.to_py()) > bound 1686 ): 1687 self.unsupported( 1688 f"{type_value.value} parameter {param_value.name} exceeds " 1689 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1690 ) 1691 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1692 1693 return expression
1695 def datatype_sql(self, expression: exp.DataType) -> str: 1696 nested = "" 1697 values = "" 1698 1699 expr_nested = expression.args.get("nested") 1700 type_value = expression.this 1701 1702 if ( 1703 not expr_nested 1704 and isinstance(type_value, exp.DType) 1705 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1706 ): 1707 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1708 1709 interior = ( 1710 self.expressions( 1711 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1712 ) 1713 if expr_nested and self.pretty 1714 else self.expressions(expression, flat=True) 1715 ) 1716 1717 if type_value in self.UNSUPPORTED_TYPES: 1718 self.unsupported( 1719 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1720 ) 1721 1722 type_sql: t.Any = "" 1723 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1724 type_sql = self.sql(expression, "kind") 1725 elif type_value == exp.DType.CHARACTER_SET: 1726 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1727 else: 1728 type_sql = ( 1729 self.TYPE_MAPPING.get(type_value, type_value.value) 1730 if isinstance(type_value, exp.DType) 1731 else type_value 1732 ) 1733 1734 if interior: 1735 if expr_nested: 1736 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1737 if expression.args.get("values") is not None: 1738 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1739 values = self.expressions(expression, key="values", flat=True) 1740 values = f"{delimiters[0]}{values}{delimiters[1]}" 1741 elif type_value == exp.DType.INTERVAL: 1742 nested = f" {interior}" 1743 else: 1744 nested = f"({interior})" 1745 1746 type_sql = f"{type_sql}{nested}{values}" 1747 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1748 exp.DType.TIMETZ, 1749 exp.DType.TIMESTAMPTZ, 1750 ): 1751 type_sql = f"{type_sql} WITH TIME ZONE" 1752 1753 collate = self.sql(expression, "collate") 1754 if collate: 1755 type_sql = f"{type_sql} COLLATE {collate}" 1756 1757 return type_sql
1759 def directory_sql(self, expression: exp.Directory) -> str: 1760 local = "LOCAL " if expression.args.get("local") else "" 1761 row_format = self.sql(expression, "row_format") 1762 row_format = f" {row_format}" if row_format else "" 1763 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1765 def delete_sql(self, expression: exp.Delete) -> str: 1766 hint = self.sql(expression, "hint") 1767 this = self.sql(expression, "this") 1768 this = f" FROM {this}" if this else "" 1769 using = self.expressions(expression, key="using") 1770 using = f" USING {using}" if using else "" 1771 cluster = self.sql(expression, "cluster") 1772 cluster = f" {cluster}" if cluster else "" 1773 where = self.sql(expression, "where") 1774 returning = self.sql(expression, "returning") 1775 order = self.sql(expression, "order") 1776 limit = self.sql(expression, "limit") 1777 tables = self.expressions(expression, key="tables") 1778 tables = f" {tables}" if tables else "" 1779 if self.RETURNING_END: 1780 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1781 else: 1782 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1783 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1785 def drop_sql(self, expression: exp.Drop) -> str: 1786 this = self.sql(expression, "this") 1787 expressions = self.expressions(expression, flat=True) 1788 expressions = f" ({expressions})" if expressions else "" 1789 kind = expression.args["kind"] 1790 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1791 iceberg = ( 1792 " ICEBERG" 1793 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1794 else "" 1795 ) 1796 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1797 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1798 on_cluster = self.sql(expression, "cluster") 1799 on_cluster = f" {on_cluster}" if on_cluster else "" 1800 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1801 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1802 cascade = " CASCADE" if expression.args.get("cascade") else "" 1803 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1804 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1805 purge = " PURGE" if expression.args.get("purge") else "" 1806 sync = " SYNC" if expression.args.get("sync") else "" 1807 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1809 def set_operation(self, expression: exp.SetOperation) -> str: 1810 op_type = type(expression) 1811 op_name = op_type.key.upper() 1812 1813 distinct = expression.args.get("distinct") 1814 if ( 1815 distinct is False 1816 and op_type in (exp.Except, exp.Intersect) 1817 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1818 ): 1819 self.unsupported(f"{op_name} ALL is not supported") 1820 1821 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1822 1823 if distinct is None: 1824 distinct = default_distinct 1825 if distinct is None: 1826 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1827 1828 if distinct is default_distinct: 1829 distinct_or_all = "" 1830 else: 1831 distinct_or_all = " DISTINCT" if distinct else " ALL" 1832 1833 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1834 side_kind = f"{side_kind} " if side_kind else "" 1835 1836 by_name = " BY NAME" if expression.args.get("by_name") else "" 1837 on = self.expressions(expression, key="on", flat=True) 1838 on = f" ON ({on})" if on else "" 1839 1840 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1842 def set_operations(self, expression: exp.SetOperation) -> str: 1843 if not self.SET_OP_MODIFIERS: 1844 limit = expression.args.get("limit") 1845 order = expression.args.get("order") 1846 1847 if limit or order: 1848 select = self._move_ctes_to_top_level( 1849 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1850 ) 1851 1852 if limit: 1853 select = select.limit(limit.pop(), copy=False) 1854 if order: 1855 select = select.order_by(order.pop(), copy=False) 1856 return self.sql(select) 1857 1858 sqls: list[str] = [] 1859 stack: list[str | exp.Expr] = [expression] 1860 1861 while stack: 1862 node = stack.pop() 1863 1864 if isinstance(node, exp.SetOperation): 1865 stack.append(node.expression) 1866 stack.append( 1867 self.maybe_comment( 1868 self.set_operation(node), comments=node.comments, separated=True 1869 ) 1870 ) 1871 stack.append(node.this) 1872 else: 1873 sqls.append(self.sql(node)) 1874 1875 this = self.sep().join(sqls) 1876 this = self.query_modifiers(expression, this) 1877 return self.prepend_ctes(expression, this)
1879 def fetch_sql(self, expression: exp.Fetch) -> str: 1880 direction = expression.args.get("direction") 1881 direction = f" {direction}" if direction else "" 1882 count = self.sql(expression, "count") 1883 count = f" {count}" if count else "" 1884 limit_options = self.sql(expression, "limit_options") 1885 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1886 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1888 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1889 percent = " PERCENT" if expression.args.get("percent") else "" 1890 rows = " ROWS" if expression.args.get("rows") else "" 1891 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1892 if not with_ties and rows: 1893 with_ties = " ONLY" 1894 return f"{percent}{rows}{with_ties}"
1896 def filter_sql(self, expression: exp.Filter) -> str: 1897 if self.AGGREGATE_FILTER_SUPPORTED: 1898 this = self.sql(expression, "this") 1899 where = self.sql(expression, "expression").strip() 1900 return f"{this} FILTER({where})" 1901 1902 agg = expression.this 1903 agg_arg = agg.this 1904 cond = expression.expression.this 1905 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1906 return self.sql(agg)
1915 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1916 using = self.sql(expression, "using") 1917 using = f" USING {using}" if using else "" 1918 columns = self.expressions(expression, key="columns", flat=True) 1919 columns = f"({columns})" if columns else "" 1920 partition_by = self.expressions(expression, key="partition_by", flat=True) 1921 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1922 where = self.sql(expression, "where") 1923 include = self.expressions(expression, key="include", flat=True) 1924 if include: 1925 include = f" INCLUDE ({include})" 1926 with_storage = self.expressions(expression, key="with_storage", flat=True) 1927 with_storage = f" WITH ({with_storage})" if with_storage else "" 1928 tablespace = self.sql(expression, "tablespace") 1929 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1930 on = self.sql(expression, "on") 1931 on = f" ON {on}" if on else "" 1932 1933 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1935 def index_sql(self, expression: exp.Index) -> str: 1936 unique = "UNIQUE " if expression.args.get("unique") else "" 1937 primary = "PRIMARY " if expression.args.get("primary") else "" 1938 amp = "AMP " if expression.args.get("amp") else "" 1939 name = self.sql(expression, "this") 1940 name = f"{name} " if name else "" 1941 table = self.sql(expression, "table") 1942 table = f"{self.INDEX_ON} {table}" if table else "" 1943 1944 index = "INDEX " if not table else "" 1945 1946 params = self.sql(expression, "params") 1947 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1949 def identifier_sql(self, expression: exp.Identifier) -> str: 1950 text = expression.name 1951 lower = text.lower() 1952 quoted = expression.quoted 1953 text = lower if self.normalize and not quoted else text 1954 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1955 if ( 1956 quoted 1957 or self.dialect.can_quote(expression, self.identify) 1958 or lower in self.RESERVED_KEYWORDS 1959 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1960 ): 1961 text = ( 1962 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1963 ) 1964 return text
1979 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1980 input_format = self.sql(expression, "input_format") 1981 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1982 output_format = self.sql(expression, "output_format") 1983 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1984 return self.sep().join((input_format, output_format))
1994 def properties_sql(self, expression: exp.Properties) -> str: 1995 root_properties = [] 1996 with_properties = [] 1997 1998 for p in expression.expressions: 1999 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2000 if p_loc == exp.Properties.Location.POST_WITH: 2001 with_properties.append(p) 2002 elif p_loc == exp.Properties.Location.POST_SCHEMA: 2003 root_properties.append(p) 2004 2005 root_props_ast = exp.Properties(expressions=root_properties) 2006 root_props_ast.parent = expression.parent 2007 2008 with_props_ast = exp.Properties(expressions=with_properties) 2009 with_props_ast.parent = expression.parent 2010 2011 root_props = self.root_properties(root_props_ast) 2012 with_props = self.with_properties(with_props_ast) 2013 2014 if root_props and with_props and not self.pretty: 2015 with_props = " " + with_props 2016 2017 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2024 def properties( 2025 self, 2026 properties: exp.Properties, 2027 prefix: str = "", 2028 sep: str = ", ", 2029 suffix: str = "", 2030 wrapped: bool = True, 2031 ) -> str: 2032 if properties.expressions: 2033 expressions = self.expressions(properties, sep=sep, indent=False) 2034 if expressions: 2035 expressions = self.wrap(expressions) if wrapped else expressions 2036 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2037 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2042 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2043 properties_locs = defaultdict(list) 2044 for p in properties.expressions: 2045 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2046 if p_loc != exp.Properties.Location.UNSUPPORTED: 2047 properties_locs[p_loc].append(p) 2048 else: 2049 self.unsupported(f"Unsupported property {p.key}") 2050 2051 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2058 def property_sql(self, expression: exp.Property) -> str: 2059 property_cls = expression.__class__ 2060 if property_cls == exp.Property: 2061 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2062 2063 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2064 if not property_name: 2065 self.unsupported(f"Unsupported property {expression.key}") 2066 2067 return f"{property_name}={self.sql(expression, 'this')}"
2072 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2073 if self.SUPPORTS_CREATE_TABLE_LIKE: 2074 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2075 options = f" {options}" if options else "" 2076 2077 like = f"LIKE {self.sql(expression, 'this')}{options}" 2078 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2079 like = f"({like})" 2080 2081 return like 2082 2083 if expression.expressions: 2084 self.unsupported("Transpilation of LIKE property options is unsupported") 2085 2086 select = exp.select("*").from_(expression.this).limit(0) 2087 return f"AS {self.sql(select)}"
2094 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2095 no = "NO " if expression.args.get("no") else "" 2096 local = expression.args.get("local") 2097 local = f"{local} " if local else "" 2098 dual = "DUAL " if expression.args.get("dual") else "" 2099 before = "BEFORE " if expression.args.get("before") else "" 2100 after = "AFTER " if expression.args.get("after") else "" 2101 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:
2117 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2118 if expression.args.get("no"): 2119 return "NO MERGEBLOCKRATIO" 2120 if expression.args.get("default"): 2121 return "DEFAULT MERGEBLOCKRATIO" 2122 2123 percent = " PERCENT" if expression.args.get("percent") else "" 2124 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2131 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2132 default = expression.args.get("default") 2133 minimum = expression.args.get("minimum") 2134 maximum = expression.args.get("maximum") 2135 if default or minimum or maximum: 2136 if default: 2137 prop = "DEFAULT" 2138 elif minimum: 2139 prop = "MINIMUM" 2140 else: 2141 prop = "MAXIMUM" 2142 return f"{prop} DATABLOCKSIZE" 2143 units = expression.args.get("units") 2144 units = f" {units}" if units else "" 2145 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2147 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2148 autotemp = expression.args.get("autotemp") 2149 always = expression.args.get("always") 2150 default = expression.args.get("default") 2151 manual = expression.args.get("manual") 2152 never = expression.args.get("never") 2153 2154 if autotemp is not None: 2155 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2156 elif always: 2157 prop = "ALWAYS" 2158 elif default: 2159 prop = "DEFAULT" 2160 elif manual: 2161 prop = "MANUAL" 2162 elif never: 2163 prop = "NEVER" 2164 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2166 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2167 no = expression.args.get("no") 2168 no = " NO" if no else "" 2169 concurrent = expression.args.get("concurrent") 2170 concurrent = " CONCURRENT" if concurrent else "" 2171 target = self.sql(expression, "target") 2172 target = f" {target}" if target else "" 2173 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2175 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2176 if isinstance(expression.this, list): 2177 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2178 if expression.this: 2179 modulus = self.sql(expression, "this") 2180 remainder = self.sql(expression, "expression") 2181 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2182 2183 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2184 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2185 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2187 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2188 this = self.sql(expression, "this") 2189 2190 for_values_or_default = expression.expression 2191 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2192 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2193 else: 2194 for_values_or_default = " DEFAULT" 2195 2196 return f"PARTITION OF {this}{for_values_or_default}"
2198 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2199 kind = expression.args.get("kind") 2200 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2201 for_or_in = expression.args.get("for_or_in") 2202 for_or_in = f" {for_or_in}" if for_or_in else "" 2203 lock_type = expression.args.get("lock_type") 2204 override = " OVERRIDE" if expression.args.get("override") else "" 2205 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2207 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2208 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2209 statistics = expression.args.get("statistics") 2210 statistics_sql = "" 2211 if statistics is not None: 2212 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2213 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2215 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2216 this = self.sql(expression, "this") 2217 this = f"HISTORY_TABLE={this}" if this else "" 2218 data_consistency: str | None = self.sql(expression, "data_consistency") 2219 data_consistency = ( 2220 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2221 ) 2222 retention_period: str | None = self.sql(expression, "retention_period") 2223 retention_period = ( 2224 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2225 ) 2226 2227 if this: 2228 on_sql = self.func("ON", this, data_consistency, retention_period) 2229 else: 2230 on_sql = "ON" if expression.args.get("on") else "OFF" 2231 2232 sql = f"SYSTEM_VERSIONING={on_sql}" 2233 2234 return f"WITH({sql})" if expression.args.get("with_") else sql
2236 def insert_sql(self, expression: exp.Insert) -> str: 2237 hint = self.sql(expression, "hint") 2238 overwrite = expression.args.get("overwrite") 2239 2240 if isinstance(expression.this, exp.Directory): 2241 this = " OVERWRITE" if overwrite else " INTO" 2242 else: 2243 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2244 2245 stored = self.sql(expression, "stored") 2246 stored = f" {stored}" if stored else "" 2247 alternative = expression.args.get("alternative") 2248 alternative = f" OR {alternative}" if alternative else "" 2249 ignore = " IGNORE" if expression.args.get("ignore") else "" 2250 is_function = expression.args.get("is_function") 2251 if is_function: 2252 this = f"{this} FUNCTION" 2253 this = f"{this} {self.sql(expression, 'this')}" 2254 2255 exists = " IF EXISTS" if expression.args.get("exists") else "" 2256 where = self.sql(expression, "where") 2257 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2258 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2259 on_conflict = self.sql(expression, "conflict") 2260 on_conflict = f" {on_conflict}" if on_conflict else "" 2261 by_name = " BY NAME" if expression.args.get("by_name") else "" 2262 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2263 returning = self.sql(expression, "returning") 2264 2265 if self.RETURNING_END: 2266 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2267 else: 2268 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2269 2270 partition_by = self.sql(expression, "partition") 2271 partition_by = f" {partition_by}" if partition_by else "" 2272 settings = self.sql(expression, "settings") 2273 settings = f" {settings}" if settings else "" 2274 2275 source = self.sql(expression, "source") 2276 source = f"TABLE {source}" if source else "" 2277 2278 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2279 return self.prepend_ctes(expression, sql)
2297 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2298 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2299 2300 constraint = self.sql(expression, "constraint") 2301 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2302 2303 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2304 if conflict_keys: 2305 conflict_keys = f"({conflict_keys})" 2306 2307 index_predicate = self.sql(expression, "index_predicate") 2308 conflict_keys = f"{conflict_keys}{index_predicate} " 2309 2310 action = self.sql(expression, "action") 2311 2312 expressions = self.expressions(expression, flat=True) 2313 if expressions: 2314 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2315 expressions = f" {set_keyword}{expressions}" 2316 2317 where = self.sql(expression, "where") 2318 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2323 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2324 fields = self.sql(expression, "fields") 2325 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2326 escaped = self.sql(expression, "escaped") 2327 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2328 items = self.sql(expression, "collection_items") 2329 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2330 keys = self.sql(expression, "map_keys") 2331 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2332 lines = self.sql(expression, "lines") 2333 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2334 null = self.sql(expression, "null") 2335 null = f" NULL DEFINED AS {null}" if null else "" 2336 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2364 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2365 table = self.table_parts(expression) 2366 only = "ONLY " if expression.args.get("only") else "" 2367 partition = self.sql(expression, "partition") 2368 partition = f" {partition}" if partition else "" 2369 version = self.sql(expression, "version") 2370 version = f" {version}" if version else "" 2371 alias = self.sql(expression, "alias") 2372 alias = f"{sep}{alias}" if alias else "" 2373 2374 sample = self.sql(expression, "sample") 2375 post_alias = "" 2376 pre_alias = "" 2377 2378 if self.dialect.ALIAS_POST_TABLESAMPLE: 2379 pre_alias = sample 2380 else: 2381 post_alias = sample 2382 2383 if self.dialect.ALIAS_POST_VERSION: 2384 pre_alias = f"{pre_alias}{version}" 2385 else: 2386 post_alias = f"{post_alias}{version}" 2387 2388 hints = self.expressions(expression, key="hints", sep=" ") 2389 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2390 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2391 joins = self.indent( 2392 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2393 ) 2394 laterals = self.expressions(expression, key="laterals", sep="") 2395 2396 file_format = self.sql(expression, "format") 2397 if file_format: 2398 pattern = self.sql(expression, "pattern") 2399 pattern = f", PATTERN => {pattern}" if pattern else "" 2400 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2401 2402 ordinality = expression.args.get("ordinality") or "" 2403 if ordinality: 2404 ordinality = f" WITH ORDINALITY{alias}" 2405 alias = "" 2406 2407 when = self.sql(expression, "when") 2408 if when: 2409 table = f"{table} {when}" 2410 2411 changes = self.sql(expression, "changes") 2412 changes = f" {changes}" if changes else "" 2413 2414 rows_from = self.expressions(expression, key="rows_from") 2415 if rows_from: 2416 table = f"ROWS FROM {self.wrap(rows_from)}" 2417 2418 indexed = expression.args.get("indexed") 2419 if indexed is not None: 2420 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2421 else: 2422 indexed = "" 2423 2424 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2426 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2427 table = self.func("TABLE", expression.this) 2428 alias = self.sql(expression, "alias") 2429 alias = f" AS {alias}" if alias else "" 2430 sample = self.sql(expression, "sample") 2431 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2432 joins = self.indent( 2433 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2434 ) 2435 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2437 def tablesample_sql( 2438 self, 2439 expression: exp.TableSample, 2440 tablesample_keyword: str | None = None, 2441 ) -> str: 2442 method = self.sql(expression, "method") 2443 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2444 numerator = self.sql(expression, "bucket_numerator") 2445 denominator = self.sql(expression, "bucket_denominator") 2446 field = self.sql(expression, "bucket_field") 2447 field = f" ON {field}" if field else "" 2448 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2449 seed = self.sql(expression, "seed") 2450 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2451 2452 size = self.sql(expression, "size") 2453 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2454 size = f"{size} ROWS" 2455 2456 percent = self.sql(expression, "percent") 2457 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2458 percent = f"{percent} PERCENT" 2459 2460 expr = f"{bucket}{percent}{size}" 2461 if self.TABLESAMPLE_REQUIRES_PARENS: 2462 expr = f"({expr})" 2463 2464 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2541 def pivot_sql(self, expression: exp.Pivot) -> str: 2542 expressions = self.expressions(expression, flat=True) 2543 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2544 2545 group = self.sql(expression, "group") 2546 2547 if expression.this: 2548 this = self.sql(expression, "this") 2549 if not expressions: 2550 sql = f"UNPIVOT {this}" 2551 else: 2552 on = f"{self.seg('ON')} {expressions}" 2553 into = self.sql(expression, "into") 2554 into = f"{self.seg('INTO')} {into}" if into else "" 2555 using = self.expressions(expression, key="using", flat=True) 2556 using = f"{self.seg('USING')} {using}" if using else "" 2557 sql = f"{direction} {this}{on}{into}{using}{group}" 2558 return self.prepend_ctes(expression, sql) 2559 2560 if not expression.unpivot: 2561 # Wrap IN-list values with explicit aliases where the target dialect would differ 2562 new_field_exprs = self._pivot_in_value_aliases(expression) 2563 if new_field_exprs is not None: 2564 expression.fields[0].set("expressions", new_field_exprs) 2565 2566 alias = self.sql(expression, "alias") 2567 alias = f" AS {alias}" if alias else "" 2568 2569 fields = self.expressions( 2570 expression, 2571 "fields", 2572 sep=" ", 2573 dynamic=True, 2574 new_line=True, 2575 skip_first=True, 2576 skip_last=True, 2577 ) 2578 2579 include_nulls = expression.args.get("include_nulls") 2580 if include_nulls is not None: 2581 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2582 else: 2583 nulls = "" 2584 2585 default_on_null = self.sql(expression, "default_on_null") 2586 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2587 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2588 return self.prepend_ctes(expression, sql)
2631 def update_sql(self, expression: exp.Update) -> str: 2632 hint = self.sql(expression, "hint") 2633 this = self.sql(expression, "this") 2634 join_sql, from_sql = self._update_from_joins_sql(expression) 2635 set_sql = self.expressions(expression, flat=True) 2636 where_sql = self.sql(expression, "where") 2637 returning = self.sql(expression, "returning") 2638 order = self.sql(expression, "order") 2639 limit = self.sql(expression, "limit") 2640 if self.RETURNING_END: 2641 expression_sql = f"{from_sql}{where_sql}{returning}" 2642 else: 2643 expression_sql = f"{returning}{from_sql}{where_sql}" 2644 options = self.expressions(expression, key="options") 2645 options = f" OPTION({options})" if options else "" 2646 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2647 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2649 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2650 values_as_table = values_as_table and self.VALUES_AS_TABLE 2651 2652 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2653 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2654 args = self.expressions(expression) 2655 alias = self.sql(expression, "alias") 2656 values = f"VALUES{self.seg('')}{args}" 2657 values = ( 2658 f"({values})" 2659 if self.WRAP_DERIVED_VALUES 2660 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2661 else values 2662 ) 2663 values = self.query_modifiers(expression, values) 2664 return f"{values} AS {alias}" if alias else values 2665 2666 # Converts `VALUES...` expression into a series of select unions. 2667 alias_node = expression.args.get("alias") 2668 column_names = alias_node and alias_node.columns 2669 2670 selects: list[exp.Query] = [] 2671 2672 for i, tup in enumerate(expression.expressions): 2673 row = tup.expressions 2674 2675 if i == 0 and column_names: 2676 row = [ 2677 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2678 ] 2679 2680 selects.append(exp.Select(expressions=row)) 2681 2682 if self.pretty: 2683 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2684 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2685 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2686 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2687 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2688 2689 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2690 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2691 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2696 @unsupported_args("expressions") 2697 def into_sql(self, expression: exp.Into) -> str: 2698 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2699 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2700 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2713 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2714 this = self.sql(expression, "this") 2715 2716 columns = self.expressions(expression, flat=True) 2717 2718 from_sql = self.sql(expression, "from_index") 2719 from_sql = f" FROM {from_sql}" if from_sql else "" 2720 2721 properties = expression.args.get("properties") 2722 properties_sql = ( 2723 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2724 ) 2725 2726 return f"{this}({columns}){from_sql}{properties_sql}"
2735 def group_sql(self, expression: exp.Group) -> str: 2736 group_by_all = expression.args.get("all") 2737 if group_by_all is True: 2738 modifier = " ALL" 2739 elif group_by_all is False: 2740 modifier = " DISTINCT" 2741 else: 2742 modifier = "" 2743 2744 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2745 2746 grouping_sets = self.expressions(expression, key="grouping_sets") 2747 cube = self.expressions(expression, key="cube") 2748 rollup = self.expressions(expression, key="rollup") 2749 2750 groupings = csv( 2751 self.seg(grouping_sets) if grouping_sets else "", 2752 self.seg(cube) if cube else "", 2753 self.seg(rollup) if rollup else "", 2754 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2755 sep=self.GROUPINGS_SEP, 2756 ) 2757 2758 if ( 2759 expression.expressions 2760 and groupings 2761 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2762 ): 2763 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2764 2765 return f"{group_by}{groupings}"
2771 def connect_sql(self, expression: exp.Connect) -> str: 2772 start = self.sql(expression, "start") 2773 start = self.seg(f"START WITH {start}") if start else "" 2774 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2775 connect = self.sql(expression, "connect") 2776 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2777 return start + connect
2782 def join_sql(self, expression: exp.Join) -> str: 2783 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2784 side = None 2785 else: 2786 side = expression.side 2787 2788 op_sql = " ".join( 2789 op 2790 for op in ( 2791 expression.method, 2792 "GLOBAL" if expression.args.get("global_") else None, 2793 side, 2794 expression.kind, 2795 expression.hint if self.JOIN_HINTS else None, 2796 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2797 ) 2798 if op 2799 ) 2800 match_cond = self.sql(expression, "match_condition") 2801 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2802 on_sql = self.sql(expression, "on") 2803 using = expression.args.get("using") 2804 2805 if not on_sql and using: 2806 on_sql = csv(*(self.sql(column) for column in using)) 2807 2808 this = expression.this 2809 this_sql = self.sql(this) 2810 2811 exprs = self.expressions(expression) 2812 if exprs: 2813 this_sql = f"{this_sql},{self.seg(exprs)}" 2814 2815 if on_sql: 2816 on_sql = self.indent(on_sql, skip_first=True) 2817 space = self.seg(" " * self.pad) if self.pretty else " " 2818 if using: 2819 on_sql = f"{space}USING ({on_sql})" 2820 else: 2821 on_sql = f"{space}ON {on_sql}" 2822 elif not op_sql: 2823 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2824 return f" {this_sql}" 2825 2826 return f", {this_sql}" 2827 2828 if op_sql != "STRAIGHT_JOIN": 2829 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2830 2831 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2832 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:
2839 def lateral_op(self, expression: exp.Lateral) -> str: 2840 cross_apply = expression.args.get("cross_apply") 2841 2842 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2843 if cross_apply is True: 2844 op = "INNER JOIN " 2845 elif cross_apply is False: 2846 op = "LEFT JOIN " 2847 else: 2848 op = "" 2849 2850 return f"{op}LATERAL"
2852 def lateral_sql(self, expression: exp.Lateral) -> str: 2853 this = self.sql(expression, "this") 2854 2855 if expression.args.get("view"): 2856 alias = expression.args["alias"] 2857 columns = self.expressions(alias, key="columns", flat=True) 2858 table = f" {alias.name}" if alias.name else "" 2859 columns = f" AS {columns}" if columns else "" 2860 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2861 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2862 2863 alias = self.sql(expression, "alias") 2864 alias = f" AS {alias}" if alias else "" 2865 2866 ordinality = expression.args.get("ordinality") or "" 2867 if ordinality: 2868 ordinality = f" WITH ORDINALITY{alias}" 2869 alias = "" 2870 2871 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2873 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2874 this = self.sql(expression, "this") 2875 2876 args = [ 2877 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2878 for e in (expression.args.get(k) for k in ("offset", "expression")) 2879 if e 2880 ] 2881 2882 args_sql = ", ".join(self.sql(e) for e in args) 2883 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2884 expressions = self.expressions(expression, flat=True) 2885 limit_options = self.sql(expression, "limit_options") 2886 expressions = f" BY {expressions}" if expressions else "" 2887 2888 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2890 def offset_sql(self, expression: exp.Offset) -> str: 2891 this = self.sql(expression, "this") 2892 value = expression.expression 2893 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2894 expressions = self.expressions(expression, flat=True) 2895 expressions = f" BY {expressions}" if expressions else "" 2896 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2898 def setitem_sql(self, expression: exp.SetItem) -> str: 2899 kind = self.sql(expression, "kind") 2900 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2901 kind = "" 2902 else: 2903 kind = f"{kind} " if kind else "" 2904 this = self.sql(expression, "this") 2905 expressions = self.expressions(expression) 2906 collate = self.sql(expression, "collate") 2907 collate = f" COLLATE {collate}" if collate else "" 2908 global_ = "GLOBAL " if expression.args.get("global_") else "" 2909 return f"{global_}{kind}{this}{expressions}{collate}"
2916 def queryband_sql(self, expression: exp.QueryBand) -> str: 2917 this = self.sql(expression, "this") 2918 update = " UPDATE" if expression.args.get("update") else "" 2919 scope = self.sql(expression, "scope") 2920 scope = f" FOR {scope}" if scope else "" 2921 2922 return f"QUERY_BAND = {this}{update}{scope}"
2927 def lock_sql(self, expression: exp.Lock) -> str: 2928 if not self.LOCKING_READS_SUPPORTED: 2929 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2930 return "" 2931 2932 update = expression.args["update"] 2933 key = expression.args.get("key") 2934 if update: 2935 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2936 else: 2937 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2938 expressions = self.expressions(expression, flat=True) 2939 expressions = f" OF {expressions}" if expressions else "" 2940 wait = expression.args.get("wait") 2941 2942 if wait is not None: 2943 if isinstance(wait, exp.Literal): 2944 wait = f" WAIT {self.sql(wait)}" 2945 else: 2946 wait = " NOWAIT" if wait else " SKIP LOCKED" 2947 2948 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:
2956 def escape_str( 2957 self, 2958 text: str, 2959 escape_backslash: bool = True, 2960 delimiter: str | None = None, 2961 escaped_delimiter: str | None = None, 2962 is_byte_string: bool = False, 2963 ) -> str: 2964 if is_byte_string: 2965 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2966 else: 2967 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2968 2969 if supports_escape_sequences: 2970 text = "".join( 2971 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2972 for ch in text 2973 ) 2974 2975 delimiter = delimiter or self.dialect.QUOTE_END 2976 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2977 2978 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2980 def loaddata_sql(self, expression: exp.LoadData) -> str: 2981 is_overwrite = expression.args.get("overwrite") 2982 overwrite = " OVERWRITE" if is_overwrite else "" 2983 this = self.sql(expression, "this") 2984 2985 files = expression.args.get("files") 2986 if files: 2987 files_sql = self.expressions(files, flat=True) 2988 files_sql = f"FILES{self.wrap(files_sql)}" 2989 if is_overwrite: 2990 this = f" {this}" 2991 elif expression.args.get("temp"): 2992 this = f" INTO TEMP TABLE {this}" 2993 else: 2994 this = f" INTO TABLE {this}" 2995 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2996 2997 local = " LOCAL" if expression.args.get("local") else "" 2998 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2999 this = f" INTO TABLE {this}" 3000 partition = self.sql(expression, "partition") 3001 partition = f" {partition}" if partition else "" 3002 input_format = self.sql(expression, "input_format") 3003 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 3004 serde = self.sql(expression, "serde") 3005 serde = f" SERDE {serde}" if serde else "" 3006 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
3020 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 3021 this = self.sql(expression, "this") 3022 this = f"{this} " if this else this 3023 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 3024 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
3026 def withfill_sql(self, expression: exp.WithFill) -> str: 3027 from_sql = self.sql(expression, "from_") 3028 from_sql = f" FROM {from_sql}" if from_sql else "" 3029 to_sql = self.sql(expression, "to") 3030 to_sql = f" TO {to_sql}" if to_sql else "" 3031 step_sql = self.sql(expression, "step") 3032 step_sql = f" STEP {step_sql}" if step_sql else "" 3033 interpolated_values = [ 3034 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 3035 if isinstance(e, exp.Alias) 3036 else self.sql(e, "this") 3037 for e in expression.args.get("interpolate") or [] 3038 ] 3039 interpolate = ( 3040 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 3041 ) 3042 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
3094 def ordered_sql(self, expression: exp.Ordered) -> str: 3095 desc = expression.args.get("desc") 3096 asc = not desc 3097 3098 nulls_first = expression.args.get("nulls_first") 3099 nulls_last = not nulls_first 3100 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 3101 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 3102 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 3103 3104 this = self.sql(expression, "this") 3105 3106 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 3107 nulls_sort_change = "" 3108 if nulls_first and ( 3109 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 3110 ): 3111 nulls_sort_change = " NULLS FIRST" 3112 elif ( 3113 nulls_last 3114 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 3115 and not nulls_are_last 3116 ): 3117 nulls_sort_change = " NULLS LAST" 3118 3119 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 3120 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 3121 window = expression.find_ancestor(exp.Window, exp.Select) 3122 3123 if isinstance(window, exp.Window): 3124 window_this = window.this 3125 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 3126 window_this = window_this.this 3127 spec = window.args.get("spec") 3128 else: 3129 window_this = None 3130 spec = None 3131 3132 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 3133 # without a spec or with a ROWS spec, but not with RANGE 3134 if not ( 3135 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3136 and (not spec or spec.text("kind").upper() == "ROWS") 3137 ): 3138 if window_this and spec: 3139 self.unsupported( 3140 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3141 ) 3142 nulls_sort_change = "" 3143 elif self.NULL_ORDERING_SUPPORTED is False and ( 3144 (asc and nulls_sort_change == " NULLS LAST") 3145 or (desc and nulls_sort_change == " NULLS FIRST") 3146 ): 3147 # BigQuery does not allow these ordering/nulls combinations when used under 3148 # an aggregation func or under a window containing one 3149 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3150 3151 if isinstance(ancestor, exp.Window): 3152 ancestor = ancestor.this 3153 if isinstance(ancestor, exp.AggFunc): 3154 self.unsupported( 3155 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3156 ) 3157 nulls_sort_change = "" 3158 elif self.NULL_ORDERING_SUPPORTED is None: 3159 if expression.this.is_int: 3160 self.unsupported( 3161 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3162 ) 3163 elif not isinstance(expression.this, exp.Rand): 3164 resolved = self._resolve_ordered_for_null_ordering_simulation(expression) 3165 target = self.sql(resolved) if resolved is not None else this 3166 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3167 this = f"CASE WHEN {target} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {target}" 3168 nulls_sort_change = "" 3169 3170 with_fill = self.sql(expression, "with_fill") 3171 with_fill = f" {with_fill}" if with_fill else "" 3172 3173 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3183 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3184 partition = self.partition_by_sql(expression) 3185 order = self.sql(expression, "order") 3186 measures = self.expressions(expression, key="measures") 3187 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3188 rows = self.sql(expression, "rows") 3189 rows = self.seg(rows) if rows else "" 3190 after = self.sql(expression, "after") 3191 after = self.seg(after) if after else "" 3192 pattern = self.sql(expression, "pattern") 3193 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3194 definition_sqls = [ 3195 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3196 for definition in expression.args.get("define", []) 3197 ] 3198 definitions = self.expressions(sqls=definition_sqls) 3199 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3200 body = "".join( 3201 ( 3202 partition, 3203 order, 3204 measures, 3205 rows, 3206 after, 3207 pattern, 3208 define, 3209 ) 3210 ) 3211 alias = self.sql(expression, "alias") 3212 alias = f" {alias}" if alias else "" 3213 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3215 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3216 limit = expression.args.get("limit") 3217 3218 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3219 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3220 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3221 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3222 3223 return csv( 3224 *sqls, 3225 *[self.sql(join) for join in expression.args.get("joins") or []], 3226 self.sql(expression, "match"), 3227 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3228 self.sql(expression, "prewhere"), 3229 self.sql(expression, "where"), 3230 self.sql(expression, "connect"), 3231 self.sql(expression, "group"), 3232 self.sql(expression, "having"), 3233 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3234 self.sql(expression, "order"), 3235 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3236 *self.after_limit_modifiers(expression), 3237 self.options_modifier(expression), 3238 self.sql(expression, "for_"), 3239 sep="", 3240 )
3246 def forclause_sql(self, expression: exp.ForClause) -> str: 3247 kind = expression.args["kind"] 3248 if kind == "BROWSE": 3249 return f"{self.sep()}FOR BROWSE" 3250 # FOR XML/JSON always carry at least AUTO/PATH. An empty rendering means 3251 # the target dialect doesn't support QueryOption, so we drop the clause. 3252 options = self.expressions(expression, key="expressions") 3253 if not options: 3254 return "" 3255 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]:
3274 def select_sql(self, expression: exp.Select) -> str: 3275 into = expression.args.get("into") 3276 if not self.SUPPORTS_SELECT_INTO and into: 3277 into.pop() 3278 3279 hint = self.sql(expression, "hint") 3280 distinct = self.sql(expression, "distinct") 3281 distinct = f" {distinct}" if distinct else "" 3282 kind = self.sql(expression, "kind") 3283 3284 limit = expression.args.get("limit") 3285 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3286 top = self.limit_sql(limit, top=True) 3287 limit.pop() 3288 else: 3289 top = "" 3290 3291 expressions = self.expressions(expression) 3292 3293 if kind: 3294 if kind in self.SELECT_KINDS: 3295 kind = f" AS {kind}" 3296 else: 3297 if kind == "STRUCT": 3298 expressions = self.expressions( 3299 sqls=[ 3300 self.sql( 3301 exp.Struct( 3302 expressions=[ 3303 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3304 if isinstance(e, exp.Alias) 3305 else e 3306 for e in expression.expressions 3307 ] 3308 ) 3309 ) 3310 ] 3311 ) 3312 kind = "" 3313 3314 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3315 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3316 3317 exclude = expression.args.get("exclude") 3318 3319 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3320 exclude_sql = self.expressions(sqls=exclude, flat=True) 3321 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3322 3323 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3324 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3325 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3326 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3327 sql = self.query_modifiers( 3328 expression, 3329 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3330 self.sql(expression, "into", comment=False), 3331 self.sql(expression, "from_", comment=False), 3332 ) 3333 3334 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3335 if expression.args.get("with_"): 3336 sql = self.maybe_comment(sql, expression) 3337 expression.pop_comments() 3338 3339 sql = self.prepend_ctes(expression, sql) 3340 3341 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3342 expression.set("exclude", None) 3343 subquery = expression.subquery(copy=False) 3344 star = exp.Star(except_=exclude) 3345 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3346 3347 if not self.SUPPORTS_SELECT_INTO and into: 3348 if into.args.get("temporary"): 3349 table_kind = " TEMPORARY" 3350 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3351 table_kind = " UNLOGGED" 3352 else: 3353 table_kind = "" 3354 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3355 3356 return sql
3368 def star_sql(self, expression: exp.Star) -> str: 3369 except_ = self.expressions(expression, key="except_", flat=True) 3370 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3371 replace = self.expressions(expression, key="replace", flat=True) 3372 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3373 rename = self.expressions(expression, key="rename", flat=True) 3374 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3375 return f"*{except_}{replace}{rename}"
3391 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3392 alias = self.sql(expression, "alias") 3393 alias = f"{sep}{alias}" if alias else "" 3394 sample = self.sql(expression, "sample") 3395 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3396 alias = f"{sample}{alias}" 3397 3398 # Set to None so it's not generated again by self.query_modifiers() 3399 expression.set("sample", None) 3400 3401 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3402 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3403 return self.prepend_ctes(expression, sql)
3409 def unnest_sql(self, expression: exp.Unnest) -> str: 3410 args = self.expressions(expression, flat=True) 3411 3412 alias = expression.args.get("alias") 3413 offset = expression.args.get("offset") 3414 3415 if self.UNNEST_WITH_ORDINALITY: 3416 if alias and isinstance(offset, exp.Expr): 3417 alias.append("columns", offset) 3418 expression.set("offset", None) 3419 3420 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3421 columns = alias.columns 3422 alias = self.sql(columns[0]) if columns else "" 3423 else: 3424 alias = self.sql(alias) 3425 3426 alias = f" AS {alias}" if alias else alias 3427 if self.UNNEST_WITH_ORDINALITY: 3428 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3429 else: 3430 if isinstance(offset, exp.Expr): 3431 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3432 elif offset: 3433 suffix = f"{alias} WITH OFFSET" 3434 else: 3435 suffix = alias 3436 3437 return f"UNNEST({args}){suffix}"
3446 def window_sql(self, expression: exp.Window) -> str: 3447 this = self.sql(expression, "this") 3448 partition = self.partition_by_sql(expression) 3449 order = expression.args.get("order") 3450 order = self.order_sql(order, flat=True) if order else "" 3451 spec = self.sql(expression, "spec") 3452 alias = self.sql(expression, "alias") 3453 over = self.sql(expression, "over") or "OVER" 3454 3455 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3456 3457 first = expression.args.get("first") 3458 if first is None: 3459 first = "" 3460 else: 3461 first = "FIRST" if first else "LAST" 3462 3463 if not partition and not order and not spec and alias: 3464 return f"{this} {alias}" 3465 3466 args = self.format_args( 3467 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3468 ) 3469 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3475 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3476 kind = self.sql(expression, "kind") 3477 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3478 end = ( 3479 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3480 or "CURRENT ROW" 3481 ) 3482 3483 window_spec = f"{kind} BETWEEN {start} AND {end}" 3484 3485 exclude = self.sql(expression, "exclude") 3486 if exclude: 3487 if self.SUPPORTS_WINDOW_EXCLUDE: 3488 window_spec += f" EXCLUDE {exclude}" 3489 else: 3490 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3491 3492 return window_spec
3499 def between_sql(self, expression: exp.Between) -> str: 3500 this = self.sql(expression, "this") 3501 low = self.sql(expression, "low") 3502 high = self.sql(expression, "high") 3503 symmetric = expression.args.get("symmetric") 3504 3505 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3506 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3507 3508 flag = ( 3509 " SYMMETRIC" 3510 if symmetric 3511 else " ASYMMETRIC" 3512 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3513 else "" # silently drop ASYMMETRIC – semantics identical 3514 ) 3515 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]:
3517 def bracket_offset_expressions( 3518 self, expression: exp.Bracket, index_offset: int | None = None 3519 ) -> list[exp.Expr]: 3520 if expression.args.get("json_access"): 3521 return expression.expressions 3522 3523 return apply_index_offset( 3524 expression.this, 3525 expression.expressions, 3526 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3527 dialect=self.dialect, 3528 )
3541 def any_sql(self, expression: exp.Any) -> str: 3542 this = self.sql(expression, "this") 3543 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3544 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3545 this = self.wrap(this) 3546 return f"ANY{this}" 3547 return f"ANY {this}"
3552 def case_sql(self, expression: exp.Case) -> str: 3553 this = self.sql(expression, "this") 3554 statements = [f"CASE {this}" if this else "CASE"] 3555 3556 for e in expression.args["ifs"]: 3557 statements.append(f"WHEN {self.sql(e, 'this')}") 3558 statements.append(f"THEN {self.sql(e, 'true')}") 3559 3560 default = self.sql(expression, "default") 3561 3562 if default: 3563 statements.append(f"ELSE {default}") 3564 3565 statements.append("END") 3566 3567 if self.pretty and self.too_wide(statements): 3568 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3569 3570 return " ".join(statements)
3582 def extract_sql(self, expression: exp.Extract) -> str: 3583 import sqlglot.dialects.dialect 3584 3585 this = ( 3586 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3587 if self.NORMALIZE_EXTRACT_DATE_PARTS 3588 else expression.this 3589 ) 3590 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3591 expression_sql = self.sql(expression, "expression") 3592 3593 return f"EXTRACT({this_sql} FROM {expression_sql})"
3595 def trim_sql(self, expression: exp.Trim) -> str: 3596 trim_type = self.sql(expression, "position") 3597 3598 if trim_type == "LEADING": 3599 func_name = "LTRIM" 3600 elif trim_type == "TRAILING": 3601 func_name = "RTRIM" 3602 else: 3603 func_name = "TRIM" 3604 3605 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3607 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3608 args = expression.expressions 3609 if isinstance(expression, exp.ConcatWs): 3610 args = args[1:] # Skip the delimiter 3611 3612 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3613 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3614 3615 concat_coalesce = ( 3616 self.dialect.CONCAT_WS_COALESCE 3617 if isinstance(expression, exp.ConcatWs) 3618 else self.dialect.CONCAT_COALESCE 3619 ) 3620 3621 if not concat_coalesce and expression.args.get("coalesce"): 3622 3623 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3624 if not e.type: 3625 import sqlglot.optimizer.annotate_types 3626 3627 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3628 3629 if e.is_string or e.is_type(exp.DType.ARRAY): 3630 return e 3631 3632 return exp.func("coalesce", e, exp.Literal.string("")) 3633 3634 args = [_wrap_with_coalesce(e) for e in args] 3635 3636 return args
3638 def concat_sql(self, expression: exp.Concat) -> str: 3639 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3640 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3641 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3642 # instead of coalescing them to empty string. 3643 import sqlglot.dialects.dialect 3644 3645 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3646 3647 expressions = self.convert_concat_args(expression) 3648 3649 # Some dialects don't allow a single-argument CONCAT call 3650 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3651 return self.sql(expressions[0]) 3652 3653 return self.func("CONCAT", *expressions)
3655 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3656 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3657 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3658 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3659 all_args = expression.expressions 3660 expression.set("coalesce", True) 3661 return self.sql( 3662 exp.case() 3663 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3664 .else_(expression) 3665 ) 3666 3667 return self.func( 3668 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3669 )
3675 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3676 expressions = self.expressions(expression, flat=True) 3677 expressions = f" ({expressions})" if expressions else "" 3678 reference = self.sql(expression, "reference") 3679 reference = f" {reference}" if reference else "" 3680 delete = self.sql(expression, "delete") 3681 delete = f" ON DELETE {delete}" if delete else "" 3682 update = self.sql(expression, "update") 3683 update = f" ON UPDATE {update}" if update else "" 3684 options = self.expressions(expression, key="options", flat=True, sep=" ") 3685 options = f" {options}" if options else "" 3686 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3688 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3689 this = self.sql(expression, "this") 3690 this = f" {this}" if this else "" 3691 expressions = self.expressions(expression, flat=True) 3692 include = self.sql(expression, "include") 3693 options = self.expressions(expression, key="options", flat=True, sep=" ") 3694 options = f" {options}" if options else "" 3695 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3700 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3701 if self.MATCH_AGAINST_TABLE_PREFIX: 3702 expressions = [] 3703 for expr in expression.expressions: 3704 if isinstance(expr, exp.Table): 3705 expressions.append(f"TABLE {self.sql(expr)}") 3706 else: 3707 expressions.append(expr) 3708 else: 3709 expressions = expression.expressions 3710 3711 modifier = expression.args.get("modifier") 3712 modifier = f" {modifier}" if modifier else "" 3713 return ( 3714 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3715 )
3720 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3721 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3722 3723 if expression.args.get("escape"): 3724 path = self.escape_str(path) 3725 3726 if self.QUOTE_JSON_PATH: 3727 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3728 3729 return path
3731 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3732 if isinstance(expression, exp.JSONPathPart): 3733 transform = self.TRANSFORMS.get(expression.__class__) 3734 if not callable(transform): 3735 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3736 return "" 3737 3738 return transform(self, expression) 3739 3740 if isinstance(expression, int): 3741 return str(expression) 3742 3743 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3744 escaped = expression.replace("'", "\\'") 3745 escaped = f"\\'{expression}\\'" 3746 else: 3747 escaped = expression.replace('"', '\\"') 3748 escaped = f'"{escaped}"' 3749 3750 return escaped
3755 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3756 # Output the Teradata column FORMAT override. 3757 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3758 this = self.sql(expression, "this") 3759 fmt = self.sql(expression, "format") 3760 return f"{this} (FORMAT {fmt})"
3788 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3789 null_handling = expression.args.get("null_handling") 3790 null_handling = f" {null_handling}" if null_handling else "" 3791 return_type = self.sql(expression, "return_type") 3792 return_type = f" RETURNING {return_type}" if return_type else "" 3793 strict = " STRICT" if expression.args.get("strict") else "" 3794 return self.func( 3795 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3796 )
3798 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3799 this = self.sql(expression, "this") 3800 order = self.sql(expression, "order") 3801 null_handling = expression.args.get("null_handling") 3802 null_handling = f" {null_handling}" if null_handling else "" 3803 return_type = self.sql(expression, "return_type") 3804 return_type = f" RETURNING {return_type}" if return_type else "" 3805 strict = " STRICT" if expression.args.get("strict") else "" 3806 return self.func( 3807 "JSON_ARRAYAGG", 3808 this, 3809 suffix=f"{order}{null_handling}{return_type}{strict})", 3810 )
3812 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3813 path = self.sql(expression, "path") 3814 path = f" PATH {path}" if path else "" 3815 nested_schema = self.sql(expression, "nested_schema") 3816 3817 if nested_schema: 3818 return f"NESTED{path} {nested_schema}" 3819 3820 this = self.sql(expression, "this") 3821 kind = self.sql(expression, "kind") 3822 kind = f" {kind}" if kind else "" 3823 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3824 3825 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3826 return f"{this}{kind}{format_json}{path}{ordinality}"
3831 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3832 this = self.sql(expression, "this") 3833 path = self.sql(expression, "path") 3834 path = f", {path}" if path else "" 3835 error_handling = expression.args.get("error_handling") 3836 error_handling = f" {error_handling}" if error_handling else "" 3837 empty_handling = expression.args.get("empty_handling") 3838 empty_handling = f" {empty_handling}" if empty_handling else "" 3839 schema = self.sql(expression, "schema") 3840 return self.func( 3841 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3842 )
3844 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3845 this = self.sql(expression, "this") 3846 kind = self.sql(expression, "kind") 3847 path = self.sql(expression, "path") 3848 path = f" {path}" if path else "" 3849 as_json = " AS JSON" if expression.args.get("as_json") else "" 3850 return f"{this} {kind}{path}{as_json}"
3852 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3853 this = self.sql(expression, "this") 3854 path = self.sql(expression, "path") 3855 path = f", {path}" if path else "" 3856 expressions = self.expressions(expression) 3857 with_ = ( 3858 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3859 if expressions 3860 else "" 3861 ) 3862 return f"OPENJSON({this}{path}){with_}"
3864 def in_sql(self, expression: exp.In) -> str: 3865 query = expression.args.get("query") 3866 unnest = expression.args.get("unnest") 3867 field = expression.args.get("field") 3868 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3869 3870 if query: 3871 in_sql = self.sql(query) 3872 elif unnest: 3873 in_sql = self.in_unnest_op(unnest) 3874 elif field: 3875 in_sql = self.sql(field) 3876 else: 3877 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3878 3879 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3884 def interval_sql(self, expression: exp.Interval) -> str: 3885 unit_expression = expression.args.get("unit") 3886 unit = self.sql(unit_expression) if unit_expression else "" 3887 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3888 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3889 unit = f" {unit}" if unit else "" 3890 3891 if self.SINGLE_STRING_INTERVAL: 3892 this = expression.this.name if expression.this else "" 3893 if this: 3894 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3895 return f"INTERVAL '{this}'{unit}" 3896 return f"INTERVAL '{this}{unit}'" 3897 return f"INTERVAL{unit}" 3898 3899 this = self.sql(expression, "this") 3900 if this: 3901 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3902 this = f" {this}" if unwrapped else f" ({this})" 3903 3904 return f"INTERVAL{this}{unit}"
3909 def reference_sql(self, expression: exp.Reference) -> str: 3910 this = self.sql(expression, "this") 3911 expressions = self.expressions(expression, flat=True) 3912 expressions = f"({expressions})" if expressions else "" 3913 options = self.expressions(expression, key="options", flat=True, sep=" ") 3914 options = f" {options}" if options else "" 3915 return f"REFERENCES {this}{expressions}{options}"
3917 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3918 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3919 parent = expression.parent 3920 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3921 3922 return self.func( 3923 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3924 )
3944 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3945 alias = expression.args["alias"] 3946 3947 parent = expression.parent 3948 pivot = parent and parent.parent 3949 3950 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3951 identifier_alias = isinstance(alias, exp.Identifier) 3952 literal_alias = isinstance(alias, exp.Literal) 3953 3954 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3955 alias.replace(exp.Literal.string(alias.output_name)) 3956 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3957 alias.replace(exp.to_identifier(alias.output_name)) 3958 3959 return self.alias_sql(expression)
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:
3991 def connector_sql( 3992 self, 3993 expression: exp.Connector, 3994 op: str, 3995 stack: list[str | exp.Expr] | None = None, 3996 ) -> str: 3997 if stack is not None: 3998 if expression.expressions: 3999 stack.append(self.expressions(expression, sep=f" {op} ")) 4000 else: 4001 stack.append(expression.right) 4002 if expression.comments and self.comments: 4003 op = self.maybe_comment(op, comments=expression.comments) 4004 stack.extend((op, expression.left)) 4005 return op 4006 4007 stack = [expression] 4008 sqls: list[str] = [] 4009 ops = set() 4010 4011 while stack: 4012 node = stack.pop() 4013 if isinstance(node, exp.Connector): 4014 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 4015 else: 4016 sql = self.sql(node) 4017 if sqls and sqls[-1] in ops: 4018 sqls[-1] += f" {sql}" 4019 else: 4020 sqls.append(sql) 4021 4022 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 4023 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
4043 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 4044 format_sql = self.sql(expression, "format") 4045 format_sql = f" FORMAT {format_sql}" if format_sql else "" 4046 to_sql = self.sql(expression, "to") 4047 to_sql = f" {to_sql}" if to_sql else "" 4048 action = self.sql(expression, "action") 4049 action = f" {action}" if action else "" 4050 default = self.sql(expression, "default") 4051 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 4052 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
4070 def comment_sql(self, expression: exp.Comment) -> str: 4071 this = self.sql(expression, "this") 4072 kind = expression.args["kind"] 4073 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 4074 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 4075 expression_sql = self.sql(expression, "expression") 4076 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
4078 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 4079 this = self.sql(expression, "this") 4080 delete = " DELETE" if expression.args.get("delete") else "" 4081 recompress = self.sql(expression, "recompress") 4082 recompress = f" RECOMPRESS {recompress}" if recompress else "" 4083 to_disk = self.sql(expression, "to_disk") 4084 to_disk = f" TO DISK {to_disk}" if to_disk else "" 4085 to_volume = self.sql(expression, "to_volume") 4086 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 4087 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
4089 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 4090 where = self.sql(expression, "where") 4091 group = self.sql(expression, "group") 4092 aggregates = self.expressions(expression, key="aggregates") 4093 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 4094 4095 if not (where or group or aggregates) and len(expression.expressions) == 1: 4096 return f"TTL {self.expressions(expression, flat=True)}" 4097 4098 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
4117 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 4118 this = self.sql(expression, "this") 4119 4120 dtype = self.sql(expression, "dtype") 4121 if dtype: 4122 collate = self.sql(expression, "collate") 4123 collate = f" COLLATE {collate}" if collate else "" 4124 using = self.sql(expression, "using") 4125 using = f" USING {using}" if using else "" 4126 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 4127 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 4128 4129 default = self.sql(expression, "default") 4130 if default: 4131 return f"ALTER COLUMN {this} SET DEFAULT {default}" 4132 4133 comment = self.sql(expression, "comment") 4134 if comment: 4135 return f"ALTER COLUMN {this} COMMENT {comment}" 4136 4137 visible = expression.args.get("visible") 4138 if visible: 4139 return f"ALTER COLUMN {this} SET {visible}" 4140 4141 allow_null = expression.args.get("allow_null") 4142 drop = expression.args.get("drop") 4143 4144 if not drop and not allow_null: 4145 self.unsupported("Unsupported ALTER COLUMN syntax") 4146 4147 if allow_null is not None: 4148 keyword = "DROP" if drop else "SET" 4149 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4150 4151 return f"ALTER COLUMN {this} DROP DEFAULT"
4153 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4154 this = self.sql(expression, "this") 4155 rename_from = self.sql(expression, "rename_from") 4156 if rename_from: 4157 if not self.SUPPORTS_CHANGE_COLUMN: 4158 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4159 return f"CHANGE COLUMN {rename_from} {this}" 4160 if not self.SUPPORTS_MODIFY_COLUMN: 4161 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4162 return f"MODIFY COLUMN {this}"
4178 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4179 compound = " COMPOUND" if expression.args.get("compound") else "" 4180 this = self.sql(expression, "this") 4181 expressions = self.expressions(expression, flat=True) 4182 expressions = f"({expressions})" if expressions else "" 4183 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4185 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4186 if not self.RENAME_TABLE_WITH_DB: 4187 # Remove db from tables 4188 expression = expression.transform( 4189 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4190 ).assert_is(exp.AlterRename) 4191 this = self.sql(expression, "this") 4192 to_kw = " TO" if include_to else "" 4193 return f"RENAME{to_kw} {this}"
4208 def alter_sql(self, expression: exp.Alter) -> str: 4209 actions = expression.args["actions"] 4210 4211 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4212 actions[0], exp.ColumnDef 4213 ): 4214 actions_sql = self.expressions(expression, key="actions", flat=True) 4215 actions_sql = f"ADD {actions_sql}" 4216 else: 4217 actions_list = [] 4218 for action in actions: 4219 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4220 action_sql = self.add_column_sql(action) 4221 else: 4222 action_sql = self.sql(action) 4223 if isinstance(action, exp.Query): 4224 action_sql = f"AS {action_sql}" 4225 4226 actions_list.append(action_sql) 4227 4228 actions_sql = self.format_args(*actions_list).lstrip("\n") 4229 4230 iceberg = ( 4231 "ICEBERG " 4232 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4233 else "" 4234 ) 4235 exists = " IF EXISTS" if expression.args.get("exists") else "" 4236 on_cluster = self.sql(expression, "cluster") 4237 on_cluster = f" {on_cluster}" if on_cluster else "" 4238 only = " ONLY" if expression.args.get("only") else "" 4239 options = self.expressions(expression, key="options") 4240 options = f", {options}" if options else "" 4241 kind = self.sql(expression, "kind") 4242 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4243 check = " WITH CHECK" if expression.args.get("check") else "" 4244 cascade = ( 4245 " CASCADE" 4246 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4247 else "" 4248 ) 4249 this = self.sql(expression, "this") 4250 this = f" {this}" if this else "" 4251 4252 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4259 def add_column_sql(self, expression: exp.Expr) -> str: 4260 sql = self.sql(expression) 4261 if isinstance(expression, exp.Schema): 4262 column_text = " COLUMNS" 4263 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4264 column_text = " COLUMN" 4265 else: 4266 column_text = "" 4267 4268 return f"ADD{column_text} {sql}"
4281 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4282 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4283 location = self.sql(expression, "location") 4284 location = f" {location}" if location else "" 4285 return f"ADD {exists}{self.sql(expression.this)}{location}"
4287 def distinct_sql(self, expression: exp.Distinct) -> str: 4288 this = self.expressions(expression, flat=True) 4289 4290 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4291 case = exp.case() 4292 for arg in expression.expressions: 4293 case = case.when(arg.is_(exp.null()), exp.null()) 4294 this = self.sql(case.else_(f"({this})")) 4295 4296 this = f" {this}" if this else "" 4297 4298 on = self.sql(expression, "on") 4299 on = f" ON {on}" if on else "" 4300 return f"DISTINCT{this}{on}"
4327 def div_sql(self, expression: exp.Div) -> str: 4328 l, r = expression.left, expression.right 4329 4330 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4331 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4332 4333 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4334 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4335 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4336 4337 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4338 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4339 return self.sql( 4340 exp.cast( 4341 l / r, 4342 to=exp.DType.BIGINT, 4343 ) 4344 ) 4345 4346 return self.binary(expression, "/")
4371 def escape_sql(self, expression: exp.Escape) -> str: 4372 this = expression.this 4373 if ( 4374 isinstance(this, (exp.Like, exp.ILike)) 4375 and isinstance(this.expression, (exp.All, exp.Any)) 4376 and not self.SUPPORTS_LIKE_QUANTIFIERS 4377 ): 4378 return self._like_sql(this, escape=expression) 4379 return self.binary(expression, "ESCAPE")
4496 def log_sql(self, expression: exp.Log) -> str: 4497 this = expression.this 4498 expr = expression.expression 4499 4500 if self.dialect.LOG_BASE_FIRST is False: 4501 this, expr = expr, this 4502 elif self.dialect.LOG_BASE_FIRST is None and expr: 4503 if this.name in ("2", "10"): 4504 return self.func(f"LOG{this.name}", expr) 4505 4506 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4507 4508 return self.func("LOG", this, expr)
4517 def binary(self, expression: exp.Binary, op: str) -> str: 4518 sqls: list[str] = [] 4519 stack: list[None | str | exp.Expr] = [expression] 4520 binary_type = type(expression) 4521 4522 while stack: 4523 node = stack.pop() 4524 4525 if type(node) is binary_type: 4526 op_func = node.args.get("operator") 4527 if op_func: 4528 op = f"OPERATOR({self.sql(op_func)})" 4529 4530 stack.append(node.args.get("expression")) 4531 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4532 stack.append(node.args.get("this")) 4533 else: 4534 sqls.append(self.sql(node)) 4535 4536 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4545 def function_fallback_sql(self, expression: exp.Func) -> str: 4546 args = [] 4547 4548 for key in expression.arg_types: 4549 arg_value = expression.args.get(key) 4550 4551 if isinstance(arg_value, list): 4552 for value in arg_value: 4553 args.append(value) 4554 elif arg_value is not None: 4555 args.append(arg_value) 4556 4557 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4558 name = expression.meta_get("name") or expression.sql_name() 4559 else: 4560 name = expression.sql_name() 4561 4562 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:
4575 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4576 arg_sqls = tuple( 4577 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4578 ) 4579 if self.pretty and self.too_wide(arg_sqls): 4580 return self.indent( 4581 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4582 ) 4583 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:
4588 def format_time( 4589 self, 4590 expression: exp.Expr, 4591 inverse_time_mapping: dict[str, str] | None = None, 4592 inverse_time_trie: dict | None = None, 4593 ) -> str | None: 4594 return format_time( 4595 self.sql(expression, "format"), 4596 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4597 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4598 )
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:
4600 def expressions( 4601 self, 4602 expression: exp.Expr | None = None, 4603 key: str | None = None, 4604 sqls: t.Collection[str | exp.Expr] | None = None, 4605 flat: bool = False, 4606 indent: bool = True, 4607 skip_first: bool = False, 4608 skip_last: bool = False, 4609 sep: str = ", ", 4610 prefix: str = "", 4611 dynamic: bool = False, 4612 new_line: bool = False, 4613 ) -> str: 4614 expressions = expression.args.get(key or "expressions") if expression else sqls 4615 4616 if not expressions: 4617 return "" 4618 4619 if flat: 4620 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4621 4622 num_sqls = len(expressions) 4623 result_sqls = [] 4624 4625 for i, e in enumerate(expressions): 4626 sql = self.sql(e, comment=False) 4627 if not sql: 4628 continue 4629 4630 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4631 4632 if self.pretty: 4633 if self.leading_comma: 4634 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4635 else: 4636 result_sqls.append( 4637 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4638 ) 4639 else: 4640 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4641 4642 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4643 if new_line: 4644 result_sqls.insert(0, "") 4645 result_sqls.append("") 4646 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4647 else: 4648 result_sql = "".join(result_sqls) 4649 4650 return ( 4651 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4652 if indent 4653 else result_sql 4654 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4656 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4657 flat = flat or isinstance(expression.parent, exp.Properties) 4658 expressions_sql = self.expressions(expression, flat=flat) 4659 if flat: 4660 return f"{op} {expressions_sql}" 4661 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4663 def naked_property(self, expression: exp.Property) -> str: 4664 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4665 if not property_name: 4666 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4667 return f"{property_name} {self.sql(expression, 'this')}"
4675 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4676 this = self.sql(expression, "this") 4677 expressions = self.no_identify(self.expressions, expression) 4678 expressions = ( 4679 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4680 ) 4681 return f"{this}{expressions}" if expressions.strip() != "" else this
4700 def when_sql(self, expression: exp.When) -> str: 4701 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4702 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4703 condition = self.sql(expression, "condition") 4704 condition = f" AND {condition}" if condition else "" 4705 4706 then_expression = expression.args.get("then") 4707 if isinstance(then_expression, exp.Insert): 4708 this = self.sql(then_expression, "this") 4709 this = f"INSERT {this}" if this else "INSERT" 4710 then = self.sql(then_expression, "expression") 4711 then = f"{this} VALUES {then}" if then else this 4712 elif isinstance(then_expression, exp.Update): 4713 if isinstance(then_expression.args.get("expressions"), exp.Star): 4714 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4715 else: 4716 expressions_sql = self.expressions(then_expression) 4717 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4718 else: 4719 then = self.sql(then_expression) 4720 4721 if isinstance(then_expression, (exp.Insert, exp.Update)): 4722 where = self.sql(then_expression, "where") 4723 if where and not self.SUPPORTS_MERGE_WHERE: 4724 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4725 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4726 where = "" 4727 then = f"{then}{where}" 4728 return f"WHEN {matched}{source}{condition} THEN {then}"
4733 def merge_sql(self, expression: exp.Merge) -> str: 4734 table = expression.this 4735 table_alias = "" 4736 4737 hints = table.args.get("hints") 4738 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4739 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4740 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4741 4742 this = self.sql(table) 4743 using = f"USING {self.sql(expression, 'using')}" 4744 whens = self.sql(expression, "whens") 4745 4746 on = self.sql(expression, "on") 4747 on = f"ON {on}" if on else "" 4748 4749 if not on: 4750 on = self.expressions(expression, key="using_cond") 4751 on = f"USING ({on})" if on else "" 4752 4753 returning = self.sql(expression, "returning") 4754 if returning: 4755 whens = f"{whens}{returning}" 4756 4757 sep = self.sep() 4758 4759 return self.prepend_ctes( 4760 expression, 4761 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4762 )
@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:
4768 @unsupported_args("default") 4769 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4770 if not self.SUPPORTS_TO_NUMBER: 4771 self.unsupported("Unsupported TO_NUMBER function") 4772 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4773 4774 fmt = expression.args.get("format") 4775 if not fmt: 4776 self.unsupported("Conversion format is required for TO_NUMBER") 4777 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4778 4779 return self.func("TO_NUMBER", expression.this, fmt)
4781 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4782 this = self.sql(expression, "this") 4783 kind = self.sql(expression, "kind") 4784 settings_sql = self.expressions(expression, key="settings", sep=" ") 4785 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4786 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:
4807 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4808 expressions = self.expressions(expression, flat=True) 4809 expressions = f" {self.wrap(expressions)}" if expressions else "" 4810 buckets = self.sql(expression, "buckets") 4811 kind = self.sql(expression, "kind") 4812 buckets = f" BUCKETS {buckets}" if buckets else "" 4813 order = self.sql(expression, "order") 4814 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
4819 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4820 expressions = self.expressions(expression, key="expressions", flat=True) 4821 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4822 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4823 buckets = self.sql(expression, "buckets") 4824 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4826 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4827 this = self.sql(expression, "this") 4828 having = self.sql(expression, "having") 4829 4830 if having: 4831 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4832 4833 return self.func("ANY_VALUE", this)
4835 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4836 transform = self.func("TRANSFORM", *expression.expressions) 4837 row_format_before = self.sql(expression, "row_format_before") 4838 row_format_before = f" {row_format_before}" if row_format_before else "" 4839 record_writer = self.sql(expression, "record_writer") 4840 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4841 using = f" USING {self.sql(expression, 'command_script')}" 4842 schema = self.sql(expression, "schema") 4843 schema = f" AS {schema}" if schema else "" 4844 row_format_after = self.sql(expression, "row_format_after") 4845 row_format_after = f" {row_format_after}" if row_format_after else "" 4846 record_reader = self.sql(expression, "record_reader") 4847 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4848 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:
4850 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4851 key_block_size = self.sql(expression, "key_block_size") 4852 if key_block_size: 4853 return f"KEY_BLOCK_SIZE = {key_block_size}" 4854 4855 using = self.sql(expression, "using") 4856 if using: 4857 return f"USING {using}" 4858 4859 parser = self.sql(expression, "parser") 4860 if parser: 4861 return f"WITH PARSER {parser}" 4862 4863 comment = self.sql(expression, "comment") 4864 if comment: 4865 return f"COMMENT {comment}" 4866 4867 visible = expression.args.get("visible") 4868 if visible is not None: 4869 return "VISIBLE" if visible else "INVISIBLE" 4870 4871 engine_attr = self.sql(expression, "engine_attr") 4872 if engine_attr: 4873 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4874 4875 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4876 if secondary_engine_attr: 4877 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4878 4879 self.unsupported("Unsupported index constraint option.") 4880 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
4886 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4887 kind = self.sql(expression, "kind") 4888 kind = f"{kind} INDEX" if kind else "INDEX" 4889 this = self.sql(expression, "this") 4890 this = f" {this}" if this else "" 4891 index_type = self.sql(expression, "index_type") 4892 index_type = f" USING {index_type}" if index_type else "" 4893 expressions = self.expressions(expression, flat=True) 4894 expressions = f" ({expressions})" if expressions else "" 4895 options = self.expressions(expression, key="options", sep=" ") 4896 options = f" {options}" if options else "" 4897 return f"{kind}{this}{index_type}{expressions}{options}"
4899 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4900 if self.NVL2_SUPPORTED: 4901 return self.function_fallback_sql(expression) 4902 4903 case = exp.Case().when( 4904 expression.this.is_(exp.null()).not_(copy=False), 4905 expression.args["true"], 4906 copy=False, 4907 ) 4908 else_cond = expression.args.get("false") 4909 if else_cond: 4910 case.else_(else_cond, copy=False) 4911 4912 return self.sql(case)
4914 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4915 this = self.sql(expression, "this") 4916 expr = self.sql(expression, "expression") 4917 position = self.sql(expression, "position") 4918 position = f", {position}" if position else "" 4919 iterator = self.sql(expression, "iterator") 4920 condition = self.sql(expression, "condition") 4921 condition = f" IF {condition}" if condition else "" 4922 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
4972 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4973 this_sql = self.sql(expression, "this") 4974 if isinstance(expression.this, exp.Table): 4975 this_sql = f"TABLE {this_sql}" 4976 4977 return self.func( 4978 "FORECAST", 4979 this_sql, 4980 expression.args.get("data_col"), 4981 expression.args.get("timestamp_col"), 4982 expression.args.get("model"), 4983 expression.args.get("id_cols"), 4984 expression.args.get("horizon"), 4985 expression.args.get("forecast_end_timestamp"), 4986 expression.args.get("confidence_level"), 4987 expression.args.get("output_historical_time_series"), 4988 expression.args.get("context_window"), 4989 )
4991 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4992 this_sql = self.sql(expression, "this") 4993 if isinstance(expression.this, exp.Table): 4994 this_sql = f"TABLE {this_sql}" 4995 4996 return self.func( 4997 "FEATURES_AT_TIME", 4998 this_sql, 4999 expression.args.get("time"), 5000 expression.args.get("num_rows"), 5001 expression.args.get("ignore_feature_nulls"), 5002 )
5004 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 5005 this_sql = self.sql(expression, "this") 5006 if isinstance(expression.this, exp.Table): 5007 this_sql = f"TABLE {this_sql}" 5008 5009 query_table = self.sql(expression, "query_table") 5010 if isinstance(expression.args["query_table"], exp.Table): 5011 query_table = f"TABLE {query_table}" 5012 5013 return self.func( 5014 "VECTOR_SEARCH", 5015 this_sql, 5016 expression.args.get("column_to_search"), 5017 query_table, 5018 expression.args.get("query_column_to_search"), 5019 expression.args.get("top_k"), 5020 expression.args.get("distance_type"), 5021 expression.args.get("options"), 5022 )
5034 def toarray_sql(self, expression: exp.ToArray) -> str: 5035 arg = expression.this 5036 if not arg.type: 5037 import sqlglot.optimizer.annotate_types 5038 5039 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 5040 5041 if arg.is_type(exp.DType.ARRAY): 5042 return self.sql(arg) 5043 5044 cond_for_null = arg.is_(exp.null()) 5045 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
5047 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 5048 this = expression.this 5049 time_format = self.format_time(expression) 5050 5051 if time_format: 5052 return self.sql( 5053 exp.cast( 5054 exp.StrToTime(this=this, format=expression.args["format"]), 5055 exp.DType.TIME, 5056 ) 5057 ) 5058 5059 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 5060 return self.sql(this) 5061 5062 return self.sql(exp.cast(this, exp.DType.TIME))
5064 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 5065 this = expression.this 5066 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 5067 return self.sql(this) 5068 5069 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
5071 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 5072 this = expression.this 5073 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 5074 return self.sql(this) 5075 5076 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
5078 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 5079 this = expression.this 5080 time_format = self.format_time(expression) 5081 safe = expression.args.get("safe") 5082 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 5083 return self.sql( 5084 exp.cast( 5085 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 5086 exp.DType.DATE, 5087 ) 5088 ) 5089 5090 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 5091 return self.sql(this) 5092 5093 if safe: 5094 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 5095 5096 return self.sql(exp.cast(this, exp.DType.DATE))
5108 def lastday_sql(self, expression: exp.LastDay) -> str: 5109 if self.LAST_DAY_SUPPORTS_DATE_PART: 5110 return self.function_fallback_sql(expression) 5111 5112 unit = expression.text("unit") 5113 if unit and unit != "MONTH": 5114 self.unsupported("Date parts are not supported in LAST_DAY.") 5115 5116 return self.func("LAST_DAY", expression.this)
5128 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 5129 if self.CAN_IMPLEMENT_ARRAY_ANY: 5130 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 5131 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 5132 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 5133 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 5134 5135 import sqlglot.dialects.dialect 5136 5137 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 5138 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 5139 self.unsupported("ARRAY_ANY is unsupported") 5140 5141 return self.function_fallback_sql(expression)
5143 def struct_sql(self, expression: exp.Struct) -> str: 5144 expression.set( 5145 "expressions", 5146 [ 5147 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 5148 if isinstance(e, exp.PropertyEQ) 5149 else e 5150 for e in expression.expressions 5151 ], 5152 ) 5153 5154 return self.function_fallback_sql(expression)
5162 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5163 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5164 tables = f" {self.expressions(expression)}" 5165 5166 exists = " IF EXISTS" if expression.args.get("exists") else "" 5167 5168 on_cluster = self.sql(expression, "cluster") 5169 on_cluster = f" {on_cluster}" if on_cluster else "" 5170 5171 identity = self.sql(expression, "identity") 5172 identity = f" {identity} IDENTITY" if identity else "" 5173 5174 option = self.sql(expression, "option") 5175 option = f" {option}" if option else "" 5176 5177 partition = self.sql(expression, "partition") 5178 partition = f" {partition}" if partition else "" 5179 5180 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5184 def convert_sql(self, expression: exp.Convert) -> str: 5185 to = expression.this 5186 value = expression.expression 5187 style = expression.args.get("style") 5188 safe = expression.args.get("safe") 5189 strict = expression.args.get("strict") 5190 5191 if not to or not value: 5192 return "" 5193 5194 # Retrieve length of datatype and override to default if not specified 5195 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5196 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5197 5198 transformed: exp.Expr | None = None 5199 cast = exp.Cast if strict else exp.TryCast 5200 5201 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5202 if isinstance(style, exp.Literal) and style.is_int: 5203 import sqlglot.dialects.tsql 5204 5205 style_value = style.name 5206 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5207 if not converted_style: 5208 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5209 5210 fmt = exp.Literal.string(converted_style) 5211 5212 if to.this == exp.DType.DATE: 5213 transformed = exp.StrToDate(this=value, format=fmt) 5214 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5215 transformed = exp.StrToTime(this=value, format=fmt) 5216 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5217 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5218 elif to.this == exp.DType.TEXT: 5219 transformed = exp.TimeToStr(this=value, format=fmt) 5220 5221 if not transformed: 5222 transformed = cast(this=value, to=to, safe=safe) 5223 5224 return self.sql(transformed)
5295 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5296 option = self.sql(expression, "this") 5297 5298 if expression.expressions: 5299 upper = option.upper() 5300 5301 # Snowflake FILE_FORMAT options are separated by whitespace 5302 sep = " " if upper == "FILE_FORMAT" else ", " 5303 5304 # Databricks copy/format options do not set their list of values with EQ 5305 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5306 values = self.expressions(expression, flat=True, sep=sep) 5307 return f"{option}{op}({values})" 5308 5309 value = self.sql(expression, "expression") 5310 5311 if not value: 5312 return option 5313 5314 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5315 5316 return f"{option}{op}{value}"
5318 def credentials_sql(self, expression: exp.Credentials) -> str: 5319 cred_expr = expression.args.get("credentials") 5320 if isinstance(cred_expr, exp.Literal): 5321 # Redshift case: CREDENTIALS <string> 5322 credentials = self.sql(expression, "credentials") 5323 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5324 else: 5325 # Snowflake case: CREDENTIALS = (...) 5326 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5327 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5328 5329 storage = self.sql(expression, "storage") 5330 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5331 5332 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5333 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5334 5335 iam_role = self.sql(expression, "iam_role") 5336 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5337 5338 region = self.sql(expression, "region") 5339 region = f" REGION {region}" if region else "" 5340 5341 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5343 def copy_sql(self, expression: exp.Copy) -> str: 5344 this = self.sql(expression, "this") 5345 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5346 5347 credentials = self.sql(expression, "credentials") 5348 credentials = self.seg(credentials) if credentials else "" 5349 files = self.expressions(expression, key="files", flat=True) 5350 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5351 5352 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5353 params = self.expressions( 5354 expression, 5355 key="params", 5356 sep=sep, 5357 new_line=True, 5358 skip_last=True, 5359 skip_first=True, 5360 indent=self.COPY_PARAMS_ARE_WRAPPED, 5361 ) 5362 5363 if params: 5364 if self.COPY_PARAMS_ARE_WRAPPED: 5365 params = f" WITH ({params})" 5366 elif not self.pretty and (files or credentials): 5367 params = f" {params}" 5368 5369 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5374 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5375 on_sql = "ON" if expression.args.get("on") else "OFF" 5376 filter_col: str | None = self.sql(expression, "filter_column") 5377 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5378 retention_period: str | None = self.sql(expression, "retention_period") 5379 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5380 5381 if filter_col or retention_period: 5382 on_sql = self.func("ON", filter_col, retention_period) 5383 5384 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5386 def maskingpolicycolumnconstraint_sql( 5387 self, expression: exp.MaskingPolicyColumnConstraint 5388 ) -> str: 5389 this = self.sql(expression, "this") 5390 expressions = self.expressions(expression, flat=True) 5391 expressions = f" USING ({expressions})" if expressions else "" 5392 return f"MASKING POLICY {this}{expressions}"
5402 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5403 this = self.sql(expression, "this") 5404 expr = expression.expression 5405 5406 if isinstance(expr, exp.Func): 5407 # T-SQL's CLR functions are case sensitive 5408 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5409 else: 5410 expr = self.sql(expression, "expression") 5411 5412 return self.scope_resolution(expr, this)
5420 def rand_sql(self, expression: exp.Rand) -> str: 5421 lower = self.sql(expression, "lower") 5422 upper = self.sql(expression, "upper") 5423 5424 if lower and upper: 5425 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5426 return self.func("RAND", expression.this)
5428 def changes_sql(self, expression: exp.Changes) -> str: 5429 information = self.sql(expression, "information") 5430 information = f"INFORMATION => {information}" 5431 at_before = self.sql(expression, "at_before") 5432 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5433 end = self.sql(expression, "end") 5434 end = f"{self.seg('')}{end}" if end else "" 5435 5436 return f"CHANGES ({information}){at_before}{end}"
5438 def pad_sql(self, expression: exp.Pad) -> str: 5439 prefix = "L" if expression.args.get("is_left") else "R" 5440 5441 fill_pattern = self.sql(expression, "fill_pattern") or None 5442 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5443 fill_pattern = "' '" 5444 5445 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5451 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5452 generate_series = exp.GenerateSeries(**expression.args) 5453 5454 parent = expression.parent 5455 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5456 parent = parent.parent 5457 5458 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5459 return self.sql(exp.Unnest(expressions=[generate_series])) 5460 5461 if isinstance(parent, exp.Select): 5462 self.unsupported("GenerateSeries projection unnesting is not supported.") 5463 5464 return self.sql(generate_series)
5466 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5467 if self.SUPPORTS_CONVERT_TIMEZONE: 5468 return self.function_fallback_sql(expression) 5469 5470 source_tz = expression.args.get("source_tz") 5471 target_tz = expression.args.get("target_tz") 5472 timestamp = expression.args.get("timestamp") 5473 5474 if source_tz and timestamp: 5475 timestamp = exp.AtTimeZone( 5476 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5477 ) 5478 5479 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5480 5481 return self.sql(expr)
5483 def json_sql(self, expression: exp.JSON) -> str: 5484 this = self.sql(expression, "this") 5485 this = f" {this}" if this else "" 5486 5487 _with = expression.args.get("with_") 5488 5489 if _with is None: 5490 with_sql = "" 5491 elif not _with: 5492 with_sql = " WITHOUT" 5493 else: 5494 with_sql = " WITH" 5495 5496 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5497 5498 return f"JSON{this}{with_sql}{unique_sql}"
5500 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5501 path = self.sql(expression, "path") 5502 returning = self.sql(expression, "returning") 5503 returning = f" RETURNING {returning}" if returning else "" 5504 5505 on_condition = self.sql(expression, "on_condition") 5506 on_condition = f" {on_condition}" if on_condition else "" 5507 5508 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5514 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5515 else_ = "ELSE " if expression.args.get("else_") else "" 5516 condition = self.sql(expression, "expression") 5517 condition = f"WHEN {condition} THEN " if condition else else_ 5518 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5519 return f"{condition}{insert}"
5527 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5528 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5529 empty = expression.args.get("empty") 5530 empty = ( 5531 f"DEFAULT {empty} ON EMPTY" 5532 if isinstance(empty, exp.Expr) 5533 else self.sql(expression, "empty") 5534 ) 5535 5536 error = expression.args.get("error") 5537 error = ( 5538 f"DEFAULT {error} ON ERROR" 5539 if isinstance(error, exp.Expr) 5540 else self.sql(expression, "error") 5541 ) 5542 5543 if error and empty: 5544 error = ( 5545 f"{empty} {error}" 5546 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5547 else f"{error} {empty}" 5548 ) 5549 empty = "" 5550 5551 null = self.sql(expression, "null") 5552 5553 return f"{empty}{error}{null}"
5559 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5560 this = self.sql(expression, "this") 5561 path = self.sql(expression, "path") 5562 5563 passing = self.expressions(expression, "passing") 5564 passing = f" PASSING {passing}" if passing else "" 5565 5566 on_condition = self.sql(expression, "on_condition") 5567 on_condition = f" {on_condition}" if on_condition else "" 5568 5569 path = f"{path}{passing}{on_condition}" 5570 5571 return self.func("JSON_EXISTS", this, path)
5696 def overlay_sql(self, expression: exp.Overlay) -> str: 5697 this = self.sql(expression, "this") 5698 expr = self.sql(expression, "expression") 5699 from_sql = self.sql(expression, "from_") 5700 for_sql = self.sql(expression, "for_") 5701 for_sql = f" FOR {for_sql}" if for_sql else "" 5702 5703 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
5710 def string_sql(self, expression: exp.String) -> str: 5711 this = expression.this 5712 zone = expression.args.get("zone") 5713 5714 if zone: 5715 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5716 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5717 # set for source_tz to transpile the time conversion before the STRING cast 5718 this = exp.ConvertTimezone( 5719 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5720 ) 5721 5722 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
5732 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5733 filler = self.sql(expression, "this") 5734 filler = f" {filler}" if filler else "" 5735 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5736 return f"TRUNCATE{filler} {with_count}"
5738 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5739 if self.SUPPORTS_UNIX_SECONDS: 5740 return self.function_fallback_sql(expression) 5741 5742 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5743 5744 return self.sql( 5745 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5746 )
5748 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5749 dim = expression.expression 5750 5751 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5752 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5753 if not (dim.is_int and dim.name == "1"): 5754 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5755 dim = None 5756 5757 # If dimension is required but not specified, default initialize it 5758 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5759 dim = exp.Literal.number(1) 5760 5761 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5763 def attach_sql(self, expression: exp.Attach) -> str: 5764 this = self.sql(expression, "this") 5765 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5766 expressions = self.expressions(expression) 5767 expressions = f" ({expressions})" if expressions else "" 5768 5769 return f"ATTACH{exists_sql} {this}{expressions}"
5771 def detach_sql(self, expression: exp.Detach) -> str: 5772 kind = self.sql(expression, "kind") 5773 kind = f" {kind}" if kind else "" 5774 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5775 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5776 exists = " IF EXISTS" if expression.args.get("exists") else "" 5777 if exists: 5778 kind = kind or " DATABASE" 5779 5780 this = self.sql(expression, "this") 5781 this = f" {this}" if this else "" 5782 cluster = self.sql(expression, "cluster") 5783 cluster = f" {cluster}" if cluster else "" 5784 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5785 sync = " SYNC" if expression.args.get("sync") else "" 5786 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
5799 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5800 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5801 encode = f"{encode} {self.sql(expression, 'this')}" 5802 5803 properties = expression.args.get("properties") 5804 if properties: 5805 encode = f"{encode} {self.properties(properties)}" 5806 5807 return encode
5809 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5810 this = self.sql(expression, "this") 5811 include = f"INCLUDE {this}" 5812 5813 column_def = self.sql(expression, "column_def") 5814 if column_def: 5815 include = f"{include} {column_def}" 5816 5817 alias = self.sql(expression, "alias") 5818 if alias: 5819 include = f"{include} AS {alias}" 5820 5821 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
5834 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5835 partitions = self.expressions(expression, "partition_expressions") 5836 create = self.expressions(expression, "create_expressions") 5837 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
5839 def partitionbyrangepropertydynamic_sql( 5840 self, expression: exp.PartitionByRangePropertyDynamic 5841 ) -> str: 5842 start = self.sql(expression, "start") 5843 end = self.sql(expression, "end") 5844 5845 every = expression.args["every"] 5846 if isinstance(every, exp.Interval) and every.this.is_string: 5847 every.this.replace(exp.Literal.number(every.name)) 5848 5849 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5862 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5863 kind = self.sql(expression, "kind") 5864 option = self.sql(expression, "option") 5865 option = f" {option}" if option else "" 5866 this = self.sql(expression, "this") 5867 this = f" {this}" if this else "" 5868 columns = self.expressions(expression) 5869 columns = f" {columns}" if columns else "" 5870 return f"{kind}{option} STATISTICS{this}{columns}"
5872 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5873 this = self.sql(expression, "this") 5874 columns = self.expressions(expression) 5875 inner_expression = self.sql(expression, "expression") 5876 inner_expression = f" {inner_expression}" if inner_expression else "" 5877 update_options = self.sql(expression, "update_options") 5878 update_options = f" {update_options} UPDATE" if update_options else "" 5879 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
5890 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5891 kind = self.sql(expression, "kind") 5892 this = self.sql(expression, "this") 5893 this = f" {this}" if this else "" 5894 inner_expression = self.sql(expression, "expression") 5895 return f"VALIDATE {kind}{this}{inner_expression}"
5897 def analyze_sql(self, expression: exp.Analyze) -> str: 5898 options = self.expressions(expression, key="options", sep=" ") 5899 options = f" {options}" if options else "" 5900 kind = self.sql(expression, "kind") 5901 kind = f" {kind}" if kind else "" 5902 this = self.sql(expression, "this") 5903 this = f" {this}" if this else "" 5904 mode = self.sql(expression, "mode") 5905 mode = f" {mode}" if mode else "" 5906 properties = self.sql(expression, "properties") 5907 properties = f" {properties}" if properties else "" 5908 partition = self.sql(expression, "partition") 5909 partition = f" {partition}" if partition else "" 5910 inner_expression = self.sql(expression, "expression") 5911 inner_expression = f" {inner_expression}" if inner_expression else "" 5912 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5914 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5915 this = self.sql(expression, "this") 5916 namespaces = self.expressions(expression, key="namespaces") 5917 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5918 passing = self.expressions(expression, key="passing") 5919 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5920 columns = self.expressions(expression, key="columns") 5921 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5922 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5923 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5929 def export_sql(self, expression: exp.Export) -> str: 5930 this = self.sql(expression, "this") 5931 connection = self.sql(expression, "connection") 5932 connection = f"WITH CONNECTION {connection} " if connection else "" 5933 options = self.sql(expression, "options") 5934 return f"EXPORT DATA {connection}{options} AS {this}"
5940 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5941 variables = self.expressions(expression, "this") 5942 default = self.sql(expression, "default") 5943 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5944 5945 kind = self.sql(expression, "kind") 5946 if isinstance(expression.args.get("kind"), exp.Schema): 5947 kind = f"TABLE {kind}" 5948 5949 kind = f" {kind}" if kind else "" 5950 5951 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
5953 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5954 kind = self.sql(expression, "kind") 5955 this = self.sql(expression, "this") 5956 set = self.sql(expression, "expression") 5957 using = self.sql(expression, "using") 5958 using = f" USING {using}" if using else "" 5959 5960 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5961 5962 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:
5985 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5986 # Snowflake GET/PUT statements: 5987 # PUT <file> <internalStage> <properties> 5988 # GET <internalStage> <file> <properties> 5989 props = expression.args.get("properties") 5990 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5991 this = self.sql(expression, "this") 5992 target = self.sql(expression, "target") 5993 5994 if isinstance(expression, exp.Put): 5995 return f"PUT {this} {target}{props_sql}" 5996 else: 5997 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
5999 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 6000 this = self.sql(expression, "this") 6001 expr = self.sql(expression, "expression") 6002 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 6003 return f"TRANSLATE({this} USING {expr}{with_error})"
6005 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 6006 if self.SUPPORTS_DECODE_CASE: 6007 return self.func("DECODE", *expression.expressions) 6008 6009 decode_expr, *expressions = expression.expressions 6010 6011 ifs = [] 6012 for search, result in zip(expressions[::2], expressions[1::2]): 6013 if isinstance(search, exp.Literal): 6014 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 6015 elif isinstance(search, exp.Null): 6016 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 6017 else: 6018 if isinstance(search, exp.Binary): 6019 search = exp.paren(search) 6020 6021 cond = exp.or_( 6022 decode_expr.eq(search), 6023 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 6024 copy=False, 6025 ) 6026 ifs.append(exp.If(this=cond, true=result)) 6027 6028 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 6029 return self.sql(case)
6031 def semanticview_sql(self, expression: exp.SemanticView) -> str: 6032 this = self.sql(expression, "this") 6033 this = self.seg(this, sep="") 6034 dimensions = self.expressions( 6035 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 6036 ) 6037 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 6038 metrics = self.expressions( 6039 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 6040 ) 6041 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 6042 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 6043 facts = self.seg(f"FACTS {facts}") if facts else "" 6044 where = self.sql(expression, "where") 6045 where = self.seg(f"WHERE {where}") if where else "" 6046 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 6047 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
6049 def getextract_sql(self, expression: exp.GetExtract) -> str: 6050 this = expression.this 6051 expr = expression.expression 6052 6053 if not this.type or not expression.type: 6054 import sqlglot.optimizer.annotate_types 6055 6056 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 6057 6058 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 6059 return self.sql(exp.Bracket(this=this, expressions=[expr])) 6060 6061 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
6078 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 6079 method = self.sql(expression, "method") 6080 kind = expression.args.get("kind") 6081 if not kind: 6082 return f"REFRESH {method}" 6083 6084 every = self.sql(expression, "every") 6085 unit = self.sql(expression, "unit") 6086 every = f" EVERY {every} {unit}" if every else "" 6087 starts = self.sql(expression, "starts") 6088 starts = f" STARTS {starts}" if starts else "" 6089 6090 return f"REFRESH {method} ON {kind}{every}{starts}"
6099 def uuid_sql(self, expression: exp.Uuid) -> str: 6100 is_string = expression.args.get("is_string", False) 6101 uuid_func_sql = self.func("UUID") 6102 6103 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 6104 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 6105 6106 return uuid_func_sql
6108 def initcap_sql(self, expression: exp.Initcap) -> str: 6109 delimiters = expression.expression 6110 6111 if delimiters: 6112 # do not generate delimiters arg if we are round-tripping from default delimiters 6113 if ( 6114 delimiters.is_string 6115 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 6116 ): 6117 delimiters = None 6118 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 6119 self.unsupported("INITCAP does not support custom delimiters") 6120 delimiters = None 6121 6122 return self.func("INITCAP", expression.this, delimiters)
6132 def weekstart_sql(self, expression: exp.WeekStart) -> str: 6133 this = expression.this.name.upper() 6134 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 6135 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 6136 return "WEEK" 6137 6138 return self.func("WEEK", expression.this)
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: