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.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 681 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 682 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 683 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 684 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 685 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 686 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 687 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 688 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 689 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 690 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 691 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 692 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 693 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 694 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 695 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 696 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 697 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 698 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 699 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 700 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 701 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 702 exp.HeapProperty: exp.Properties.Location.POST_WITH, 703 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 704 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 705 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 706 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 707 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 708 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 709 exp.JournalProperty: exp.Properties.Location.POST_NAME, 710 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 711 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 712 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 713 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 714 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 715 exp.LogProperty: exp.Properties.Location.POST_NAME, 716 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 717 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 718 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 719 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 720 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 721 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 722 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 723 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 724 exp.Order: exp.Properties.Location.POST_SCHEMA, 725 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 727 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 729 exp.Property: exp.Properties.Location.POST_WITH, 730 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 731 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 734 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 735 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 736 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 737 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 741 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 742 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 743 exp.Set: exp.Properties.Location.POST_SCHEMA, 744 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.SetProperty: exp.Properties.Location.POST_CREATE, 746 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 747 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 748 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 749 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 750 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 751 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 754 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 756 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.Tags: exp.Properties.Location.POST_WITH, 758 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 759 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 760 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 761 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 762 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 763 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 764 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 765 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 766 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 767 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 768 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 769 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 770 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 771 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 772 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 773 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 774 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 775 } 776 777 # Keywords that can't be used as unquoted identifier names 778 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 779 780 # Exprs whose comments are separated from them for better formatting 781 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 782 exp.Command, 783 exp.Create, 784 exp.Describe, 785 exp.Delete, 786 exp.Drop, 787 exp.From, 788 exp.Insert, 789 exp.Join, 790 exp.MultitableInserts, 791 exp.Order, 792 exp.Group, 793 exp.Having, 794 exp.Select, 795 exp.SetOperation, 796 exp.Update, 797 exp.Where, 798 exp.With, 799 ) 800 801 # Exprs that should not have their comments generated in maybe_comment 802 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 803 exp.Binary, 804 exp.SetOperation, 805 ) 806 807 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 808 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 809 exp.Column, 810 exp.Literal, 811 exp.Neg, 812 exp.Paren, 813 ) 814 815 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 816 exp.DType.NVARCHAR, 817 exp.DType.VARCHAR, 818 exp.DType.CHAR, 819 exp.DType.NCHAR, 820 } 821 822 # Exprs that need to have all CTEs under them bubbled up to them 823 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 824 825 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 826 827 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 828 829 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 830 831 __slots__ = ( 832 "pretty", 833 "identify", 834 "normalize", 835 "pad", 836 "_indent", 837 "normalize_functions", 838 "unsupported_level", 839 "max_unsupported", 840 "leading_comma", 841 "max_text_width", 842 "comments", 843 "dialect", 844 "unsupported_messages", 845 "_escaped_quote_end", 846 "_escaped_byte_quote_end", 847 "_escaped_identifier_end", 848 "_next_name", 849 "_identifier_start", 850 "_identifier_end", 851 "_quote_json_path_key_using_brackets", 852 "_dispatch", 853 ) 854 855 def __init__( 856 self, 857 pretty: bool | int | None = None, 858 identify: str | bool = False, 859 normalize: bool = False, 860 pad: int = 2, 861 indent: int = 2, 862 normalize_functions: str | bool | None = None, 863 unsupported_level: ErrorLevel = ErrorLevel.WARN, 864 max_unsupported: int = 3, 865 leading_comma: bool = False, 866 max_text_width: int = 80, 867 comments: bool = True, 868 dialect: DialectType = None, 869 ): 870 import sqlglot 871 import sqlglot.dialects.dialect 872 873 self.pretty = pretty if pretty is not None else sqlglot.pretty 874 self.identify = identify 875 self.normalize = normalize 876 self.pad = pad 877 self._indent = indent 878 self.unsupported_level = unsupported_level 879 self.max_unsupported = max_unsupported 880 self.leading_comma = leading_comma 881 self.max_text_width = max_text_width 882 self.comments = comments 883 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 884 885 # This is both a Dialect property and a Generator argument, so we prioritize the latter 886 self.normalize_functions = ( 887 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 888 ) 889 890 self.unsupported_messages: list[str] = [] 891 self._escaped_quote_end: str = ( 892 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 893 ) 894 self._escaped_byte_quote_end: str = ( 895 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 896 if self.dialect.BYTE_END 897 else "" 898 ) 899 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 900 901 self._next_name = name_sequence("_t") 902 903 self._identifier_start = self.dialect.IDENTIFIER_START 904 self._identifier_end = self.dialect.IDENTIFIER_END 905 906 self._quote_json_path_key_using_brackets = True 907 908 cls = type(self) 909 dispatch = _DISPATCH_CACHE.get(cls) 910 if dispatch is None: 911 dispatch = _build_dispatch(cls) 912 _DISPATCH_CACHE[cls] = dispatch 913 self._dispatch = dispatch 914 915 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 916 """ 917 Generates the SQL string corresponding to the given syntax tree. 918 919 Args: 920 expression: The syntax tree. 921 copy: Whether to copy the expression. The generator performs mutations so 922 it is safer to copy. 923 924 Returns: 925 The SQL string corresponding to `expression`. 926 """ 927 if copy: 928 expression = expression.copy() 929 930 expression = self.preprocess(expression) 931 932 self.unsupported_messages = [] 933 sql = self.sql(expression).strip() 934 935 if self.pretty: 936 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 937 938 if self.unsupported_level == ErrorLevel.IGNORE: 939 return sql 940 941 if self.unsupported_level == ErrorLevel.WARN: 942 for msg in self.unsupported_messages: 943 logger.warning(msg) 944 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 945 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 946 947 return sql 948 949 def preprocess(self, expression: exp.Expr) -> exp.Expr: 950 """Apply generic preprocessing transformations to a given expression.""" 951 expression = self._move_ctes_to_top_level(expression) 952 953 if self.ENSURE_BOOLS: 954 import sqlglot.transforms 955 956 expression = sqlglot.transforms.ensure_bools(expression) 957 958 return expression 959 960 def _move_ctes_to_top_level(self, expression: E) -> E: 961 if ( 962 not expression.parent 963 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 964 and any(node.parent is not expression for node in expression.find_all(exp.With)) 965 ): 966 import sqlglot.transforms 967 968 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 969 return expression 970 971 def unsupported(self, message: str) -> None: 972 if self.unsupported_level == ErrorLevel.IMMEDIATE: 973 raise UnsupportedError(message) 974 self.unsupported_messages.append(message) 975 976 def sep(self, sep: str = " ") -> str: 977 return f"{sep.strip()}\n" if self.pretty else sep 978 979 def seg(self, sql: str, sep: str = " ") -> str: 980 return f"{self.sep(sep)}{sql}" 981 982 def sanitize_comment(self, comment: str) -> str: 983 comment = " " + comment if comment[0].strip() else comment 984 comment = comment + " " if comment[-1].strip() else comment 985 986 # Escape block comment markers to prevent premature closure or unintended nesting. 987 # This is necessary because single-line comments (--) are converted to block comments 988 # (/* */) on output, and any */ in the original text would close the comment early. 989 comment = comment.replace("*/", "* /").replace("/*", "/ *") 990 991 return comment 992 993 def maybe_comment( 994 self, 995 sql: str, 996 expression: exp.Expr | None = None, 997 comments: list[str] | None = None, 998 separated: bool = False, 999 ) -> str: 1000 comments = ( 1001 ((expression and expression.comments) if comments is None else comments) # type: ignore 1002 if self.comments 1003 else None 1004 ) 1005 1006 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 1007 return sql 1008 1009 comments_sql = " ".join( 1010 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 1011 ) 1012 1013 if not comments_sql: 1014 return sql 1015 1016 comments_sql = self._replace_line_breaks(comments_sql) 1017 1018 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1019 return ( 1020 f"{self.sep()}{comments_sql}{sql}" 1021 if not sql or sql[0].isspace() 1022 else f"{comments_sql}{self.sep()}{sql}" 1023 ) 1024 1025 return f"{sql} {comments_sql}" 1026 1027 def wrap(self, expression: exp.Expr | str) -> str: 1028 this_sql = ( 1029 self.sql(expression) 1030 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1031 else self.sql(expression, "this") 1032 ) 1033 if not this_sql: 1034 return "()" 1035 1036 this_sql = self.indent(this_sql, level=1, pad=0) 1037 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1038 1039 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1040 original = self.identify 1041 self.identify = False 1042 result = func(*args, **kwargs) 1043 self.identify = original 1044 return result 1045 1046 def normalize_func(self, name: str) -> str: 1047 if self.normalize_functions == "upper" or self.normalize_functions is True: 1048 return name.upper() 1049 if self.normalize_functions == "lower": 1050 return name.lower() 1051 return name 1052 1053 def indent( 1054 self, 1055 sql: str, 1056 level: int = 0, 1057 pad: int | None = None, 1058 skip_first: bool = False, 1059 skip_last: bool = False, 1060 ) -> str: 1061 if not self.pretty or not sql: 1062 return sql 1063 1064 pad = self.pad if pad is None else pad 1065 lines = sql.split("\n") 1066 1067 return "\n".join( 1068 ( 1069 line 1070 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1071 else f"{' ' * (level * self._indent + pad)}{line}" 1072 ) 1073 for i, line in enumerate(lines) 1074 ) 1075 1076 def sql( 1077 self, 1078 expression: str | exp.Expr | None, 1079 key: str | None = None, 1080 comment: bool = True, 1081 ) -> str: 1082 if not expression: 1083 return "" 1084 1085 if isinstance(expression, str): 1086 return expression 1087 1088 if key: 1089 value = expression.args.get(key) 1090 if value: 1091 return self.sql(value) 1092 return "" 1093 1094 handler = self._dispatch.get(expression.__class__) 1095 1096 if handler: 1097 sql = handler(self, expression) 1098 elif isinstance(expression, exp.Func): 1099 sql = self.function_fallback_sql(expression) 1100 elif isinstance(expression, exp.Property): 1101 sql = self.property_sql(expression) 1102 else: 1103 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1104 1105 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1106 1107 def uncache_sql(self, expression: exp.Uncache) -> str: 1108 table = self.sql(expression, "this") 1109 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1110 return f"UNCACHE TABLE{exists_sql} {table}" 1111 1112 def cache_sql(self, expression: exp.Cache) -> str: 1113 lazy = " LAZY" if expression.args.get("lazy") else "" 1114 table = self.sql(expression, "this") 1115 options = expression.args.get("options") 1116 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1117 sql = self.sql(expression, "expression") 1118 sql = f" AS{self.sep()}{sql}" if sql else "" 1119 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1120 return self.prepend_ctes(expression, sql) 1121 1122 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1123 default = "DEFAULT " if expression.args.get("default") else "" 1124 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1125 1126 def column_parts(self, expression: exp.Column) -> str: 1127 return ".".join( 1128 self.sql(part) 1129 for part in ( 1130 expression.args.get("catalog"), 1131 expression.args.get("db"), 1132 expression.args.get("table"), 1133 expression.args.get("this"), 1134 ) 1135 if part 1136 ) 1137 1138 def column_sql(self, expression: exp.Column) -> str: 1139 join_mark = " (+)" if expression.args.get("join_mark") else "" 1140 1141 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1142 join_mark = "" 1143 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1144 1145 return f"{self.column_parts(expression)}{join_mark}" 1146 1147 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1148 return self.column_sql(expression) 1149 1150 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1151 this = self.sql(expression, "this") 1152 this = f" {this}" if this else "" 1153 position = self.sql(expression, "position") 1154 return f"{position}{this}" 1155 1156 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1157 column = self.sql(expression, "this") 1158 kind = self.sql(expression, "kind") 1159 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1160 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1161 kind = f"{sep}{kind}" if kind else "" 1162 constraints = f" {constraints}" if constraints else "" 1163 position = self.sql(expression, "position") 1164 position = f" {position}" if position else "" 1165 1166 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1167 kind = "" 1168 1169 return f"{exists}{column}{kind}{constraints}{position}" 1170 1171 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1172 this = self.sql(expression, "this") 1173 kind_sql = self.sql(expression, "kind").strip() 1174 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1175 1176 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1177 this = self.sql(expression, "this") 1178 if expression.args.get("not_null"): 1179 persisted = " PERSISTED NOT NULL" 1180 elif expression.args.get("persisted"): 1181 persisted = " PERSISTED" 1182 else: 1183 persisted = "" 1184 1185 return f"AS {this}{persisted}" 1186 1187 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1188 return self.token_sql(TokenType.AUTO_INCREMENT) 1189 1190 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1191 if isinstance(expression.this, list): 1192 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1193 else: 1194 this = self.sql(expression, "this") 1195 1196 return f"COMPRESS {this}" 1197 1198 def generatedasidentitycolumnconstraint_sql( 1199 self, expression: exp.GeneratedAsIdentityColumnConstraint 1200 ) -> str: 1201 this = "" 1202 if expression.this is not None: 1203 on_null = " ON NULL" if expression.args.get("on_null") else "" 1204 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1205 1206 start = expression.args.get("start") 1207 start = f"START WITH {start}" if start else "" 1208 increment = expression.args.get("increment") 1209 increment = f" INCREMENT BY {increment}" if increment else "" 1210 minvalue = expression.args.get("minvalue") 1211 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1212 maxvalue = expression.args.get("maxvalue") 1213 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1214 cycle = expression.args.get("cycle") 1215 cycle_sql = "" 1216 1217 if cycle is not None: 1218 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1219 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1220 1221 sequence_opts = "" 1222 if start or increment or cycle_sql: 1223 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1224 sequence_opts = f" ({sequence_opts.strip()})" 1225 1226 expr = self.sql(expression, "expression") 1227 expr = f"({expr})" if expr else "IDENTITY" 1228 1229 return f"GENERATED{this} AS {expr}{sequence_opts}" 1230 1231 def generatedasrowcolumnconstraint_sql( 1232 self, expression: exp.GeneratedAsRowColumnConstraint 1233 ) -> str: 1234 start = "START" if expression.args.get("start") else "END" 1235 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1236 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1237 1238 def periodforsystemtimeconstraint_sql( 1239 self, expression: exp.PeriodForSystemTimeConstraint 1240 ) -> str: 1241 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1242 1243 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1244 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1245 1246 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1247 desc = expression.args.get("desc") 1248 if desc is not None: 1249 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1250 options = self.expressions(expression, key="options", flat=True, sep=" ") 1251 options = f" {options}" if options else "" 1252 return f"PRIMARY KEY{options}" 1253 1254 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1255 this = self.sql(expression, "this") 1256 this = f" {this}" if this else "" 1257 index_type = expression.args.get("index_type") 1258 index_type = f" USING {index_type}" if index_type else "" 1259 on_conflict = self.sql(expression, "on_conflict") 1260 on_conflict = f" {on_conflict}" if on_conflict else "" 1261 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1262 options = self.expressions(expression, key="options", flat=True, sep=" ") 1263 options = f" {options}" if options else "" 1264 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1265 1266 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1267 input_ = expression.args.get("input_") 1268 output = expression.args.get("output") 1269 variadic = expression.args.get("variadic") 1270 1271 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1272 if variadic: 1273 return "VARIADIC" 1274 1275 if input_ and output: 1276 return f"IN{self.INOUT_SEPARATOR}OUT" 1277 if input_: 1278 return "IN" 1279 if output: 1280 return "OUT" 1281 1282 return "" 1283 1284 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1285 return self.sql(expression, "this") 1286 1287 def create_sql(self, expression: exp.Create) -> str: 1288 kind = self.sql(expression, "kind") 1289 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1290 1291 properties = expression.args.get("properties") 1292 1293 if ( 1294 kind == "TRIGGER" 1295 and properties 1296 and properties.expressions 1297 and isinstance(properties.expressions[0], exp.TriggerProperties) 1298 and properties.expressions[0].args.get("constraint") 1299 ): 1300 kind = f"CONSTRAINT {kind}" 1301 1302 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1303 1304 this = self.createable_sql(expression, properties_locs) 1305 1306 properties_sql = "" 1307 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1308 exp.Properties.Location.POST_WITH 1309 ): 1310 props_ast = exp.Properties( 1311 expressions=[ 1312 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1313 *properties_locs[exp.Properties.Location.POST_WITH], 1314 ] 1315 ) 1316 props_ast.parent = expression 1317 properties_sql = self.sql(props_ast) 1318 1319 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1320 properties_sql = self.sep() + properties_sql 1321 elif not self.pretty: 1322 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1323 properties_sql = f" {properties_sql}" 1324 1325 begin = " BEGIN" if expression.args.get("begin") else "" 1326 1327 expression_sql = self.sql(expression, "expression") 1328 if expression_sql: 1329 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1330 1331 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1332 postalias_props_sql = "" 1333 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1334 postalias_props_sql = self.properties( 1335 exp.Properties( 1336 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1337 ), 1338 wrapped=False, 1339 ) 1340 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1341 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1342 1343 postindex_props_sql = "" 1344 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1345 postindex_props_sql = self.properties( 1346 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1347 wrapped=False, 1348 prefix=" ", 1349 ) 1350 1351 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1352 indexes = f" {indexes}" if indexes else "" 1353 index_sql = indexes + postindex_props_sql 1354 1355 replace = " OR REPLACE" if expression.args.get("replace") else "" 1356 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1357 unique = " UNIQUE" if expression.args.get("unique") else "" 1358 1359 clustered = expression.args.get("clustered") 1360 if clustered is None: 1361 clustered_sql = "" 1362 elif clustered: 1363 clustered_sql = " CLUSTERED COLUMNSTORE" 1364 else: 1365 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1366 1367 postcreate_props_sql = "" 1368 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1369 postcreate_props_sql = self.properties( 1370 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1371 sep=" ", 1372 prefix=" ", 1373 wrapped=False, 1374 ) 1375 1376 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1377 1378 postexpression_props_sql = "" 1379 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1380 postexpression_props_sql = self.properties( 1381 exp.Properties( 1382 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1383 ), 1384 sep=" ", 1385 prefix=" ", 1386 wrapped=False, 1387 ) 1388 1389 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1390 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1391 no_schema_binding = ( 1392 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1393 ) 1394 1395 clone = self.sql(expression, "clone") 1396 clone = f" {clone}" if clone else "" 1397 1398 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1399 properties_expression = f"{expression_sql}{properties_sql}" 1400 else: 1401 properties_expression = f"{properties_sql}{expression_sql}" 1402 1403 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1404 return self.prepend_ctes(expression, expression_sql) 1405 1406 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1407 start = self.sql(expression, "start") 1408 start = f"START WITH {start}" if start else "" 1409 increment = self.sql(expression, "increment") 1410 increment = f" INCREMENT BY {increment}" if increment else "" 1411 minvalue = self.sql(expression, "minvalue") 1412 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1413 maxvalue = self.sql(expression, "maxvalue") 1414 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1415 owned = self.sql(expression, "owned") 1416 owned = f" OWNED BY {owned}" if owned else "" 1417 1418 cache = expression.args.get("cache") 1419 if cache is None: 1420 cache_str = "" 1421 elif cache is True: 1422 cache_str = " CACHE" 1423 else: 1424 cache_str = f" CACHE {cache}" 1425 1426 options = self.expressions(expression, key="options", flat=True, sep=" ") 1427 options = f" {options}" if options else "" 1428 1429 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1430 1431 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1432 timing = expression.args.get("timing", "") 1433 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1434 timing_events = f"{timing} {events}".strip() if timing or events else "" 1435 1436 parts = [timing_events, "ON", self.sql(expression, "table")] 1437 1438 if referenced_table := expression.args.get("referenced_table"): 1439 parts.extend(["FROM", self.sql(referenced_table)]) 1440 1441 if deferrable := expression.args.get("deferrable"): 1442 parts.append(deferrable) 1443 1444 if initially := expression.args.get("initially"): 1445 parts.append(f"INITIALLY {initially}") 1446 1447 if referencing := expression.args.get("referencing"): 1448 parts.append(self.sql(referencing)) 1449 1450 if for_each := expression.args.get("for_each"): 1451 parts.append(f"FOR EACH {for_each}") 1452 1453 if when := expression.args.get("when"): 1454 parts.append(f"WHEN ({self.sql(when)})") 1455 1456 parts.append(self.sql(expression, "execute")) 1457 1458 return self.sep().join(parts) 1459 1460 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1461 parts = [] 1462 1463 if old_alias := expression.args.get("old"): 1464 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1465 1466 if new_alias := expression.args.get("new"): 1467 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1468 1469 return f"REFERENCING {' '.join(parts)}" 1470 1471 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1472 columns = expression.args.get("columns") 1473 if columns: 1474 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1475 1476 return self.sql(expression, "this") 1477 1478 def clone_sql(self, expression: exp.Clone) -> str: 1479 this = self.sql(expression, "this") 1480 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1481 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1482 return f"{shallow}{keyword} {this}" 1483 1484 def describe_sql(self, expression: exp.Describe) -> str: 1485 style = expression.args.get("style") 1486 style = f" {style}" if style else "" 1487 partition = self.sql(expression, "partition") 1488 partition = f" {partition}" if partition else "" 1489 format = self.sql(expression, "format") 1490 format = f" {format}" if format else "" 1491 as_json = " AS JSON" if expression.args.get("as_json") else "" 1492 1493 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1494 1495 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1496 tag = self.sql(expression, "tag") 1497 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1498 1499 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1500 with_ = self.sql(expression, "with_") 1501 if with_: 1502 sql = f"{with_}{self.sep()}{sql}" 1503 return sql 1504 1505 def with_sql(self, expression: exp.With) -> str: 1506 sql = self.expressions(expression, flat=True) 1507 recursive = ( 1508 "RECURSIVE " 1509 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1510 else "" 1511 ) 1512 search = self.sql(expression, "search") 1513 search = f" {search}" if search else "" 1514 1515 return f"WITH {recursive}{sql}{search}" 1516 1517 def cte_sql(self, expression: exp.CTE) -> str: 1518 alias = expression.args.get("alias") 1519 if alias: 1520 alias.add_comments(expression.pop_comments()) 1521 1522 alias_sql = self.sql(expression, "alias") 1523 1524 materialized = expression.args.get("materialized") 1525 if materialized is False: 1526 materialized = "NOT MATERIALIZED " 1527 elif materialized: 1528 materialized = "MATERIALIZED " 1529 1530 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1531 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1532 1533 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1534 1535 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1536 alias = self.sql(expression, "this") 1537 columns = self.expressions(expression, key="columns", flat=True) 1538 columns = f"({columns})" if columns else "" 1539 1540 if ( 1541 columns 1542 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1543 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1544 ): 1545 columns = "" 1546 self.unsupported("Named columns are not supported in table alias.") 1547 1548 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1549 alias = self._next_name() 1550 1551 return f"{alias}{columns}" 1552 1553 def bitstring_sql(self, expression: exp.BitString) -> str: 1554 this = self.sql(expression, "this") 1555 if self.dialect.BIT_START: 1556 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1557 return f"{int(this, 2)}" 1558 1559 def hexstring_sql( 1560 self, expression: exp.HexString, binary_function_repr: str | None = None 1561 ) -> str: 1562 this = self.sql(expression, "this") 1563 is_integer_type = expression.args.get("is_integer") 1564 1565 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1566 not self.dialect.HEX_START and not binary_function_repr 1567 ): 1568 # Integer representation will be returned if: 1569 # - The read dialect treats the hex value as integer literal but not the write 1570 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1571 return f"{int(this, 16)}" 1572 1573 if not is_integer_type: 1574 # Read dialect treats the hex value as BINARY/BLOB 1575 if binary_function_repr: 1576 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1577 return self.func(binary_function_repr, exp.Literal.string(this)) 1578 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1579 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1580 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1581 1582 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1583 1584 def bytestring_sql(self, expression: exp.ByteString) -> str: 1585 this = self.sql(expression, "this") 1586 if self.dialect.BYTE_START: 1587 escaped_byte_string = self.escape_str( 1588 this, 1589 escape_backslash=False, 1590 delimiter=self.dialect.BYTE_END, 1591 escaped_delimiter=self._escaped_byte_quote_end, 1592 is_byte_string=True, 1593 ) 1594 is_bytes = expression.args.get("is_bytes", False) 1595 delimited_byte_string = ( 1596 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1597 ) 1598 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1599 return self.sql( 1600 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1601 ) 1602 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1603 return self.sql( 1604 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1605 ) 1606 1607 return delimited_byte_string 1608 return this 1609 1610 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1611 this = self.sql(expression, "this") 1612 escape = expression.args.get("escape") 1613 1614 if self.dialect.UNICODE_START: 1615 escape_substitute = r"\\\1" 1616 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1617 else: 1618 escape_substitute = r"\\u\1" 1619 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1620 1621 if escape: 1622 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1623 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1624 else: 1625 escape_pattern = ESCAPED_UNICODE_RE 1626 escape_sql = "" 1627 1628 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1629 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1630 1631 return f"{left_quote}{this}{right_quote}{escape_sql}" 1632 1633 def rawstring_sql(self, expression: exp.RawString) -> str: 1634 string = expression.this 1635 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1636 string = string.replace("\\", "\\\\") 1637 1638 string = self.escape_str(string, escape_backslash=False) 1639 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1640 1641 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1642 this = self.sql(expression, "this") 1643 specifier = self.sql(expression, "expression") 1644 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1645 return f"{this}{specifier}" 1646 1647 def datatype_param_bound_limiter( 1648 self, 1649 expression: exp.DataType, 1650 type_value: exp.DType, 1651 defaults: tuple[int, ...], 1652 bounds: tuple[int | None, ...], 1653 ) -> exp.DataType: 1654 params = expression.expressions 1655 1656 if not params: 1657 if defaults: 1658 expression.set( 1659 "expressions", 1660 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1661 ) 1662 return expression 1663 1664 if not bounds: 1665 return expression 1666 1667 for i, param in enumerate(params): 1668 bound = bounds[i] if i < len(bounds) else None 1669 if bound is None: 1670 continue 1671 1672 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1673 if ( 1674 isinstance(param_value, exp.Literal) 1675 and param_value.is_number 1676 and int(param_value.to_py()) > bound 1677 ): 1678 self.unsupported( 1679 f"{type_value.value} parameter {param_value.name} exceeds " 1680 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1681 ) 1682 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1683 1684 return expression 1685 1686 def datatype_sql(self, expression: exp.DataType) -> str: 1687 nested = "" 1688 values = "" 1689 1690 expr_nested = expression.args.get("nested") 1691 type_value = expression.this 1692 1693 if ( 1694 not expr_nested 1695 and isinstance(type_value, exp.DType) 1696 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1697 ): 1698 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1699 1700 interior = ( 1701 self.expressions( 1702 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1703 ) 1704 if expr_nested and self.pretty 1705 else self.expressions(expression, flat=True) 1706 ) 1707 1708 if type_value in self.UNSUPPORTED_TYPES: 1709 self.unsupported( 1710 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1711 ) 1712 1713 type_sql: t.Any = "" 1714 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1715 type_sql = self.sql(expression, "kind") 1716 elif type_value == exp.DType.CHARACTER_SET: 1717 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1718 else: 1719 type_sql = ( 1720 self.TYPE_MAPPING.get(type_value, type_value.value) 1721 if isinstance(type_value, exp.DType) 1722 else type_value 1723 ) 1724 1725 if interior: 1726 if expr_nested: 1727 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1728 if expression.args.get("values") is not None: 1729 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1730 values = self.expressions(expression, key="values", flat=True) 1731 values = f"{delimiters[0]}{values}{delimiters[1]}" 1732 elif type_value == exp.DType.INTERVAL: 1733 nested = f" {interior}" 1734 else: 1735 nested = f"({interior})" 1736 1737 type_sql = f"{type_sql}{nested}{values}" 1738 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1739 exp.DType.TIMETZ, 1740 exp.DType.TIMESTAMPTZ, 1741 ): 1742 type_sql = f"{type_sql} WITH TIME ZONE" 1743 1744 collate = self.sql(expression, "collate") 1745 if collate: 1746 type_sql = f"{type_sql} COLLATE {collate}" 1747 1748 return type_sql 1749 1750 def directory_sql(self, expression: exp.Directory) -> str: 1751 local = "LOCAL " if expression.args.get("local") else "" 1752 row_format = self.sql(expression, "row_format") 1753 row_format = f" {row_format}" if row_format else "" 1754 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1755 1756 def delete_sql(self, expression: exp.Delete) -> str: 1757 hint = self.sql(expression, "hint") 1758 this = self.sql(expression, "this") 1759 this = f" FROM {this}" if this else "" 1760 using = self.expressions(expression, key="using") 1761 using = f" USING {using}" if using else "" 1762 cluster = self.sql(expression, "cluster") 1763 cluster = f" {cluster}" if cluster else "" 1764 where = self.sql(expression, "where") 1765 returning = self.sql(expression, "returning") 1766 order = self.sql(expression, "order") 1767 limit = self.sql(expression, "limit") 1768 tables = self.expressions(expression, key="tables") 1769 tables = f" {tables}" if tables else "" 1770 if self.RETURNING_END: 1771 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1772 else: 1773 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1774 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1775 1776 def drop_sql(self, expression: exp.Drop) -> str: 1777 this = self.sql(expression, "this") 1778 expressions = self.expressions(expression, flat=True) 1779 expressions = f" ({expressions})" if expressions else "" 1780 kind = expression.args["kind"] 1781 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1782 iceberg = ( 1783 " ICEBERG" 1784 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1785 else "" 1786 ) 1787 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1788 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1789 on_cluster = self.sql(expression, "cluster") 1790 on_cluster = f" {on_cluster}" if on_cluster else "" 1791 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1792 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1793 cascade = " CASCADE" if expression.args.get("cascade") else "" 1794 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1795 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1796 purge = " PURGE" if expression.args.get("purge") else "" 1797 sync = " SYNC" if expression.args.get("sync") else "" 1798 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}" 1799 1800 def set_operation(self, expression: exp.SetOperation) -> str: 1801 op_type = type(expression) 1802 op_name = op_type.key.upper() 1803 1804 distinct = expression.args.get("distinct") 1805 if ( 1806 distinct is False 1807 and op_type in (exp.Except, exp.Intersect) 1808 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1809 ): 1810 self.unsupported(f"{op_name} ALL is not supported") 1811 1812 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1813 1814 if distinct is None: 1815 distinct = default_distinct 1816 if distinct is None: 1817 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1818 1819 if distinct is default_distinct: 1820 distinct_or_all = "" 1821 else: 1822 distinct_or_all = " DISTINCT" if distinct else " ALL" 1823 1824 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1825 side_kind = f"{side_kind} " if side_kind else "" 1826 1827 by_name = " BY NAME" if expression.args.get("by_name") else "" 1828 on = self.expressions(expression, key="on", flat=True) 1829 on = f" ON ({on})" if on else "" 1830 1831 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1832 1833 def set_operations(self, expression: exp.SetOperation) -> str: 1834 if not self.SET_OP_MODIFIERS: 1835 limit = expression.args.get("limit") 1836 order = expression.args.get("order") 1837 1838 if limit or order: 1839 select = self._move_ctes_to_top_level( 1840 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1841 ) 1842 1843 if limit: 1844 select = select.limit(limit.pop(), copy=False) 1845 if order: 1846 select = select.order_by(order.pop(), copy=False) 1847 return self.sql(select) 1848 1849 sqls: list[str] = [] 1850 stack: list[str | exp.Expr] = [expression] 1851 1852 while stack: 1853 node = stack.pop() 1854 1855 if isinstance(node, exp.SetOperation): 1856 stack.append(node.expression) 1857 stack.append( 1858 self.maybe_comment( 1859 self.set_operation(node), comments=node.comments, separated=True 1860 ) 1861 ) 1862 stack.append(node.this) 1863 else: 1864 sqls.append(self.sql(node)) 1865 1866 this = self.sep().join(sqls) 1867 this = self.query_modifiers(expression, this) 1868 return self.prepend_ctes(expression, this) 1869 1870 def fetch_sql(self, expression: exp.Fetch) -> str: 1871 direction = expression.args.get("direction") 1872 direction = f" {direction}" if direction else "" 1873 count = self.sql(expression, "count") 1874 count = f" {count}" if count else "" 1875 limit_options = self.sql(expression, "limit_options") 1876 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1877 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1878 1879 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1880 percent = " PERCENT" if expression.args.get("percent") else "" 1881 rows = " ROWS" if expression.args.get("rows") else "" 1882 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1883 if not with_ties and rows: 1884 with_ties = " ONLY" 1885 return f"{percent}{rows}{with_ties}" 1886 1887 def filter_sql(self, expression: exp.Filter) -> str: 1888 if self.AGGREGATE_FILTER_SUPPORTED: 1889 this = self.sql(expression, "this") 1890 where = self.sql(expression, "expression").strip() 1891 return f"{this} FILTER({where})" 1892 1893 agg = expression.this 1894 agg_arg = agg.this 1895 cond = expression.expression.this 1896 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1897 return self.sql(agg) 1898 1899 def hint_sql(self, expression: exp.Hint) -> str: 1900 if not self.QUERY_HINTS: 1901 self.unsupported("Hints are not supported") 1902 return "" 1903 1904 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1905 1906 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1907 using = self.sql(expression, "using") 1908 using = f" USING {using}" if using else "" 1909 columns = self.expressions(expression, key="columns", flat=True) 1910 columns = f"({columns})" if columns else "" 1911 partition_by = self.expressions(expression, key="partition_by", flat=True) 1912 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1913 where = self.sql(expression, "where") 1914 include = self.expressions(expression, key="include", flat=True) 1915 if include: 1916 include = f" INCLUDE ({include})" 1917 with_storage = self.expressions(expression, key="with_storage", flat=True) 1918 with_storage = f" WITH ({with_storage})" if with_storage else "" 1919 tablespace = self.sql(expression, "tablespace") 1920 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1921 on = self.sql(expression, "on") 1922 on = f" ON {on}" if on else "" 1923 1924 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1925 1926 def index_sql(self, expression: exp.Index) -> str: 1927 unique = "UNIQUE " if expression.args.get("unique") else "" 1928 primary = "PRIMARY " if expression.args.get("primary") else "" 1929 amp = "AMP " if expression.args.get("amp") else "" 1930 name = self.sql(expression, "this") 1931 name = f"{name} " if name else "" 1932 table = self.sql(expression, "table") 1933 table = f"{self.INDEX_ON} {table}" if table else "" 1934 1935 index = "INDEX " if not table else "" 1936 1937 params = self.sql(expression, "params") 1938 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1939 1940 def identifier_sql(self, expression: exp.Identifier) -> str: 1941 text = expression.name 1942 lower = text.lower() 1943 quoted = expression.quoted 1944 text = lower if self.normalize and not quoted else text 1945 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1946 if ( 1947 quoted 1948 or self.dialect.can_quote(expression, self.identify) 1949 or lower in self.RESERVED_KEYWORDS 1950 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1951 ): 1952 text = ( 1953 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1954 ) 1955 return text 1956 1957 def hex_sql(self, expression: exp.Hex) -> str: 1958 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1959 if self.dialect.HEX_LOWERCASE: 1960 text = self.func("LOWER", text) 1961 1962 return text 1963 1964 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1965 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1966 if not self.dialect.HEX_LOWERCASE: 1967 text = self.func("LOWER", text) 1968 return text 1969 1970 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1971 input_format = self.sql(expression, "input_format") 1972 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1973 output_format = self.sql(expression, "output_format") 1974 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1975 return self.sep().join((input_format, output_format)) 1976 1977 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1978 string = self.sql(exp.Literal.string(expression.name)) 1979 return f"{prefix}{string}" 1980 1981 def partition_sql(self, expression: exp.Partition) -> str: 1982 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1983 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1984 1985 def properties_sql(self, expression: exp.Properties) -> str: 1986 root_properties = [] 1987 with_properties = [] 1988 1989 for p in expression.expressions: 1990 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1991 if p_loc == exp.Properties.Location.POST_WITH: 1992 with_properties.append(p) 1993 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1994 root_properties.append(p) 1995 1996 root_props_ast = exp.Properties(expressions=root_properties) 1997 root_props_ast.parent = expression.parent 1998 1999 with_props_ast = exp.Properties(expressions=with_properties) 2000 with_props_ast.parent = expression.parent 2001 2002 root_props = self.root_properties(root_props_ast) 2003 with_props = self.with_properties(with_props_ast) 2004 2005 if root_props and with_props and not self.pretty: 2006 with_props = " " + with_props 2007 2008 return root_props + with_props 2009 2010 def root_properties(self, properties: exp.Properties) -> str: 2011 if properties.expressions: 2012 return self.expressions(properties, indent=False, sep=" ") 2013 return "" 2014 2015 def properties( 2016 self, 2017 properties: exp.Properties, 2018 prefix: str = "", 2019 sep: str = ", ", 2020 suffix: str = "", 2021 wrapped: bool = True, 2022 ) -> str: 2023 if properties.expressions: 2024 expressions = self.expressions(properties, sep=sep, indent=False) 2025 if expressions: 2026 expressions = self.wrap(expressions) if wrapped else expressions 2027 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2028 return "" 2029 2030 def with_properties(self, properties: exp.Properties) -> str: 2031 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2032 2033 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2034 properties_locs = defaultdict(list) 2035 for p in properties.expressions: 2036 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2037 if p_loc != exp.Properties.Location.UNSUPPORTED: 2038 properties_locs[p_loc].append(p) 2039 else: 2040 self.unsupported(f"Unsupported property {p.key}") 2041 2042 return properties_locs 2043 2044 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2045 if isinstance(expression.this, exp.Dot): 2046 return self.sql(expression, "this") 2047 return f"'{expression.name}'" if string_key else expression.name 2048 2049 def property_sql(self, expression: exp.Property) -> str: 2050 property_cls = expression.__class__ 2051 if property_cls == exp.Property: 2052 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2053 2054 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2055 if not property_name: 2056 self.unsupported(f"Unsupported property {expression.key}") 2057 2058 return f"{property_name}={self.sql(expression, 'this')}" 2059 2060 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2061 return f"UUID {self.sql(expression, 'this')}" 2062 2063 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2064 if self.SUPPORTS_CREATE_TABLE_LIKE: 2065 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2066 options = f" {options}" if options else "" 2067 2068 like = f"LIKE {self.sql(expression, 'this')}{options}" 2069 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2070 like = f"({like})" 2071 2072 return like 2073 2074 if expression.expressions: 2075 self.unsupported("Transpilation of LIKE property options is unsupported") 2076 2077 select = exp.select("*").from_(expression.this).limit(0) 2078 return f"AS {self.sql(select)}" 2079 2080 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2081 no = "NO " if expression.args.get("no") else "" 2082 protection = " PROTECTION" if expression.args.get("protection") else "" 2083 return f"{no}FALLBACK{protection}" 2084 2085 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2086 no = "NO " if expression.args.get("no") else "" 2087 local = expression.args.get("local") 2088 local = f"{local} " if local else "" 2089 dual = "DUAL " if expression.args.get("dual") else "" 2090 before = "BEFORE " if expression.args.get("before") else "" 2091 after = "AFTER " if expression.args.get("after") else "" 2092 return f"{no}{local}{dual}{before}{after}JOURNAL" 2093 2094 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2095 freespace = self.sql(expression, "this") 2096 percent = " PERCENT" if expression.args.get("percent") else "" 2097 return f"FREESPACE={freespace}{percent}" 2098 2099 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2100 if expression.args.get("default"): 2101 property = "DEFAULT" 2102 elif expression.args.get("on"): 2103 property = "ON" 2104 else: 2105 property = "OFF" 2106 return f"CHECKSUM={property}" 2107 2108 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2109 if expression.args.get("no"): 2110 return "NO MERGEBLOCKRATIO" 2111 if expression.args.get("default"): 2112 return "DEFAULT MERGEBLOCKRATIO" 2113 2114 percent = " PERCENT" if expression.args.get("percent") else "" 2115 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2116 2117 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2118 expressions = self.expressions(expression, flat=True) 2119 expressions = f"({expressions})" if expressions else "" 2120 return f"USING {self.sql(expression, 'this')}{expressions}" 2121 2122 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2123 default = expression.args.get("default") 2124 minimum = expression.args.get("minimum") 2125 maximum = expression.args.get("maximum") 2126 if default or minimum or maximum: 2127 if default: 2128 prop = "DEFAULT" 2129 elif minimum: 2130 prop = "MINIMUM" 2131 else: 2132 prop = "MAXIMUM" 2133 return f"{prop} DATABLOCKSIZE" 2134 units = expression.args.get("units") 2135 units = f" {units}" if units else "" 2136 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2137 2138 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2139 autotemp = expression.args.get("autotemp") 2140 always = expression.args.get("always") 2141 default = expression.args.get("default") 2142 manual = expression.args.get("manual") 2143 never = expression.args.get("never") 2144 2145 if autotemp is not None: 2146 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2147 elif always: 2148 prop = "ALWAYS" 2149 elif default: 2150 prop = "DEFAULT" 2151 elif manual: 2152 prop = "MANUAL" 2153 elif never: 2154 prop = "NEVER" 2155 return f"BLOCKCOMPRESSION={prop}" 2156 2157 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2158 no = expression.args.get("no") 2159 no = " NO" if no else "" 2160 concurrent = expression.args.get("concurrent") 2161 concurrent = " CONCURRENT" if concurrent else "" 2162 target = self.sql(expression, "target") 2163 target = f" {target}" if target else "" 2164 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2165 2166 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2167 if isinstance(expression.this, list): 2168 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2169 if expression.this: 2170 modulus = self.sql(expression, "this") 2171 remainder = self.sql(expression, "expression") 2172 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2173 2174 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2175 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2176 return f"FROM ({from_expressions}) TO ({to_expressions})" 2177 2178 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2179 this = self.sql(expression, "this") 2180 2181 for_values_or_default = expression.expression 2182 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2183 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2184 else: 2185 for_values_or_default = " DEFAULT" 2186 2187 return f"PARTITION OF {this}{for_values_or_default}" 2188 2189 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2190 kind = expression.args.get("kind") 2191 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2192 for_or_in = expression.args.get("for_or_in") 2193 for_or_in = f" {for_or_in}" if for_or_in else "" 2194 lock_type = expression.args.get("lock_type") 2195 override = " OVERRIDE" if expression.args.get("override") else "" 2196 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2197 2198 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2199 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2200 statistics = expression.args.get("statistics") 2201 statistics_sql = "" 2202 if statistics is not None: 2203 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2204 return f"{data_sql}{statistics_sql}" 2205 2206 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2207 this = self.sql(expression, "this") 2208 this = f"HISTORY_TABLE={this}" if this else "" 2209 data_consistency: str | None = self.sql(expression, "data_consistency") 2210 data_consistency = ( 2211 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2212 ) 2213 retention_period: str | None = self.sql(expression, "retention_period") 2214 retention_period = ( 2215 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2216 ) 2217 2218 if this: 2219 on_sql = self.func("ON", this, data_consistency, retention_period) 2220 else: 2221 on_sql = "ON" if expression.args.get("on") else "OFF" 2222 2223 sql = f"SYSTEM_VERSIONING={on_sql}" 2224 2225 return f"WITH({sql})" if expression.args.get("with_") else sql 2226 2227 def insert_sql(self, expression: exp.Insert) -> str: 2228 hint = self.sql(expression, "hint") 2229 overwrite = expression.args.get("overwrite") 2230 2231 if isinstance(expression.this, exp.Directory): 2232 this = " OVERWRITE" if overwrite else " INTO" 2233 else: 2234 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2235 2236 stored = self.sql(expression, "stored") 2237 stored = f" {stored}" if stored else "" 2238 alternative = expression.args.get("alternative") 2239 alternative = f" OR {alternative}" if alternative else "" 2240 ignore = " IGNORE" if expression.args.get("ignore") else "" 2241 is_function = expression.args.get("is_function") 2242 if is_function: 2243 this = f"{this} FUNCTION" 2244 this = f"{this} {self.sql(expression, 'this')}" 2245 2246 exists = " IF EXISTS" if expression.args.get("exists") else "" 2247 where = self.sql(expression, "where") 2248 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2249 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2250 on_conflict = self.sql(expression, "conflict") 2251 on_conflict = f" {on_conflict}" if on_conflict else "" 2252 by_name = " BY NAME" if expression.args.get("by_name") else "" 2253 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2254 returning = self.sql(expression, "returning") 2255 2256 if self.RETURNING_END: 2257 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2258 else: 2259 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2260 2261 partition_by = self.sql(expression, "partition") 2262 partition_by = f" {partition_by}" if partition_by else "" 2263 settings = self.sql(expression, "settings") 2264 settings = f" {settings}" if settings else "" 2265 2266 source = self.sql(expression, "source") 2267 source = f"TABLE {source}" if source else "" 2268 2269 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2270 return self.prepend_ctes(expression, sql) 2271 2272 def introducer_sql(self, expression: exp.Introducer) -> str: 2273 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2274 2275 def kill_sql(self, expression: exp.Kill) -> str: 2276 kind = self.sql(expression, "kind") 2277 kind = f" {kind}" if kind else "" 2278 this = self.sql(expression, "this") 2279 this = f" {this}" if this else "" 2280 return f"KILL{kind}{this}" 2281 2282 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2283 return expression.name 2284 2285 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2286 return expression.name 2287 2288 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2289 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2290 2291 constraint = self.sql(expression, "constraint") 2292 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2293 2294 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2295 if conflict_keys: 2296 conflict_keys = f"({conflict_keys})" 2297 2298 index_predicate = self.sql(expression, "index_predicate") 2299 conflict_keys = f"{conflict_keys}{index_predicate} " 2300 2301 action = self.sql(expression, "action") 2302 2303 expressions = self.expressions(expression, flat=True) 2304 if expressions: 2305 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2306 expressions = f" {set_keyword}{expressions}" 2307 2308 where = self.sql(expression, "where") 2309 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2310 2311 def returning_sql(self, expression: exp.Returning) -> str: 2312 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2313 2314 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2315 fields = self.sql(expression, "fields") 2316 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2317 escaped = self.sql(expression, "escaped") 2318 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2319 items = self.sql(expression, "collection_items") 2320 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2321 keys = self.sql(expression, "map_keys") 2322 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2323 lines = self.sql(expression, "lines") 2324 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2325 null = self.sql(expression, "null") 2326 null = f" NULL DEFINED AS {null}" if null else "" 2327 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2328 2329 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2330 return f"WITH ({self.expressions(expression, flat=True)})" 2331 2332 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2333 this = f"{self.sql(expression, 'this')} INDEX" 2334 target = self.sql(expression, "target") 2335 target = f" FOR {target}" if target else "" 2336 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2337 2338 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2339 this = self.sql(expression, "this") 2340 kind = self.sql(expression, "kind") 2341 expr = self.sql(expression, "expression") 2342 return f"{this} ({kind} => {expr})" 2343 2344 def table_parts(self, expression: exp.Table) -> str: 2345 return ".".join( 2346 self.sql(part) 2347 for part in ( 2348 expression.args.get("catalog"), 2349 expression.args.get("db"), 2350 expression.args.get("this"), 2351 ) 2352 if part is not None 2353 ) 2354 2355 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2356 table = self.table_parts(expression) 2357 only = "ONLY " if expression.args.get("only") else "" 2358 partition = self.sql(expression, "partition") 2359 partition = f" {partition}" if partition else "" 2360 version = self.sql(expression, "version") 2361 version = f" {version}" if version else "" 2362 alias = self.sql(expression, "alias") 2363 alias = f"{sep}{alias}" if alias else "" 2364 2365 sample = self.sql(expression, "sample") 2366 post_alias = "" 2367 pre_alias = "" 2368 2369 if self.dialect.ALIAS_POST_TABLESAMPLE: 2370 pre_alias = sample 2371 else: 2372 post_alias = sample 2373 2374 if self.dialect.ALIAS_POST_VERSION: 2375 pre_alias = f"{pre_alias}{version}" 2376 else: 2377 post_alias = f"{post_alias}{version}" 2378 2379 hints = self.expressions(expression, key="hints", sep=" ") 2380 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2381 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2382 joins = self.indent( 2383 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2384 ) 2385 laterals = self.expressions(expression, key="laterals", sep="") 2386 2387 file_format = self.sql(expression, "format") 2388 if file_format: 2389 pattern = self.sql(expression, "pattern") 2390 pattern = f", PATTERN => {pattern}" if pattern else "" 2391 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2392 2393 ordinality = expression.args.get("ordinality") or "" 2394 if ordinality: 2395 ordinality = f" WITH ORDINALITY{alias}" 2396 alias = "" 2397 2398 when = self.sql(expression, "when") 2399 if when: 2400 table = f"{table} {when}" 2401 2402 changes = self.sql(expression, "changes") 2403 changes = f" {changes}" if changes else "" 2404 2405 rows_from = self.expressions(expression, key="rows_from") 2406 if rows_from: 2407 table = f"ROWS FROM {self.wrap(rows_from)}" 2408 2409 indexed = expression.args.get("indexed") 2410 if indexed is not None: 2411 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2412 else: 2413 indexed = "" 2414 2415 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2416 2417 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2418 table = self.func("TABLE", expression.this) 2419 alias = self.sql(expression, "alias") 2420 alias = f" AS {alias}" if alias else "" 2421 sample = self.sql(expression, "sample") 2422 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2423 joins = self.indent( 2424 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2425 ) 2426 return f"{table}{alias}{pivots}{sample}{joins}" 2427 2428 def tablesample_sql( 2429 self, 2430 expression: exp.TableSample, 2431 tablesample_keyword: str | None = None, 2432 ) -> str: 2433 method = self.sql(expression, "method") 2434 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2435 numerator = self.sql(expression, "bucket_numerator") 2436 denominator = self.sql(expression, "bucket_denominator") 2437 field = self.sql(expression, "bucket_field") 2438 field = f" ON {field}" if field else "" 2439 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2440 seed = self.sql(expression, "seed") 2441 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2442 2443 size = self.sql(expression, "size") 2444 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2445 size = f"{size} ROWS" 2446 2447 percent = self.sql(expression, "percent") 2448 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2449 percent = f"{percent} PERCENT" 2450 2451 expr = f"{bucket}{percent}{size}" 2452 if self.TABLESAMPLE_REQUIRES_PARENS: 2453 expr = f"({expr})" 2454 2455 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2456 2457 def pivot_sql(self, expression: exp.Pivot) -> str: 2458 expressions = self.expressions(expression, flat=True) 2459 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2460 2461 group = self.sql(expression, "group") 2462 2463 if expression.this: 2464 this = self.sql(expression, "this") 2465 if not expressions: 2466 sql = f"UNPIVOT {this}" 2467 else: 2468 on = f"{self.seg('ON')} {expressions}" 2469 into = self.sql(expression, "into") 2470 into = f"{self.seg('INTO')} {into}" if into else "" 2471 using = self.expressions(expression, key="using", flat=True) 2472 using = f"{self.seg('USING')} {using}" if using else "" 2473 sql = f"{direction} {this}{on}{into}{using}{group}" 2474 return self.prepend_ctes(expression, sql) 2475 2476 alias = self.sql(expression, "alias") 2477 alias = f" AS {alias}" if alias else "" 2478 2479 fields = self.expressions( 2480 expression, 2481 "fields", 2482 sep=" ", 2483 dynamic=True, 2484 new_line=True, 2485 skip_first=True, 2486 skip_last=True, 2487 ) 2488 2489 include_nulls = expression.args.get("include_nulls") 2490 if include_nulls is not None: 2491 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2492 else: 2493 nulls = "" 2494 2495 default_on_null = self.sql(expression, "default_on_null") 2496 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2497 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2498 return self.prepend_ctes(expression, sql) 2499 2500 def version_sql(self, expression: exp.Version) -> str: 2501 this = f"FOR {expression.name}" 2502 kind = expression.text("kind") 2503 expr = self.sql(expression, "expression") 2504 return f"{this} {kind} {expr}" 2505 2506 def tuple_sql(self, expression: exp.Tuple) -> str: 2507 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2508 2509 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2510 """ 2511 Returns (join_sql, from_sql) for UPDATE statements. 2512 - join_sql: placed after UPDATE table, before SET 2513 - from_sql: placed after SET clause (standard position) 2514 Dialects like MySQL need to convert FROM to JOIN syntax. 2515 """ 2516 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2517 return ("", self.sql(expression, "from_")) 2518 2519 # Qualify unqualified columns in SET clause with the target table 2520 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2521 target_table = expression.this 2522 if isinstance(target_table, exp.Table): 2523 target_name = exp.to_identifier(target_table.alias_or_name) 2524 for eq in expression.expressions: 2525 col = eq.this 2526 if isinstance(col, exp.Column) and not col.table: 2527 col.set("table", target_name) 2528 2529 table = from_expr.this 2530 if nested_joins := table.args.get("joins", []): 2531 table.set("joins", None) 2532 2533 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2534 for nested in nested_joins: 2535 if not nested.args.get("on") and not nested.args.get("using"): 2536 nested.set("on", exp.true()) 2537 join_sql += self.sql(nested) 2538 2539 return (join_sql, "") 2540 2541 def update_sql(self, expression: exp.Update) -> str: 2542 hint = self.sql(expression, "hint") 2543 this = self.sql(expression, "this") 2544 join_sql, from_sql = self._update_from_joins_sql(expression) 2545 set_sql = self.expressions(expression, flat=True) 2546 where_sql = self.sql(expression, "where") 2547 returning = self.sql(expression, "returning") 2548 order = self.sql(expression, "order") 2549 limit = self.sql(expression, "limit") 2550 if self.RETURNING_END: 2551 expression_sql = f"{from_sql}{where_sql}{returning}" 2552 else: 2553 expression_sql = f"{returning}{from_sql}{where_sql}" 2554 options = self.expressions(expression, key="options") 2555 options = f" OPTION({options})" if options else "" 2556 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2557 return self.prepend_ctes(expression, sql) 2558 2559 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2560 values_as_table = values_as_table and self.VALUES_AS_TABLE 2561 2562 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2563 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2564 args = self.expressions(expression) 2565 alias = self.sql(expression, "alias") 2566 values = f"VALUES{self.seg('')}{args}" 2567 values = ( 2568 f"({values})" 2569 if self.WRAP_DERIVED_VALUES 2570 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2571 else values 2572 ) 2573 values = self.query_modifiers(expression, values) 2574 return f"{values} AS {alias}" if alias else values 2575 2576 # Converts `VALUES...` expression into a series of select unions. 2577 alias_node = expression.args.get("alias") 2578 column_names = alias_node and alias_node.columns 2579 2580 selects: list[exp.Query] = [] 2581 2582 for i, tup in enumerate(expression.expressions): 2583 row = tup.expressions 2584 2585 if i == 0 and column_names: 2586 row = [ 2587 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2588 ] 2589 2590 selects.append(exp.Select(expressions=row)) 2591 2592 if self.pretty: 2593 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2594 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2595 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2596 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2597 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2598 2599 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2600 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2601 return f"({unions}){alias}" 2602 2603 def var_sql(self, expression: exp.Var) -> str: 2604 return self.sql(expression, "this") 2605 2606 @unsupported_args("expressions") 2607 def into_sql(self, expression: exp.Into) -> str: 2608 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2609 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2610 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2611 2612 def from_sql(self, expression: exp.From) -> str: 2613 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2614 2615 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2616 grouping_sets = self.expressions(expression, indent=False) 2617 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2618 2619 def rollup_sql(self, expression: exp.Rollup) -> str: 2620 expressions = self.expressions(expression, indent=False) 2621 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2622 2623 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2624 this = self.sql(expression, "this") 2625 2626 columns = self.expressions(expression, flat=True) 2627 2628 from_sql = self.sql(expression, "from_index") 2629 from_sql = f" FROM {from_sql}" if from_sql else "" 2630 2631 properties = expression.args.get("properties") 2632 properties_sql = ( 2633 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2634 ) 2635 2636 return f"{this}({columns}){from_sql}{properties_sql}" 2637 2638 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2639 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2640 2641 def cube_sql(self, expression: exp.Cube) -> str: 2642 expressions = self.expressions(expression, indent=False) 2643 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2644 2645 def group_sql(self, expression: exp.Group) -> str: 2646 group_by_all = expression.args.get("all") 2647 if group_by_all is True: 2648 modifier = " ALL" 2649 elif group_by_all is False: 2650 modifier = " DISTINCT" 2651 else: 2652 modifier = "" 2653 2654 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2655 2656 grouping_sets = self.expressions(expression, key="grouping_sets") 2657 cube = self.expressions(expression, key="cube") 2658 rollup = self.expressions(expression, key="rollup") 2659 2660 groupings = csv( 2661 self.seg(grouping_sets) if grouping_sets else "", 2662 self.seg(cube) if cube else "", 2663 self.seg(rollup) if rollup else "", 2664 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2665 sep=self.GROUPINGS_SEP, 2666 ) 2667 2668 if ( 2669 expression.expressions 2670 and groupings 2671 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2672 ): 2673 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2674 2675 return f"{group_by}{groupings}" 2676 2677 def having_sql(self, expression: exp.Having) -> str: 2678 this = self.indent(self.sql(expression, "this")) 2679 return f"{self.seg('HAVING')}{self.sep()}{this}" 2680 2681 def connect_sql(self, expression: exp.Connect) -> str: 2682 start = self.sql(expression, "start") 2683 start = self.seg(f"START WITH {start}") if start else "" 2684 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2685 connect = self.sql(expression, "connect") 2686 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2687 return start + connect 2688 2689 def prior_sql(self, expression: exp.Prior) -> str: 2690 return f"PRIOR {self.sql(expression, 'this')}" 2691 2692 def join_sql(self, expression: exp.Join) -> str: 2693 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2694 side = None 2695 else: 2696 side = expression.side 2697 2698 op_sql = " ".join( 2699 op 2700 for op in ( 2701 expression.method, 2702 "GLOBAL" if expression.args.get("global_") else None, 2703 side, 2704 expression.kind, 2705 expression.hint if self.JOIN_HINTS else None, 2706 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2707 ) 2708 if op 2709 ) 2710 match_cond = self.sql(expression, "match_condition") 2711 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2712 on_sql = self.sql(expression, "on") 2713 using = expression.args.get("using") 2714 2715 if not on_sql and using: 2716 on_sql = csv(*(self.sql(column) for column in using)) 2717 2718 this = expression.this 2719 this_sql = self.sql(this) 2720 2721 exprs = self.expressions(expression) 2722 if exprs: 2723 this_sql = f"{this_sql},{self.seg(exprs)}" 2724 2725 if on_sql: 2726 on_sql = self.indent(on_sql, skip_first=True) 2727 space = self.seg(" " * self.pad) if self.pretty else " " 2728 if using: 2729 on_sql = f"{space}USING ({on_sql})" 2730 else: 2731 on_sql = f"{space}ON {on_sql}" 2732 elif not op_sql: 2733 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2734 return f" {this_sql}" 2735 2736 return f", {this_sql}" 2737 2738 if op_sql != "STRAIGHT_JOIN": 2739 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2740 2741 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2742 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2743 2744 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2745 args = self.expressions(expression, flat=True) 2746 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2747 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2748 2749 def lateral_op(self, expression: exp.Lateral) -> str: 2750 cross_apply = expression.args.get("cross_apply") 2751 2752 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2753 if cross_apply is True: 2754 op = "INNER JOIN " 2755 elif cross_apply is False: 2756 op = "LEFT JOIN " 2757 else: 2758 op = "" 2759 2760 return f"{op}LATERAL" 2761 2762 def lateral_sql(self, expression: exp.Lateral) -> str: 2763 this = self.sql(expression, "this") 2764 2765 if expression.args.get("view"): 2766 alias = expression.args["alias"] 2767 columns = self.expressions(alias, key="columns", flat=True) 2768 table = f" {alias.name}" if alias.name else "" 2769 columns = f" AS {columns}" if columns else "" 2770 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2771 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2772 2773 alias = self.sql(expression, "alias") 2774 alias = f" AS {alias}" if alias else "" 2775 2776 ordinality = expression.args.get("ordinality") or "" 2777 if ordinality: 2778 ordinality = f" WITH ORDINALITY{alias}" 2779 alias = "" 2780 2781 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2782 2783 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2784 this = self.sql(expression, "this") 2785 2786 args = [ 2787 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2788 for e in (expression.args.get(k) for k in ("offset", "expression")) 2789 if e 2790 ] 2791 2792 args_sql = ", ".join(self.sql(e) for e in args) 2793 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2794 expressions = self.expressions(expression, flat=True) 2795 limit_options = self.sql(expression, "limit_options") 2796 expressions = f" BY {expressions}" if expressions else "" 2797 2798 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2799 2800 def offset_sql(self, expression: exp.Offset) -> str: 2801 this = self.sql(expression, "this") 2802 value = expression.expression 2803 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2804 expressions = self.expressions(expression, flat=True) 2805 expressions = f" BY {expressions}" if expressions else "" 2806 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2807 2808 def setitem_sql(self, expression: exp.SetItem) -> str: 2809 kind = self.sql(expression, "kind") 2810 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2811 kind = "" 2812 else: 2813 kind = f"{kind} " if kind else "" 2814 this = self.sql(expression, "this") 2815 expressions = self.expressions(expression) 2816 collate = self.sql(expression, "collate") 2817 collate = f" COLLATE {collate}" if collate else "" 2818 global_ = "GLOBAL " if expression.args.get("global_") else "" 2819 return f"{global_}{kind}{this}{expressions}{collate}" 2820 2821 def set_sql(self, expression: exp.Set) -> str: 2822 expressions = f" {self.expressions(expression, flat=True)}" 2823 tag = " TAG" if expression.args.get("tag") else "" 2824 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2825 2826 def queryband_sql(self, expression: exp.QueryBand) -> str: 2827 this = self.sql(expression, "this") 2828 update = " UPDATE" if expression.args.get("update") else "" 2829 scope = self.sql(expression, "scope") 2830 scope = f" FOR {scope}" if scope else "" 2831 2832 return f"QUERY_BAND = {this}{update}{scope}" 2833 2834 def pragma_sql(self, expression: exp.Pragma) -> str: 2835 return f"PRAGMA {self.sql(expression, 'this')}" 2836 2837 def lock_sql(self, expression: exp.Lock) -> str: 2838 if not self.LOCKING_READS_SUPPORTED: 2839 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2840 return "" 2841 2842 update = expression.args["update"] 2843 key = expression.args.get("key") 2844 if update: 2845 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2846 else: 2847 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2848 expressions = self.expressions(expression, flat=True) 2849 expressions = f" OF {expressions}" if expressions else "" 2850 wait = expression.args.get("wait") 2851 2852 if wait is not None: 2853 if isinstance(wait, exp.Literal): 2854 wait = f" WAIT {self.sql(wait)}" 2855 else: 2856 wait = " NOWAIT" if wait else " SKIP LOCKED" 2857 2858 return f"{lock_type}{expressions}{wait or ''}" 2859 2860 def literal_sql(self, expression: exp.Literal) -> str: 2861 text = expression.this or "" 2862 if expression.is_string: 2863 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2864 return text 2865 2866 def escape_str( 2867 self, 2868 text: str, 2869 escape_backslash: bool = True, 2870 delimiter: str | None = None, 2871 escaped_delimiter: str | None = None, 2872 is_byte_string: bool = False, 2873 ) -> str: 2874 if is_byte_string: 2875 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2876 else: 2877 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2878 2879 if supports_escape_sequences: 2880 text = "".join( 2881 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2882 for ch in text 2883 ) 2884 2885 delimiter = delimiter or self.dialect.QUOTE_END 2886 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2887 2888 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2889 2890 def loaddata_sql(self, expression: exp.LoadData) -> str: 2891 is_overwrite = expression.args.get("overwrite") 2892 overwrite = " OVERWRITE" if is_overwrite else "" 2893 this = self.sql(expression, "this") 2894 2895 files = expression.args.get("files") 2896 if files: 2897 files_sql = self.expressions(files, flat=True) 2898 files_sql = f"FILES{self.wrap(files_sql)}" 2899 this = f" {this}" if is_overwrite else f" INTO TABLE {this}" 2900 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2901 2902 local = " LOCAL" if expression.args.get("local") else "" 2903 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2904 this = f" INTO TABLE {this}" 2905 partition = self.sql(expression, "partition") 2906 partition = f" {partition}" if partition else "" 2907 input_format = self.sql(expression, "input_format") 2908 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2909 serde = self.sql(expression, "serde") 2910 serde = f" SERDE {serde}" if serde else "" 2911 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2912 2913 def null_sql(self, *_) -> str: 2914 return "NULL" 2915 2916 def boolean_sql(self, expression: exp.Boolean) -> str: 2917 return "TRUE" if expression.this else "FALSE" 2918 2919 def booland_sql(self, expression: exp.Booland) -> str: 2920 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2921 2922 def boolor_sql(self, expression: exp.Boolor) -> str: 2923 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2924 2925 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2926 this = self.sql(expression, "this") 2927 this = f"{this} " if this else this 2928 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2929 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 2930 2931 def withfill_sql(self, expression: exp.WithFill) -> str: 2932 from_sql = self.sql(expression, "from_") 2933 from_sql = f" FROM {from_sql}" if from_sql else "" 2934 to_sql = self.sql(expression, "to") 2935 to_sql = f" TO {to_sql}" if to_sql else "" 2936 step_sql = self.sql(expression, "step") 2937 step_sql = f" STEP {step_sql}" if step_sql else "" 2938 interpolated_values = [ 2939 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2940 if isinstance(e, exp.Alias) 2941 else self.sql(e, "this") 2942 for e in expression.args.get("interpolate") or [] 2943 ] 2944 interpolate = ( 2945 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2946 ) 2947 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2948 2949 def cluster_sql(self, expression: exp.Cluster) -> str: 2950 return self.op_expressions("CLUSTER BY", expression) 2951 2952 def distribute_sql(self, expression: exp.Distribute) -> str: 2953 return self.op_expressions("DISTRIBUTE BY", expression) 2954 2955 def sort_sql(self, expression: exp.Sort) -> str: 2956 return self.op_expressions("SORT BY", expression) 2957 2958 def ordered_sql(self, expression: exp.Ordered) -> str: 2959 desc = expression.args.get("desc") 2960 asc = not desc 2961 2962 nulls_first = expression.args.get("nulls_first") 2963 nulls_last = not nulls_first 2964 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2965 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2966 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2967 2968 this = self.sql(expression, "this") 2969 2970 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2971 nulls_sort_change = "" 2972 if nulls_first and ( 2973 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2974 ): 2975 nulls_sort_change = " NULLS FIRST" 2976 elif ( 2977 nulls_last 2978 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2979 and not nulls_are_last 2980 ): 2981 nulls_sort_change = " NULLS LAST" 2982 2983 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2984 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2985 window = expression.find_ancestor(exp.Window, exp.Select) 2986 2987 if isinstance(window, exp.Window): 2988 window_this = window.this 2989 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 2990 window_this = window_this.this 2991 spec = window.args.get("spec") 2992 else: 2993 window_this = None 2994 spec = None 2995 2996 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 2997 # without a spec or with a ROWS spec, but not with RANGE 2998 if not ( 2999 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3000 and (not spec or spec.text("kind").upper() == "ROWS") 3001 ): 3002 if window_this and spec: 3003 self.unsupported( 3004 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3005 ) 3006 nulls_sort_change = "" 3007 elif self.NULL_ORDERING_SUPPORTED is False and ( 3008 (asc and nulls_sort_change == " NULLS LAST") 3009 or (desc and nulls_sort_change == " NULLS FIRST") 3010 ): 3011 # BigQuery does not allow these ordering/nulls combinations when used under 3012 # an aggregation func or under a window containing one 3013 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3014 3015 if isinstance(ancestor, exp.Window): 3016 ancestor = ancestor.this 3017 if isinstance(ancestor, exp.AggFunc): 3018 self.unsupported( 3019 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3020 ) 3021 nulls_sort_change = "" 3022 elif self.NULL_ORDERING_SUPPORTED is None: 3023 if expression.this.is_int: 3024 self.unsupported( 3025 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3026 ) 3027 elif not isinstance(expression.this, exp.Rand): 3028 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3029 this = ( 3030 f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 3031 ) 3032 nulls_sort_change = "" 3033 3034 with_fill = self.sql(expression, "with_fill") 3035 with_fill = f" {with_fill}" if with_fill else "" 3036 3037 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3038 3039 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3040 window_frame = self.sql(expression, "window_frame") 3041 window_frame = f"{window_frame} " if window_frame else "" 3042 3043 this = self.sql(expression, "this") 3044 3045 return f"{window_frame}{this}" 3046 3047 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3048 partition = self.partition_by_sql(expression) 3049 order = self.sql(expression, "order") 3050 measures = self.expressions(expression, key="measures") 3051 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3052 rows = self.sql(expression, "rows") 3053 rows = self.seg(rows) if rows else "" 3054 after = self.sql(expression, "after") 3055 after = self.seg(after) if after else "" 3056 pattern = self.sql(expression, "pattern") 3057 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3058 definition_sqls = [ 3059 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3060 for definition in expression.args.get("define", []) 3061 ] 3062 definitions = self.expressions(sqls=definition_sqls) 3063 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3064 body = "".join( 3065 ( 3066 partition, 3067 order, 3068 measures, 3069 rows, 3070 after, 3071 pattern, 3072 define, 3073 ) 3074 ) 3075 alias = self.sql(expression, "alias") 3076 alias = f" {alias}" if alias else "" 3077 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3078 3079 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3080 limit = expression.args.get("limit") 3081 3082 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3083 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3084 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3085 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3086 3087 return csv( 3088 *sqls, 3089 *[self.sql(join) for join in expression.args.get("joins") or []], 3090 self.sql(expression, "match"), 3091 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3092 self.sql(expression, "prewhere"), 3093 self.sql(expression, "where"), 3094 self.sql(expression, "connect"), 3095 self.sql(expression, "group"), 3096 self.sql(expression, "having"), 3097 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3098 self.sql(expression, "order"), 3099 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3100 *self.after_limit_modifiers(expression), 3101 self.options_modifier(expression), 3102 self.for_modifiers(expression), 3103 sep="", 3104 ) 3105 3106 def options_modifier(self, expression: exp.Expr) -> str: 3107 options = self.expressions(expression, key="options") 3108 return f" {options}" if options else "" 3109 3110 def for_modifiers(self, expression: exp.Expr) -> str: 3111 for_modifiers = self.expressions(expression, key="for_") 3112 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 3113 3114 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3115 self.unsupported("Unsupported query option.") 3116 return "" 3117 3118 def offset_limit_modifiers( 3119 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3120 ) -> list[str]: 3121 return [ 3122 self.sql(expression, "offset") if fetch else self.sql(limit), 3123 self.sql(limit) if fetch else self.sql(expression, "offset"), 3124 ] 3125 3126 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3127 locks = self.expressions(expression, key="locks", sep=" ") 3128 locks = f" {locks}" if locks else "" 3129 return [locks, self.sql(expression, "sample")] 3130 3131 def select_sql(self, expression: exp.Select) -> str: 3132 into = expression.args.get("into") 3133 if not self.SUPPORTS_SELECT_INTO and into: 3134 into.pop() 3135 3136 hint = self.sql(expression, "hint") 3137 distinct = self.sql(expression, "distinct") 3138 distinct = f" {distinct}" if distinct else "" 3139 kind = self.sql(expression, "kind") 3140 3141 limit = expression.args.get("limit") 3142 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3143 top = self.limit_sql(limit, top=True) 3144 limit.pop() 3145 else: 3146 top = "" 3147 3148 expressions = self.expressions(expression) 3149 3150 if kind: 3151 if kind in self.SELECT_KINDS: 3152 kind = f" AS {kind}" 3153 else: 3154 if kind == "STRUCT": 3155 expressions = self.expressions( 3156 sqls=[ 3157 self.sql( 3158 exp.Struct( 3159 expressions=[ 3160 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3161 if isinstance(e, exp.Alias) 3162 else e 3163 for e in expression.expressions 3164 ] 3165 ) 3166 ) 3167 ] 3168 ) 3169 kind = "" 3170 3171 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3172 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3173 3174 exclude = expression.args.get("exclude") 3175 3176 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3177 exclude_sql = self.expressions(sqls=exclude, flat=True) 3178 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3179 3180 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3181 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3182 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3183 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3184 sql = self.query_modifiers( 3185 expression, 3186 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3187 self.sql(expression, "into", comment=False), 3188 self.sql(expression, "from_", comment=False), 3189 ) 3190 3191 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3192 if expression.args.get("with_"): 3193 sql = self.maybe_comment(sql, expression) 3194 expression.pop_comments() 3195 3196 sql = self.prepend_ctes(expression, sql) 3197 3198 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3199 expression.set("exclude", None) 3200 subquery = expression.subquery(copy=False) 3201 star = exp.Star(except_=exclude) 3202 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3203 3204 if not self.SUPPORTS_SELECT_INTO and into: 3205 if into.args.get("temporary"): 3206 table_kind = " TEMPORARY" 3207 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3208 table_kind = " UNLOGGED" 3209 else: 3210 table_kind = "" 3211 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3212 3213 return sql 3214 3215 def schema_sql(self, expression: exp.Schema) -> str: 3216 this = self.sql(expression, "this") 3217 sql = self.schema_columns_sql(expression) 3218 return f"{this} {sql}" if this and sql else this or sql 3219 3220 def schema_columns_sql(self, expression: exp.Expr) -> str: 3221 if expression.expressions: 3222 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3223 return "" 3224 3225 def star_sql(self, expression: exp.Star) -> str: 3226 except_ = self.expressions(expression, key="except_", flat=True) 3227 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3228 replace = self.expressions(expression, key="replace", flat=True) 3229 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3230 rename = self.expressions(expression, key="rename", flat=True) 3231 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3232 return f"*{except_}{replace}{rename}" 3233 3234 def parameter_sql(self, expression: exp.Parameter) -> str: 3235 this = self.sql(expression, "this") 3236 return f"{self.PARAMETER_TOKEN}{this}" 3237 3238 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3239 this = self.sql(expression, "this") 3240 kind = expression.text("kind") 3241 if kind: 3242 kind = f"{kind}." 3243 return f"@@{kind}{this}" 3244 3245 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3246 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3247 3248 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3249 alias = self.sql(expression, "alias") 3250 alias = f"{sep}{alias}" if alias else "" 3251 sample = self.sql(expression, "sample") 3252 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3253 alias = f"{sample}{alias}" 3254 3255 # Set to None so it's not generated again by self.query_modifiers() 3256 expression.set("sample", None) 3257 3258 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3259 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3260 return self.prepend_ctes(expression, sql) 3261 3262 def qualify_sql(self, expression: exp.Qualify) -> str: 3263 this = self.indent(self.sql(expression, "this")) 3264 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3265 3266 def unnest_sql(self, expression: exp.Unnest) -> str: 3267 args = self.expressions(expression, flat=True) 3268 3269 alias = expression.args.get("alias") 3270 offset = expression.args.get("offset") 3271 3272 if self.UNNEST_WITH_ORDINALITY: 3273 if alias and isinstance(offset, exp.Expr): 3274 alias.append("columns", offset) 3275 expression.set("offset", None) 3276 3277 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3278 columns = alias.columns 3279 alias = self.sql(columns[0]) if columns else "" 3280 else: 3281 alias = self.sql(alias) 3282 3283 alias = f" AS {alias}" if alias else alias 3284 if self.UNNEST_WITH_ORDINALITY: 3285 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3286 else: 3287 if isinstance(offset, exp.Expr): 3288 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3289 elif offset: 3290 suffix = f"{alias} WITH OFFSET" 3291 else: 3292 suffix = alias 3293 3294 return f"UNNEST({args}){suffix}" 3295 3296 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3297 return "" 3298 3299 def where_sql(self, expression: exp.Where) -> str: 3300 this = self.indent(self.sql(expression, "this")) 3301 return f"{self.seg('WHERE')}{self.sep()}{this}" 3302 3303 def window_sql(self, expression: exp.Window) -> str: 3304 this = self.sql(expression, "this") 3305 partition = self.partition_by_sql(expression) 3306 order = expression.args.get("order") 3307 order = self.order_sql(order, flat=True) if order else "" 3308 spec = self.sql(expression, "spec") 3309 alias = self.sql(expression, "alias") 3310 over = self.sql(expression, "over") or "OVER" 3311 3312 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3313 3314 first = expression.args.get("first") 3315 if first is None: 3316 first = "" 3317 else: 3318 first = "FIRST" if first else "LAST" 3319 3320 if not partition and not order and not spec and alias: 3321 return f"{this} {alias}" 3322 3323 args = self.format_args( 3324 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3325 ) 3326 return f"{this} ({args})" 3327 3328 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3329 partition = self.expressions(expression, key="partition_by", flat=True) 3330 return f"PARTITION BY {partition}" if partition else "" 3331 3332 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3333 kind = self.sql(expression, "kind") 3334 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3335 end = ( 3336 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3337 or "CURRENT ROW" 3338 ) 3339 3340 window_spec = f"{kind} BETWEEN {start} AND {end}" 3341 3342 exclude = self.sql(expression, "exclude") 3343 if exclude: 3344 if self.SUPPORTS_WINDOW_EXCLUDE: 3345 window_spec += f" EXCLUDE {exclude}" 3346 else: 3347 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3348 3349 return window_spec 3350 3351 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3352 this = self.sql(expression, "this") 3353 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3354 return f"{this} WITHIN GROUP ({expression_sql})" 3355 3356 def between_sql(self, expression: exp.Between) -> str: 3357 this = self.sql(expression, "this") 3358 low = self.sql(expression, "low") 3359 high = self.sql(expression, "high") 3360 symmetric = expression.args.get("symmetric") 3361 3362 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3363 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3364 3365 flag = ( 3366 " SYMMETRIC" 3367 if symmetric 3368 else " ASYMMETRIC" 3369 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3370 else "" # silently drop ASYMMETRIC – semantics identical 3371 ) 3372 return f"{this} BETWEEN{flag} {low} AND {high}" 3373 3374 def bracket_offset_expressions( 3375 self, expression: exp.Bracket, index_offset: int | None = None 3376 ) -> list[exp.Expr]: 3377 if expression.args.get("json_access"): 3378 return expression.expressions 3379 3380 return apply_index_offset( 3381 expression.this, 3382 expression.expressions, 3383 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3384 dialect=self.dialect, 3385 ) 3386 3387 def bracket_sql(self, expression: exp.Bracket) -> str: 3388 expressions = self.bracket_offset_expressions(expression) 3389 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3390 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3391 3392 def all_sql(self, expression: exp.All) -> str: 3393 this = self.sql(expression, "this") 3394 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3395 this = self.wrap(this) 3396 return f"ALL {this}" 3397 3398 def any_sql(self, expression: exp.Any) -> str: 3399 this = self.sql(expression, "this") 3400 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3401 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3402 this = self.wrap(this) 3403 return f"ANY{this}" 3404 return f"ANY {this}" 3405 3406 def exists_sql(self, expression: exp.Exists) -> str: 3407 return f"EXISTS{self.wrap(expression)}" 3408 3409 def case_sql(self, expression: exp.Case) -> str: 3410 this = self.sql(expression, "this") 3411 statements = [f"CASE {this}" if this else "CASE"] 3412 3413 for e in expression.args["ifs"]: 3414 statements.append(f"WHEN {self.sql(e, 'this')}") 3415 statements.append(f"THEN {self.sql(e, 'true')}") 3416 3417 default = self.sql(expression, "default") 3418 3419 if default: 3420 statements.append(f"ELSE {default}") 3421 3422 statements.append("END") 3423 3424 if self.pretty and self.too_wide(statements): 3425 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3426 3427 return " ".join(statements) 3428 3429 def constraint_sql(self, expression: exp.Constraint) -> str: 3430 this = self.sql(expression, "this") 3431 expressions = self.expressions(expression, flat=True) 3432 return f"CONSTRAINT {this} {expressions}" 3433 3434 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3435 order = expression.args.get("order") 3436 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3437 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3438 3439 def extract_sql(self, expression: exp.Extract) -> str: 3440 import sqlglot.dialects.dialect 3441 3442 this = ( 3443 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3444 if self.NORMALIZE_EXTRACT_DATE_PARTS 3445 else expression.this 3446 ) 3447 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3448 expression_sql = self.sql(expression, "expression") 3449 3450 return f"EXTRACT({this_sql} FROM {expression_sql})" 3451 3452 def trim_sql(self, expression: exp.Trim) -> str: 3453 trim_type = self.sql(expression, "position") 3454 3455 if trim_type == "LEADING": 3456 func_name = "LTRIM" 3457 elif trim_type == "TRAILING": 3458 func_name = "RTRIM" 3459 else: 3460 func_name = "TRIM" 3461 3462 return self.func(func_name, expression.this, expression.expression) 3463 3464 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3465 args = expression.expressions 3466 if isinstance(expression, exp.ConcatWs): 3467 args = args[1:] # Skip the delimiter 3468 3469 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3470 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3471 3472 concat_coalesce = ( 3473 self.dialect.CONCAT_WS_COALESCE 3474 if isinstance(expression, exp.ConcatWs) 3475 else self.dialect.CONCAT_COALESCE 3476 ) 3477 3478 if not concat_coalesce and expression.args.get("coalesce"): 3479 3480 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3481 if not e.type: 3482 import sqlglot.optimizer.annotate_types 3483 3484 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3485 3486 if e.is_string or e.is_type(exp.DType.ARRAY): 3487 return e 3488 3489 return exp.func("coalesce", e, exp.Literal.string("")) 3490 3491 args = [_wrap_with_coalesce(e) for e in args] 3492 3493 return args 3494 3495 def concat_sql(self, expression: exp.Concat) -> str: 3496 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3497 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3498 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3499 # instead of coalescing them to empty string. 3500 import sqlglot.dialects.dialect 3501 3502 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3503 3504 expressions = self.convert_concat_args(expression) 3505 3506 # Some dialects don't allow a single-argument CONCAT call 3507 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3508 return self.sql(expressions[0]) 3509 3510 return self.func("CONCAT", *expressions) 3511 3512 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3513 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3514 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3515 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3516 all_args = expression.expressions 3517 expression.set("coalesce", True) 3518 return self.sql( 3519 exp.case() 3520 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3521 .else_(expression) 3522 ) 3523 3524 return self.func( 3525 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3526 ) 3527 3528 def check_sql(self, expression: exp.Check) -> str: 3529 this = self.sql(expression, key="this") 3530 return f"CHECK ({this})" 3531 3532 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3533 expressions = self.expressions(expression, flat=True) 3534 expressions = f" ({expressions})" if expressions else "" 3535 reference = self.sql(expression, "reference") 3536 reference = f" {reference}" if reference else "" 3537 delete = self.sql(expression, "delete") 3538 delete = f" ON DELETE {delete}" if delete else "" 3539 update = self.sql(expression, "update") 3540 update = f" ON UPDATE {update}" if update else "" 3541 options = self.expressions(expression, key="options", flat=True, sep=" ") 3542 options = f" {options}" if options else "" 3543 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3544 3545 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3546 this = self.sql(expression, "this") 3547 this = f" {this}" if this else "" 3548 expressions = self.expressions(expression, flat=True) 3549 include = self.sql(expression, "include") 3550 options = self.expressions(expression, key="options", flat=True, sep=" ") 3551 options = f" {options}" if options else "" 3552 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3553 3554 def if_sql(self, expression: exp.If) -> str: 3555 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3556 3557 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3558 if self.MATCH_AGAINST_TABLE_PREFIX: 3559 expressions = [] 3560 for expr in expression.expressions: 3561 if isinstance(expr, exp.Table): 3562 expressions.append(f"TABLE {self.sql(expr)}") 3563 else: 3564 expressions.append(expr) 3565 else: 3566 expressions = expression.expressions 3567 3568 modifier = expression.args.get("modifier") 3569 modifier = f" {modifier}" if modifier else "" 3570 return ( 3571 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3572 ) 3573 3574 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3575 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3576 3577 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3578 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3579 3580 if expression.args.get("escape"): 3581 path = self.escape_str(path) 3582 3583 if self.QUOTE_JSON_PATH: 3584 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3585 3586 return path 3587 3588 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3589 if isinstance(expression, exp.JSONPathPart): 3590 transform = self.TRANSFORMS.get(expression.__class__) 3591 if not callable(transform): 3592 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3593 return "" 3594 3595 return transform(self, expression) 3596 3597 if isinstance(expression, int): 3598 return str(expression) 3599 3600 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3601 escaped = expression.replace("'", "\\'") 3602 escaped = f"\\'{expression}\\'" 3603 else: 3604 escaped = expression.replace('"', '\\"') 3605 escaped = f'"{escaped}"' 3606 3607 return escaped 3608 3609 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3610 return f"{self.sql(expression, 'this')} FORMAT JSON" 3611 3612 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3613 # Output the Teradata column FORMAT override. 3614 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3615 this = self.sql(expression, "this") 3616 fmt = self.sql(expression, "format") 3617 return f"{this} (FORMAT {fmt})" 3618 3619 def _jsonobject_sql( 3620 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3621 ) -> str: 3622 null_handling = expression.args.get("null_handling") 3623 null_handling = f" {null_handling}" if null_handling else "" 3624 3625 unique_keys = expression.args.get("unique_keys") 3626 if unique_keys is not None: 3627 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3628 else: 3629 unique_keys = "" 3630 3631 return_type = self.sql(expression, "return_type") 3632 return_type = f" RETURNING {return_type}" if return_type else "" 3633 encoding = self.sql(expression, "encoding") 3634 encoding = f" ENCODING {encoding}" if encoding else "" 3635 3636 if not name: 3637 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3638 3639 return self.func( 3640 name, 3641 *expression.expressions, 3642 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3643 ) 3644 3645 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3646 null_handling = expression.args.get("null_handling") 3647 null_handling = f" {null_handling}" if null_handling else "" 3648 return_type = self.sql(expression, "return_type") 3649 return_type = f" RETURNING {return_type}" if return_type else "" 3650 strict = " STRICT" if expression.args.get("strict") else "" 3651 return self.func( 3652 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3653 ) 3654 3655 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3656 this = self.sql(expression, "this") 3657 order = self.sql(expression, "order") 3658 null_handling = expression.args.get("null_handling") 3659 null_handling = f" {null_handling}" if null_handling else "" 3660 return_type = self.sql(expression, "return_type") 3661 return_type = f" RETURNING {return_type}" if return_type else "" 3662 strict = " STRICT" if expression.args.get("strict") else "" 3663 return self.func( 3664 "JSON_ARRAYAGG", 3665 this, 3666 suffix=f"{order}{null_handling}{return_type}{strict})", 3667 ) 3668 3669 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3670 path = self.sql(expression, "path") 3671 path = f" PATH {path}" if path else "" 3672 nested_schema = self.sql(expression, "nested_schema") 3673 3674 if nested_schema: 3675 return f"NESTED{path} {nested_schema}" 3676 3677 this = self.sql(expression, "this") 3678 kind = self.sql(expression, "kind") 3679 kind = f" {kind}" if kind else "" 3680 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3681 3682 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3683 return f"{this}{kind}{format_json}{path}{ordinality}" 3684 3685 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3686 return self.func("COLUMNS", *expression.expressions) 3687 3688 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3689 this = self.sql(expression, "this") 3690 path = self.sql(expression, "path") 3691 path = f", {path}" if path else "" 3692 error_handling = expression.args.get("error_handling") 3693 error_handling = f" {error_handling}" if error_handling else "" 3694 empty_handling = expression.args.get("empty_handling") 3695 empty_handling = f" {empty_handling}" if empty_handling else "" 3696 schema = self.sql(expression, "schema") 3697 return self.func( 3698 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3699 ) 3700 3701 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3702 this = self.sql(expression, "this") 3703 kind = self.sql(expression, "kind") 3704 path = self.sql(expression, "path") 3705 path = f" {path}" if path else "" 3706 as_json = " AS JSON" if expression.args.get("as_json") else "" 3707 return f"{this} {kind}{path}{as_json}" 3708 3709 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3710 this = self.sql(expression, "this") 3711 path = self.sql(expression, "path") 3712 path = f", {path}" if path else "" 3713 expressions = self.expressions(expression) 3714 with_ = ( 3715 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3716 if expressions 3717 else "" 3718 ) 3719 return f"OPENJSON({this}{path}){with_}" 3720 3721 def in_sql(self, expression: exp.In) -> str: 3722 query = expression.args.get("query") 3723 unnest = expression.args.get("unnest") 3724 field = expression.args.get("field") 3725 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3726 3727 if query: 3728 in_sql = self.sql(query) 3729 elif unnest: 3730 in_sql = self.in_unnest_op(unnest) 3731 elif field: 3732 in_sql = self.sql(field) 3733 else: 3734 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3735 3736 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3737 3738 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3739 return f"(SELECT {self.sql(unnest)})" 3740 3741 def interval_sql(self, expression: exp.Interval) -> str: 3742 unit_expression = expression.args.get("unit") 3743 unit = self.sql(unit_expression) if unit_expression else "" 3744 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3745 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3746 unit = f" {unit}" if unit else "" 3747 3748 if self.SINGLE_STRING_INTERVAL: 3749 this = expression.this.name if expression.this else "" 3750 if this: 3751 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3752 return f"INTERVAL '{this}'{unit}" 3753 return f"INTERVAL '{this}{unit}'" 3754 return f"INTERVAL{unit}" 3755 3756 this = self.sql(expression, "this") 3757 if this: 3758 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3759 this = f" {this}" if unwrapped else f" ({this})" 3760 3761 return f"INTERVAL{this}{unit}" 3762 3763 def return_sql(self, expression: exp.Return) -> str: 3764 return f"RETURN {self.sql(expression, 'this')}" 3765 3766 def reference_sql(self, expression: exp.Reference) -> str: 3767 this = self.sql(expression, "this") 3768 expressions = self.expressions(expression, flat=True) 3769 expressions = f"({expressions})" if expressions else "" 3770 options = self.expressions(expression, key="options", flat=True, sep=" ") 3771 options = f" {options}" if options else "" 3772 return f"REFERENCES {this}{expressions}{options}" 3773 3774 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3775 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3776 parent = expression.parent 3777 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3778 3779 return self.func( 3780 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3781 ) 3782 3783 def paren_sql(self, expression: exp.Paren) -> str: 3784 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3785 return f"({sql}{self.seg(')', sep='')}" 3786 3787 def neg_sql(self, expression: exp.Neg) -> str: 3788 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3789 this_sql = self.sql(expression, "this") 3790 sep = " " if this_sql[0] == "-" else "" 3791 return f"-{sep}{this_sql}" 3792 3793 def not_sql(self, expression: exp.Not) -> str: 3794 return f"NOT {self.sql(expression, 'this')}" 3795 3796 def alias_sql(self, expression: exp.Alias) -> str: 3797 alias = self.sql(expression, "alias") 3798 alias = f" AS {alias}" if alias else "" 3799 return f"{self.sql(expression, 'this')}{alias}" 3800 3801 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3802 alias = expression.args["alias"] 3803 3804 parent = expression.parent 3805 pivot = parent and parent.parent 3806 3807 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3808 identifier_alias = isinstance(alias, exp.Identifier) 3809 literal_alias = isinstance(alias, exp.Literal) 3810 3811 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3812 alias.replace(exp.Literal.string(alias.output_name)) 3813 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3814 alias.replace(exp.to_identifier(alias.output_name)) 3815 3816 return self.alias_sql(expression) 3817 3818 def aliases_sql(self, expression: exp.Aliases) -> str: 3819 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3820 3821 def atindex_sql(self, expression: exp.AtIndex) -> str: 3822 this = self.sql(expression, "this") 3823 index = self.sql(expression, "expression") 3824 return f"{this} AT {index}" 3825 3826 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3827 this = self.sql(expression, "this") 3828 zone = self.sql(expression, "zone") 3829 return f"{this} AT TIME ZONE {zone}" 3830 3831 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3832 this = self.sql(expression, "this") 3833 zone = self.sql(expression, "zone") 3834 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3835 3836 def add_sql(self, expression: exp.Add) -> str: 3837 return self.binary(expression, "+") 3838 3839 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 3840 return self.connector_sql(expression, "AND", stack) 3841 3842 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 3843 return self.connector_sql(expression, "OR", stack) 3844 3845 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 3846 return self.connector_sql(expression, "XOR", stack) 3847 3848 def connector_sql( 3849 self, 3850 expression: exp.Connector, 3851 op: str, 3852 stack: list[str | exp.Expr] | None = None, 3853 ) -> str: 3854 if stack is not None: 3855 if expression.expressions: 3856 stack.append(self.expressions(expression, sep=f" {op} ")) 3857 else: 3858 stack.append(expression.right) 3859 if expression.comments and self.comments: 3860 for comment in expression.comments: 3861 if comment: 3862 op += f" /*{self.sanitize_comment(comment)}*/" 3863 stack.extend((op, expression.left)) 3864 return op 3865 3866 stack = [expression] 3867 sqls: list[str] = [] 3868 ops = set() 3869 3870 while stack: 3871 node = stack.pop() 3872 if isinstance(node, exp.Connector): 3873 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3874 else: 3875 sql = self.sql(node) 3876 if sqls and sqls[-1] in ops: 3877 sqls[-1] += f" {sql}" 3878 else: 3879 sqls.append(sql) 3880 3881 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3882 return sep.join(sqls) 3883 3884 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3885 return self.binary(expression, "&") 3886 3887 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3888 return self.binary(expression, "<<") 3889 3890 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3891 return f"~{self.sql(expression, 'this')}" 3892 3893 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3894 return self.binary(expression, "|") 3895 3896 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3897 return self.binary(expression, ">>") 3898 3899 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3900 return self.binary(expression, "^") 3901 3902 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 3903 format_sql = self.sql(expression, "format") 3904 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3905 to_sql = self.sql(expression, "to") 3906 to_sql = f" {to_sql}" if to_sql else "" 3907 action = self.sql(expression, "action") 3908 action = f" {action}" if action else "" 3909 default = self.sql(expression, "default") 3910 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3911 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3912 3913 # Base implementation that excludes safe, zone, and target_type metadata args 3914 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3915 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3916 3917 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3918 zone = self.sql(expression, "this") 3919 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3920 3921 def collate_sql(self, expression: exp.Collate) -> str: 3922 if self.COLLATE_IS_FUNC: 3923 return self.function_fallback_sql(expression) 3924 return self.binary(expression, "COLLATE") 3925 3926 def command_sql(self, expression: exp.Command) -> str: 3927 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3928 3929 def comment_sql(self, expression: exp.Comment) -> str: 3930 this = self.sql(expression, "this") 3931 kind = expression.args["kind"] 3932 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3933 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3934 expression_sql = self.sql(expression, "expression") 3935 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3936 3937 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3938 this = self.sql(expression, "this") 3939 delete = " DELETE" if expression.args.get("delete") else "" 3940 recompress = self.sql(expression, "recompress") 3941 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3942 to_disk = self.sql(expression, "to_disk") 3943 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3944 to_volume = self.sql(expression, "to_volume") 3945 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3946 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3947 3948 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3949 where = self.sql(expression, "where") 3950 group = self.sql(expression, "group") 3951 aggregates = self.expressions(expression, key="aggregates") 3952 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3953 3954 if not (where or group or aggregates) and len(expression.expressions) == 1: 3955 return f"TTL {self.expressions(expression, flat=True)}" 3956 3957 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3958 3959 def transaction_sql(self, expression: exp.Transaction) -> str: 3960 modes = self.expressions(expression, key="modes") 3961 modes = f" {modes}" if modes else "" 3962 return f"BEGIN{modes}" 3963 3964 def commit_sql(self, expression: exp.Commit) -> str: 3965 chain = expression.args.get("chain") 3966 if chain is not None: 3967 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3968 3969 return f"COMMIT{chain or ''}" 3970 3971 def rollback_sql(self, expression: exp.Rollback) -> str: 3972 savepoint = expression.args.get("savepoint") 3973 savepoint = f" TO {savepoint}" if savepoint else "" 3974 return f"ROLLBACK{savepoint}" 3975 3976 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3977 this = self.sql(expression, "this") 3978 3979 dtype = self.sql(expression, "dtype") 3980 if dtype: 3981 collate = self.sql(expression, "collate") 3982 collate = f" COLLATE {collate}" if collate else "" 3983 using = self.sql(expression, "using") 3984 using = f" USING {using}" if using else "" 3985 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3986 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3987 3988 default = self.sql(expression, "default") 3989 if default: 3990 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3991 3992 comment = self.sql(expression, "comment") 3993 if comment: 3994 return f"ALTER COLUMN {this} COMMENT {comment}" 3995 3996 visible = expression.args.get("visible") 3997 if visible: 3998 return f"ALTER COLUMN {this} SET {visible}" 3999 4000 allow_null = expression.args.get("allow_null") 4001 drop = expression.args.get("drop") 4002 4003 if not drop and not allow_null: 4004 self.unsupported("Unsupported ALTER COLUMN syntax") 4005 4006 if allow_null is not None: 4007 keyword = "DROP" if drop else "SET" 4008 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4009 4010 return f"ALTER COLUMN {this} DROP DEFAULT" 4011 4012 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4013 this = self.sql(expression, "this") 4014 rename_from = self.sql(expression, "rename_from") 4015 if rename_from: 4016 if not self.SUPPORTS_CHANGE_COLUMN: 4017 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4018 return f"CHANGE COLUMN {rename_from} {this}" 4019 if not self.SUPPORTS_MODIFY_COLUMN: 4020 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4021 return f"MODIFY COLUMN {this}" 4022 4023 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4024 this = self.sql(expression, "this") 4025 4026 visible = expression.args.get("visible") 4027 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4028 4029 return f"ALTER INDEX {this} {visible_sql}" 4030 4031 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4032 this = self.sql(expression, "this") 4033 if not isinstance(expression.this, exp.Var): 4034 this = f"KEY DISTKEY {this}" 4035 return f"ALTER DISTSTYLE {this}" 4036 4037 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4038 compound = " COMPOUND" if expression.args.get("compound") else "" 4039 this = self.sql(expression, "this") 4040 expressions = self.expressions(expression, flat=True) 4041 expressions = f"({expressions})" if expressions else "" 4042 return f"ALTER{compound} SORTKEY {this or expressions}" 4043 4044 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4045 if not self.RENAME_TABLE_WITH_DB: 4046 # Remove db from tables 4047 expression = expression.transform( 4048 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4049 ).assert_is(exp.AlterRename) 4050 this = self.sql(expression, "this") 4051 to_kw = " TO" if include_to else "" 4052 return f"RENAME{to_kw} {this}" 4053 4054 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4055 exists = " IF EXISTS" if expression.args.get("exists") else "" 4056 old_column = self.sql(expression, "this") 4057 new_column = self.sql(expression, "to") 4058 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4059 4060 def alterset_sql(self, expression: exp.AlterSet) -> str: 4061 exprs = self.expressions(expression, flat=True) 4062 if self.ALTER_SET_WRAPPED: 4063 exprs = f"({exprs})" 4064 4065 return f"SET {exprs}" 4066 4067 def alter_sql(self, expression: exp.Alter) -> str: 4068 actions = expression.args["actions"] 4069 4070 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4071 actions[0], exp.ColumnDef 4072 ): 4073 actions_sql = self.expressions(expression, key="actions", flat=True) 4074 actions_sql = f"ADD {actions_sql}" 4075 else: 4076 actions_list = [] 4077 for action in actions: 4078 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4079 action_sql = self.add_column_sql(action) 4080 else: 4081 action_sql = self.sql(action) 4082 if isinstance(action, exp.Query): 4083 action_sql = f"AS {action_sql}" 4084 4085 actions_list.append(action_sql) 4086 4087 actions_sql = self.format_args(*actions_list).lstrip("\n") 4088 4089 iceberg = ( 4090 "ICEBERG " 4091 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4092 else "" 4093 ) 4094 exists = " IF EXISTS" if expression.args.get("exists") else "" 4095 on_cluster = self.sql(expression, "cluster") 4096 on_cluster = f" {on_cluster}" if on_cluster else "" 4097 only = " ONLY" if expression.args.get("only") else "" 4098 options = self.expressions(expression, key="options") 4099 options = f", {options}" if options else "" 4100 kind = self.sql(expression, "kind") 4101 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4102 check = " WITH CHECK" if expression.args.get("check") else "" 4103 cascade = ( 4104 " CASCADE" 4105 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4106 else "" 4107 ) 4108 this = self.sql(expression, "this") 4109 this = f" {this}" if this else "" 4110 4111 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4112 4113 def altersession_sql(self, expression: exp.AlterSession) -> str: 4114 items_sql = self.expressions(expression, flat=True) 4115 keyword = "UNSET" if expression.args.get("unset") else "SET" 4116 return f"{keyword} {items_sql}" 4117 4118 def add_column_sql(self, expression: exp.Expr) -> str: 4119 sql = self.sql(expression) 4120 if isinstance(expression, exp.Schema): 4121 column_text = " COLUMNS" 4122 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4123 column_text = " COLUMN" 4124 else: 4125 column_text = "" 4126 4127 return f"ADD{column_text} {sql}" 4128 4129 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4130 expressions = self.expressions(expression) 4131 exists = " IF EXISTS " if expression.args.get("exists") else " " 4132 return f"DROP{exists}{expressions}" 4133 4134 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4135 return "DROP PRIMARY KEY" 4136 4137 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4138 return f"ADD {self.expressions(expression, indent=False)}" 4139 4140 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4141 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4142 location = self.sql(expression, "location") 4143 location = f" {location}" if location else "" 4144 return f"ADD {exists}{self.sql(expression.this)}{location}" 4145 4146 def distinct_sql(self, expression: exp.Distinct) -> str: 4147 this = self.expressions(expression, flat=True) 4148 4149 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4150 case = exp.case() 4151 for arg in expression.expressions: 4152 case = case.when(arg.is_(exp.null()), exp.null()) 4153 this = self.sql(case.else_(f"({this})")) 4154 4155 this = f" {this}" if this else "" 4156 4157 on = self.sql(expression, "on") 4158 on = f" ON {on}" if on else "" 4159 return f"DISTINCT{this}{on}" 4160 4161 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4162 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4163 4164 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4165 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4166 4167 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4168 this_sql = self.sql(expression, "this") 4169 expression_sql = self.sql(expression, "expression") 4170 kind = "MAX" if expression.args.get("max") else "MIN" 4171 return f"{this_sql} HAVING {kind} {expression_sql}" 4172 4173 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4174 return self.sql( 4175 exp.Cast( 4176 this=exp.Div(this=expression.this, expression=expression.expression), 4177 to=exp.DataType(this=exp.DType.INT), 4178 ) 4179 ) 4180 4181 def dpipe_sql(self, expression: exp.DPipe) -> str: 4182 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4183 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4184 return self.binary(expression, "||") 4185 4186 def div_sql(self, expression: exp.Div) -> str: 4187 l, r = expression.left, expression.right 4188 4189 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4190 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4191 4192 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4193 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4194 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4195 4196 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4197 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4198 return self.sql( 4199 exp.cast( 4200 l / r, 4201 to=exp.DType.BIGINT, 4202 ) 4203 ) 4204 4205 return self.binary(expression, "/") 4206 4207 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4208 n = exp._wrap(expression.this, exp.Binary) 4209 d = exp._wrap(expression.expression, exp.Binary) 4210 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4211 4212 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4213 return self.binary(expression, "OVERLAPS") 4214 4215 def distance_sql(self, expression: exp.Distance) -> str: 4216 return self.binary(expression, "<->") 4217 4218 def dot_sql(self, expression: exp.Dot) -> str: 4219 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4220 4221 def eq_sql(self, expression: exp.EQ) -> str: 4222 return self.binary(expression, "=") 4223 4224 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4225 return self.binary(expression, ":=") 4226 4227 def escape_sql(self, expression: exp.Escape) -> str: 4228 this = expression.this 4229 if ( 4230 isinstance(this, (exp.Like, exp.ILike)) 4231 and isinstance(this.expression, (exp.All, exp.Any)) 4232 and not self.SUPPORTS_LIKE_QUANTIFIERS 4233 ): 4234 return self._like_sql(this, escape=expression) 4235 return self.binary(expression, "ESCAPE") 4236 4237 def glob_sql(self, expression: exp.Glob) -> str: 4238 return self.binary(expression, "GLOB") 4239 4240 def gt_sql(self, expression: exp.GT) -> str: 4241 return self.binary(expression, ">") 4242 4243 def gte_sql(self, expression: exp.GTE) -> str: 4244 return self.binary(expression, ">=") 4245 4246 def is_sql(self, expression: exp.Is) -> str: 4247 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4248 return self.sql( 4249 expression.this if expression.expression.this else exp.not_(expression.this) 4250 ) 4251 return self.binary(expression, "IS") 4252 4253 def _like_sql( 4254 self, 4255 expression: exp.Like | exp.ILike, 4256 escape: exp.Escape | None = None, 4257 ) -> str: 4258 this = expression.this 4259 rhs = expression.expression 4260 4261 if isinstance(expression, exp.Like): 4262 exp_class: type[exp.Like | exp.ILike] = exp.Like 4263 op = "LIKE" 4264 else: 4265 exp_class = exp.ILike 4266 op = "ILIKE" 4267 4268 if expression.args.get("negate"): 4269 op = f"NOT {op}" 4270 4271 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4272 exprs = rhs.this.unnest() 4273 4274 if isinstance(exprs, exp.Tuple): 4275 exprs = exprs.expressions 4276 else: 4277 exprs = [exprs] 4278 4279 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4280 4281 def _make_like(expr: exp.Expression) -> exp.Expression: 4282 like: exp.Expression = exp_class( 4283 this=this, expression=expr, negate=expression.args.get("negate") 4284 ) 4285 if escape: 4286 like = exp.Escape(this=like, expression=escape.expression.copy()) 4287 return like 4288 4289 like_expr: exp.Expr = _make_like(exprs[0]) 4290 for expr in exprs[1:]: 4291 like_expr = connective(like_expr, _make_like(expr), copy=False) 4292 4293 parent = escape.parent if escape else expression.parent 4294 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4295 parent, exp.Condition 4296 ): 4297 like_expr = exp.paren(like_expr, copy=False) 4298 4299 return self.sql(like_expr) 4300 4301 return self.binary(expression, op) 4302 4303 def like_sql(self, expression: exp.Like) -> str: 4304 return self._like_sql(expression) 4305 4306 def ilike_sql(self, expression: exp.ILike) -> str: 4307 return self._like_sql(expression) 4308 4309 def match_sql(self, expression: exp.Match) -> str: 4310 return self.binary(expression, "MATCH") 4311 4312 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4313 return self.binary(expression, "SIMILAR TO") 4314 4315 def lt_sql(self, expression: exp.LT) -> str: 4316 return self.binary(expression, "<") 4317 4318 def lte_sql(self, expression: exp.LTE) -> str: 4319 return self.binary(expression, "<=") 4320 4321 def mod_sql(self, expression: exp.Mod) -> str: 4322 return self.binary(expression, "%") 4323 4324 def mul_sql(self, expression: exp.Mul) -> str: 4325 return self.binary(expression, "*") 4326 4327 def neq_sql(self, expression: exp.NEQ) -> str: 4328 return self.binary(expression, "<>") 4329 4330 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4331 return self.binary(expression, "IS NOT DISTINCT FROM") 4332 4333 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4334 return self.binary(expression, "IS DISTINCT FROM") 4335 4336 def sub_sql(self, expression: exp.Sub) -> str: 4337 return self.binary(expression, "-") 4338 4339 def trycast_sql(self, expression: exp.TryCast) -> str: 4340 return self.cast_sql(expression, safe_prefix="TRY_") 4341 4342 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4343 return self.cast_sql(expression) 4344 4345 def try_sql(self, expression: exp.Try) -> str: 4346 if not self.TRY_SUPPORTED: 4347 self.unsupported("Unsupported TRY function") 4348 return self.sql(expression, "this") 4349 4350 return self.func("TRY", expression.this) 4351 4352 def log_sql(self, expression: exp.Log) -> str: 4353 this = expression.this 4354 expr = expression.expression 4355 4356 if self.dialect.LOG_BASE_FIRST is False: 4357 this, expr = expr, this 4358 elif self.dialect.LOG_BASE_FIRST is None and expr: 4359 if this.name in ("2", "10"): 4360 return self.func(f"LOG{this.name}", expr) 4361 4362 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4363 4364 return self.func("LOG", this, expr) 4365 4366 def use_sql(self, expression: exp.Use) -> str: 4367 kind = self.sql(expression, "kind") 4368 kind = f" {kind}" if kind else "" 4369 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4370 this = f" {this}" if this else "" 4371 return f"USE{kind}{this}" 4372 4373 def binary(self, expression: exp.Binary, op: str) -> str: 4374 sqls: list[str] = [] 4375 stack: list[None | str | exp.Expr] = [expression] 4376 binary_type = type(expression) 4377 4378 while stack: 4379 node = stack.pop() 4380 4381 if type(node) is binary_type: 4382 op_func = node.args.get("operator") 4383 if op_func: 4384 op = f"OPERATOR({self.sql(op_func)})" 4385 4386 stack.append(node.args.get("expression")) 4387 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4388 stack.append(node.args.get("this")) 4389 else: 4390 sqls.append(self.sql(node)) 4391 4392 return "".join(sqls) 4393 4394 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4395 to_clause = self.sql(expression, "to") 4396 if to_clause: 4397 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4398 4399 return self.function_fallback_sql(expression) 4400 4401 def function_fallback_sql(self, expression: exp.Func) -> str: 4402 args = [] 4403 4404 for key in expression.arg_types: 4405 arg_value = expression.args.get(key) 4406 4407 if isinstance(arg_value, list): 4408 for value in arg_value: 4409 args.append(value) 4410 elif arg_value is not None: 4411 args.append(arg_value) 4412 4413 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4414 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4415 else: 4416 name = expression.sql_name() 4417 4418 return self.func(name, *args) 4419 4420 def func( 4421 self, 4422 name: str, 4423 *args: t.Any, 4424 prefix: str = "(", 4425 suffix: str = ")", 4426 normalize: bool = True, 4427 ) -> str: 4428 name = self.normalize_func(name) if normalize else name 4429 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4430 4431 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4432 arg_sqls = tuple( 4433 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4434 ) 4435 if self.pretty and self.too_wide(arg_sqls): 4436 return self.indent( 4437 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4438 ) 4439 return sep.join(arg_sqls) 4440 4441 def too_wide(self, args: t.Iterable) -> bool: 4442 return sum(len(arg) for arg in args) > self.max_text_width 4443 4444 def format_time( 4445 self, 4446 expression: exp.Expr, 4447 inverse_time_mapping: dict[str, str] | None = None, 4448 inverse_time_trie: dict | None = None, 4449 ) -> str | None: 4450 return format_time( 4451 self.sql(expression, "format"), 4452 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4453 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4454 ) 4455 4456 def expressions( 4457 self, 4458 expression: exp.Expr | None = None, 4459 key: str | None = None, 4460 sqls: t.Collection[str | exp.Expr] | None = None, 4461 flat: bool = False, 4462 indent: bool = True, 4463 skip_first: bool = False, 4464 skip_last: bool = False, 4465 sep: str = ", ", 4466 prefix: str = "", 4467 dynamic: bool = False, 4468 new_line: bool = False, 4469 ) -> str: 4470 expressions = expression.args.get(key or "expressions") if expression else sqls 4471 4472 if not expressions: 4473 return "" 4474 4475 if flat: 4476 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4477 4478 num_sqls = len(expressions) 4479 result_sqls = [] 4480 4481 for i, e in enumerate(expressions): 4482 sql = self.sql(e, comment=False) 4483 if not sql: 4484 continue 4485 4486 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4487 4488 if self.pretty: 4489 if self.leading_comma: 4490 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4491 else: 4492 result_sqls.append( 4493 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4494 ) 4495 else: 4496 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4497 4498 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4499 if new_line: 4500 result_sqls.insert(0, "") 4501 result_sqls.append("") 4502 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4503 else: 4504 result_sql = "".join(result_sqls) 4505 4506 return ( 4507 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4508 if indent 4509 else result_sql 4510 ) 4511 4512 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4513 flat = flat or isinstance(expression.parent, exp.Properties) 4514 expressions_sql = self.expressions(expression, flat=flat) 4515 if flat: 4516 return f"{op} {expressions_sql}" 4517 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4518 4519 def naked_property(self, expression: exp.Property) -> str: 4520 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4521 if not property_name: 4522 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4523 return f"{property_name} {self.sql(expression, 'this')}" 4524 4525 def tag_sql(self, expression: exp.Tag) -> str: 4526 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4527 4528 def token_sql(self, token_type: TokenType) -> str: 4529 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4530 4531 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4532 this = self.sql(expression, "this") 4533 expressions = self.no_identify(self.expressions, expression) 4534 expressions = ( 4535 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4536 ) 4537 return f"{this}{expressions}" if expressions.strip() != "" else this 4538 4539 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4540 this = self.sql(expression, "this") 4541 expressions = self.expressions(expression, flat=True) 4542 return f"{this}({expressions})" 4543 4544 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4545 return self.binary(expression, "=>") 4546 4547 def when_sql(self, expression: exp.When) -> str: 4548 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4549 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4550 condition = self.sql(expression, "condition") 4551 condition = f" AND {condition}" if condition else "" 4552 4553 then_expression = expression.args.get("then") 4554 if isinstance(then_expression, exp.Insert): 4555 this = self.sql(then_expression, "this") 4556 this = f"INSERT {this}" if this else "INSERT" 4557 then = self.sql(then_expression, "expression") 4558 then = f"{this} VALUES {then}" if then else this 4559 elif isinstance(then_expression, exp.Update): 4560 if isinstance(then_expression.args.get("expressions"), exp.Star): 4561 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4562 else: 4563 expressions_sql = self.expressions(then_expression) 4564 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4565 else: 4566 then = self.sql(then_expression) 4567 4568 if isinstance(then_expression, (exp.Insert, exp.Update)): 4569 where = self.sql(then_expression, "where") 4570 if where and not self.SUPPORTS_MERGE_WHERE: 4571 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4572 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4573 where = "" 4574 then = f"{then}{where}" 4575 return f"WHEN {matched}{source}{condition} THEN {then}" 4576 4577 def whens_sql(self, expression: exp.Whens) -> str: 4578 return self.expressions(expression, sep=" ", indent=False) 4579 4580 def merge_sql(self, expression: exp.Merge) -> str: 4581 table = expression.this 4582 table_alias = "" 4583 4584 hints = table.args.get("hints") 4585 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4586 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4587 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4588 4589 this = self.sql(table) 4590 using = f"USING {self.sql(expression, 'using')}" 4591 whens = self.sql(expression, "whens") 4592 4593 on = self.sql(expression, "on") 4594 on = f"ON {on}" if on else "" 4595 4596 if not on: 4597 on = self.expressions(expression, key="using_cond") 4598 on = f"USING ({on})" if on else "" 4599 4600 returning = self.sql(expression, "returning") 4601 if returning: 4602 whens = f"{whens}{returning}" 4603 4604 sep = self.sep() 4605 4606 return self.prepend_ctes( 4607 expression, 4608 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4609 ) 4610 4611 @unsupported_args("format") 4612 def tochar_sql(self, expression: exp.ToChar) -> str: 4613 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4614 4615 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4616 if not self.SUPPORTS_TO_NUMBER: 4617 self.unsupported("Unsupported TO_NUMBER function") 4618 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4619 4620 fmt = expression.args.get("format") 4621 if not fmt: 4622 self.unsupported("Conversion format is required for TO_NUMBER") 4623 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4624 4625 return self.func("TO_NUMBER", expression.this, fmt) 4626 4627 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4628 this = self.sql(expression, "this") 4629 kind = self.sql(expression, "kind") 4630 settings_sql = self.expressions(expression, key="settings", sep=" ") 4631 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4632 return f"{this}({kind}{args})" 4633 4634 def dictrange_sql(self, expression: exp.DictRange) -> str: 4635 this = self.sql(expression, "this") 4636 max = self.sql(expression, "max") 4637 min = self.sql(expression, "min") 4638 return f"{this}(MIN {min} MAX {max})" 4639 4640 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4641 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4642 4643 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4644 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4645 4646 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4647 def uniquekeyproperty_sql( 4648 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4649 ) -> str: 4650 return f"{prefix} ({self.expressions(expression, flat=True)})" 4651 4652 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4653 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4654 expressions = self.expressions(expression, flat=True) 4655 expressions = f" {self.wrap(expressions)}" if expressions else "" 4656 buckets = self.sql(expression, "buckets") 4657 kind = self.sql(expression, "kind") 4658 buckets = f" BUCKETS {buckets}" if buckets else "" 4659 order = self.sql(expression, "order") 4660 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4661 4662 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4663 return "" 4664 4665 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4666 expressions = self.expressions(expression, key="expressions", flat=True) 4667 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4668 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4669 buckets = self.sql(expression, "buckets") 4670 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4671 4672 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4673 this = self.sql(expression, "this") 4674 having = self.sql(expression, "having") 4675 4676 if having: 4677 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4678 4679 return self.func("ANY_VALUE", this) 4680 4681 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4682 transform = self.func("TRANSFORM", *expression.expressions) 4683 row_format_before = self.sql(expression, "row_format_before") 4684 row_format_before = f" {row_format_before}" if row_format_before else "" 4685 record_writer = self.sql(expression, "record_writer") 4686 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4687 using = f" USING {self.sql(expression, 'command_script')}" 4688 schema = self.sql(expression, "schema") 4689 schema = f" AS {schema}" if schema else "" 4690 row_format_after = self.sql(expression, "row_format_after") 4691 row_format_after = f" {row_format_after}" if row_format_after else "" 4692 record_reader = self.sql(expression, "record_reader") 4693 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4694 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4695 4696 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4697 key_block_size = self.sql(expression, "key_block_size") 4698 if key_block_size: 4699 return f"KEY_BLOCK_SIZE = {key_block_size}" 4700 4701 using = self.sql(expression, "using") 4702 if using: 4703 return f"USING {using}" 4704 4705 parser = self.sql(expression, "parser") 4706 if parser: 4707 return f"WITH PARSER {parser}" 4708 4709 comment = self.sql(expression, "comment") 4710 if comment: 4711 return f"COMMENT {comment}" 4712 4713 visible = expression.args.get("visible") 4714 if visible is not None: 4715 return "VISIBLE" if visible else "INVISIBLE" 4716 4717 engine_attr = self.sql(expression, "engine_attr") 4718 if engine_attr: 4719 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4720 4721 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4722 if secondary_engine_attr: 4723 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4724 4725 self.unsupported("Unsupported index constraint option.") 4726 return "" 4727 4728 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4729 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4730 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4731 4732 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4733 kind = self.sql(expression, "kind") 4734 kind = f"{kind} INDEX" if kind else "INDEX" 4735 this = self.sql(expression, "this") 4736 this = f" {this}" if this else "" 4737 index_type = self.sql(expression, "index_type") 4738 index_type = f" USING {index_type}" if index_type else "" 4739 expressions = self.expressions(expression, flat=True) 4740 expressions = f" ({expressions})" if expressions else "" 4741 options = self.expressions(expression, key="options", sep=" ") 4742 options = f" {options}" if options else "" 4743 return f"{kind}{this}{index_type}{expressions}{options}" 4744 4745 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4746 if self.NVL2_SUPPORTED: 4747 return self.function_fallback_sql(expression) 4748 4749 case = exp.Case().when( 4750 expression.this.is_(exp.null()).not_(copy=False), 4751 expression.args["true"], 4752 copy=False, 4753 ) 4754 else_cond = expression.args.get("false") 4755 if else_cond: 4756 case.else_(else_cond, copy=False) 4757 4758 return self.sql(case) 4759 4760 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4761 this = self.sql(expression, "this") 4762 expr = self.sql(expression, "expression") 4763 position = self.sql(expression, "position") 4764 position = f", {position}" if position else "" 4765 iterator = self.sql(expression, "iterator") 4766 condition = self.sql(expression, "condition") 4767 condition = f" IF {condition}" if condition else "" 4768 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4769 4770 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4771 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4772 4773 def opclass_sql(self, expression: exp.Opclass) -> str: 4774 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4775 4776 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4777 model = self.sql(expression, "this") 4778 model = f"MODEL {model}" 4779 expr = expression.expression 4780 if expr: 4781 expr_sql = self.sql(expression, "expression") 4782 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 4783 else: 4784 expr_sql = None 4785 4786 parameters = self.sql(expression, "params_struct") or None 4787 4788 return self.func(name, model, expr_sql, parameters) 4789 4790 def predict_sql(self, expression: exp.Predict) -> str: 4791 return self._ml_sql(expression, "PREDICT") 4792 4793 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4794 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4795 return self._ml_sql(expression, name) 4796 4797 def generatetext_sql(self, expression: exp.GenerateText) -> str: 4798 return self._ml_sql(expression, "GENERATE_TEXT") 4799 4800 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 4801 return self._ml_sql(expression, "GENERATE_TABLE") 4802 4803 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 4804 return self._ml_sql(expression, "GENERATE_BOOL") 4805 4806 def generateint_sql(self, expression: exp.GenerateInt) -> str: 4807 return self._ml_sql(expression, "GENERATE_INT") 4808 4809 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 4810 return self._ml_sql(expression, "GENERATE_DOUBLE") 4811 4812 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4813 return self._ml_sql(expression, "TRANSLATE") 4814 4815 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4816 return self._ml_sql(expression, "FORECAST") 4817 4818 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4819 this_sql = self.sql(expression, "this") 4820 if isinstance(expression.this, exp.Table): 4821 this_sql = f"TABLE {this_sql}" 4822 4823 return self.func( 4824 "FORECAST", 4825 this_sql, 4826 expression.args.get("data_col"), 4827 expression.args.get("timestamp_col"), 4828 expression.args.get("model"), 4829 expression.args.get("id_cols"), 4830 expression.args.get("horizon"), 4831 expression.args.get("forecast_end_timestamp"), 4832 expression.args.get("confidence_level"), 4833 expression.args.get("output_historical_time_series"), 4834 expression.args.get("context_window"), 4835 ) 4836 4837 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4838 this_sql = self.sql(expression, "this") 4839 if isinstance(expression.this, exp.Table): 4840 this_sql = f"TABLE {this_sql}" 4841 4842 return self.func( 4843 "FEATURES_AT_TIME", 4844 this_sql, 4845 expression.args.get("time"), 4846 expression.args.get("num_rows"), 4847 expression.args.get("ignore_feature_nulls"), 4848 ) 4849 4850 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4851 this_sql = self.sql(expression, "this") 4852 if isinstance(expression.this, exp.Table): 4853 this_sql = f"TABLE {this_sql}" 4854 4855 query_table = self.sql(expression, "query_table") 4856 if isinstance(expression.args["query_table"], exp.Table): 4857 query_table = f"TABLE {query_table}" 4858 4859 return self.func( 4860 "VECTOR_SEARCH", 4861 this_sql, 4862 expression.args.get("column_to_search"), 4863 query_table, 4864 expression.args.get("query_column_to_search"), 4865 expression.args.get("top_k"), 4866 expression.args.get("distance_type"), 4867 expression.args.get("options"), 4868 ) 4869 4870 def forin_sql(self, expression: exp.ForIn) -> str: 4871 this = self.sql(expression, "this") 4872 expression_sql = self.sql(expression, "expression") 4873 return f"FOR {this} DO {expression_sql}" 4874 4875 def refresh_sql(self, expression: exp.Refresh) -> str: 4876 this = self.sql(expression, "this") 4877 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4878 return f"REFRESH {kind}{this}" 4879 4880 def toarray_sql(self, expression: exp.ToArray) -> str: 4881 arg = expression.this 4882 if not arg.type: 4883 import sqlglot.optimizer.annotate_types 4884 4885 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 4886 4887 if arg.is_type(exp.DType.ARRAY): 4888 return self.sql(arg) 4889 4890 cond_for_null = arg.is_(exp.null()) 4891 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4892 4893 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4894 this = expression.this 4895 time_format = self.format_time(expression) 4896 4897 if time_format: 4898 return self.sql( 4899 exp.cast( 4900 exp.StrToTime(this=this, format=expression.args["format"]), 4901 exp.DType.TIME, 4902 ) 4903 ) 4904 4905 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 4906 return self.sql(this) 4907 4908 return self.sql(exp.cast(this, exp.DType.TIME)) 4909 4910 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4911 this = expression.this 4912 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 4913 return self.sql(this) 4914 4915 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 4916 4917 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4918 this = expression.this 4919 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 4920 return self.sql(this) 4921 4922 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 4923 4924 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4925 this = expression.this 4926 time_format = self.format_time(expression) 4927 safe = expression.args.get("safe") 4928 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4929 return self.sql( 4930 exp.cast( 4931 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4932 exp.DType.DATE, 4933 ) 4934 ) 4935 4936 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 4937 return self.sql(this) 4938 4939 if safe: 4940 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 4941 4942 return self.sql(exp.cast(this, exp.DType.DATE)) 4943 4944 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4945 return self.sql( 4946 exp.func( 4947 "DATEDIFF", 4948 expression.this, 4949 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 4950 "day", 4951 ) 4952 ) 4953 4954 def lastday_sql(self, expression: exp.LastDay) -> str: 4955 if self.LAST_DAY_SUPPORTS_DATE_PART: 4956 return self.function_fallback_sql(expression) 4957 4958 unit = expression.text("unit") 4959 if unit and unit != "MONTH": 4960 self.unsupported("Date parts are not supported in LAST_DAY.") 4961 4962 return self.func("LAST_DAY", expression.this) 4963 4964 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4965 import sqlglot.dialects.dialect 4966 4967 return self.func( 4968 "DATE_ADD", 4969 expression.this, 4970 expression.expression, 4971 sqlglot.dialects.dialect.unit_to_str(expression), 4972 ) 4973 4974 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4975 if self.CAN_IMPLEMENT_ARRAY_ANY: 4976 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4977 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4978 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4979 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4980 4981 import sqlglot.dialects.dialect 4982 4983 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4984 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 4985 self.unsupported("ARRAY_ANY is unsupported") 4986 4987 return self.function_fallback_sql(expression) 4988 4989 def struct_sql(self, expression: exp.Struct) -> str: 4990 expression.set( 4991 "expressions", 4992 [ 4993 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4994 if isinstance(e, exp.PropertyEQ) 4995 else e 4996 for e in expression.expressions 4997 ], 4998 ) 4999 5000 return self.function_fallback_sql(expression) 5001 5002 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5003 low = self.sql(expression, "this") 5004 high = self.sql(expression, "expression") 5005 5006 return f"{low} TO {high}" 5007 5008 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5009 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5010 tables = f" {self.expressions(expression)}" 5011 5012 exists = " IF EXISTS" if expression.args.get("exists") else "" 5013 5014 on_cluster = self.sql(expression, "cluster") 5015 on_cluster = f" {on_cluster}" if on_cluster else "" 5016 5017 identity = self.sql(expression, "identity") 5018 identity = f" {identity} IDENTITY" if identity else "" 5019 5020 option = self.sql(expression, "option") 5021 option = f" {option}" if option else "" 5022 5023 partition = self.sql(expression, "partition") 5024 partition = f" {partition}" if partition else "" 5025 5026 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5027 5028 # This transpiles T-SQL's CONVERT function 5029 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5030 def convert_sql(self, expression: exp.Convert) -> str: 5031 to = expression.this 5032 value = expression.expression 5033 style = expression.args.get("style") 5034 safe = expression.args.get("safe") 5035 strict = expression.args.get("strict") 5036 5037 if not to or not value: 5038 return "" 5039 5040 # Retrieve length of datatype and override to default if not specified 5041 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5042 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5043 5044 transformed: exp.Expr | None = None 5045 cast = exp.Cast if strict else exp.TryCast 5046 5047 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5048 if isinstance(style, exp.Literal) and style.is_int: 5049 import sqlglot.dialects.tsql 5050 5051 style_value = style.name 5052 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5053 if not converted_style: 5054 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5055 5056 fmt = exp.Literal.string(converted_style) 5057 5058 if to.this == exp.DType.DATE: 5059 transformed = exp.StrToDate(this=value, format=fmt) 5060 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5061 transformed = exp.StrToTime(this=value, format=fmt) 5062 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5063 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5064 elif to.this == exp.DType.TEXT: 5065 transformed = exp.TimeToStr(this=value, format=fmt) 5066 5067 if not transformed: 5068 transformed = cast(this=value, to=to, safe=safe) 5069 5070 return self.sql(transformed) 5071 5072 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5073 this = expression.this 5074 if isinstance(this, exp.JSONPathWildcard): 5075 this = self.json_path_part(this) 5076 return f".{this}" if this else "" 5077 5078 if self.SAFE_JSON_PATH_KEY_RE.match(this): 5079 return f".{this}" 5080 5081 this = self.json_path_part(this) 5082 return ( 5083 f"[{this}]" 5084 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5085 else f".{this}" 5086 ) 5087 5088 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5089 this = self.json_path_part(expression.this) 5090 return f"[{this}]" if this else "" 5091 5092 def _simplify_unless_literal(self, expression: E) -> E: 5093 if not isinstance(expression, exp.Literal): 5094 import sqlglot.optimizer.simplify 5095 5096 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5097 5098 return expression 5099 5100 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5101 this = expression.this 5102 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5103 self.unsupported( 5104 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5105 ) 5106 return self.sql(this) 5107 5108 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 5109 if self.IGNORE_NULLS_BEFORE_ORDER: 5110 # The first modifier here will be the one closest to the AggFunc's arg 5111 mods = sorted( 5112 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5113 key=lambda x: ( 5114 0 5115 if isinstance(x, exp.HavingMax) 5116 else (1 if isinstance(x, exp.Order) else 2) 5117 ), 5118 ) 5119 5120 if mods: 5121 mod = mods[0] 5122 this = expression.__class__(this=mod.this.copy()) 5123 this.meta["inline"] = True 5124 mod.this.replace(this) 5125 return self.sql(expression.this) 5126 5127 agg_func = expression.find(exp.AggFunc) 5128 5129 if agg_func: 5130 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5131 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5132 5133 return f"{self.sql(expression, 'this')} {text}" 5134 5135 def _replace_line_breaks(self, string: str) -> str: 5136 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5137 if self.pretty: 5138 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5139 return string 5140 5141 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5142 option = self.sql(expression, "this") 5143 5144 if expression.expressions: 5145 upper = option.upper() 5146 5147 # Snowflake FILE_FORMAT options are separated by whitespace 5148 sep = " " if upper == "FILE_FORMAT" else ", " 5149 5150 # Databricks copy/format options do not set their list of values with EQ 5151 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5152 values = self.expressions(expression, flat=True, sep=sep) 5153 return f"{option}{op}({values})" 5154 5155 value = self.sql(expression, "expression") 5156 5157 if not value: 5158 return option 5159 5160 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5161 5162 return f"{option}{op}{value}" 5163 5164 def credentials_sql(self, expression: exp.Credentials) -> str: 5165 cred_expr = expression.args.get("credentials") 5166 if isinstance(cred_expr, exp.Literal): 5167 # Redshift case: CREDENTIALS <string> 5168 credentials = self.sql(expression, "credentials") 5169 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5170 else: 5171 # Snowflake case: CREDENTIALS = (...) 5172 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5173 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5174 5175 storage = self.sql(expression, "storage") 5176 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5177 5178 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5179 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5180 5181 iam_role = self.sql(expression, "iam_role") 5182 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5183 5184 region = self.sql(expression, "region") 5185 region = f" REGION {region}" if region else "" 5186 5187 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5188 5189 def copy_sql(self, expression: exp.Copy) -> str: 5190 this = self.sql(expression, "this") 5191 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5192 5193 credentials = self.sql(expression, "credentials") 5194 credentials = self.seg(credentials) if credentials else "" 5195 files = self.expressions(expression, key="files", flat=True) 5196 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5197 5198 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5199 params = self.expressions( 5200 expression, 5201 key="params", 5202 sep=sep, 5203 new_line=True, 5204 skip_last=True, 5205 skip_first=True, 5206 indent=self.COPY_PARAMS_ARE_WRAPPED, 5207 ) 5208 5209 if params: 5210 if self.COPY_PARAMS_ARE_WRAPPED: 5211 params = f" WITH ({params})" 5212 elif not self.pretty and (files or credentials): 5213 params = f" {params}" 5214 5215 return f"COPY{this}{kind} {files}{credentials}{params}" 5216 5217 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5218 return "" 5219 5220 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5221 on_sql = "ON" if expression.args.get("on") else "OFF" 5222 filter_col: str | None = self.sql(expression, "filter_column") 5223 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5224 retention_period: str | None = self.sql(expression, "retention_period") 5225 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5226 5227 if filter_col or retention_period: 5228 on_sql = self.func("ON", filter_col, retention_period) 5229 5230 return f"DATA_DELETION={on_sql}" 5231 5232 def maskingpolicycolumnconstraint_sql( 5233 self, expression: exp.MaskingPolicyColumnConstraint 5234 ) -> str: 5235 this = self.sql(expression, "this") 5236 expressions = self.expressions(expression, flat=True) 5237 expressions = f" USING ({expressions})" if expressions else "" 5238 return f"MASKING POLICY {this}{expressions}" 5239 5240 def gapfill_sql(self, expression: exp.GapFill) -> str: 5241 this = self.sql(expression, "this") 5242 this = f"TABLE {this}" 5243 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5244 5245 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5246 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5247 5248 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5249 this = self.sql(expression, "this") 5250 expr = expression.expression 5251 5252 if isinstance(expr, exp.Func): 5253 # T-SQL's CLR functions are case sensitive 5254 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5255 else: 5256 expr = self.sql(expression, "expression") 5257 5258 return self.scope_resolution(expr, this) 5259 5260 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5261 if self.PARSE_JSON_NAME is None: 5262 return self.sql(expression.this) 5263 5264 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5265 5266 def rand_sql(self, expression: exp.Rand) -> str: 5267 lower = self.sql(expression, "lower") 5268 upper = self.sql(expression, "upper") 5269 5270 if lower and upper: 5271 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5272 return self.func("RAND", expression.this) 5273 5274 def changes_sql(self, expression: exp.Changes) -> str: 5275 information = self.sql(expression, "information") 5276 information = f"INFORMATION => {information}" 5277 at_before = self.sql(expression, "at_before") 5278 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5279 end = self.sql(expression, "end") 5280 end = f"{self.seg('')}{end}" if end else "" 5281 5282 return f"CHANGES ({information}){at_before}{end}" 5283 5284 def pad_sql(self, expression: exp.Pad) -> str: 5285 prefix = "L" if expression.args.get("is_left") else "R" 5286 5287 fill_pattern = self.sql(expression, "fill_pattern") or None 5288 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5289 fill_pattern = "' '" 5290 5291 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5292 5293 def summarize_sql(self, expression: exp.Summarize) -> str: 5294 table = " TABLE" if expression.args.get("table") else "" 5295 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5296 5297 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5298 generate_series = exp.GenerateSeries(**expression.args) 5299 5300 parent = expression.parent 5301 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5302 parent = parent.parent 5303 5304 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5305 return self.sql(exp.Unnest(expressions=[generate_series])) 5306 5307 if isinstance(parent, exp.Select): 5308 self.unsupported("GenerateSeries projection unnesting is not supported.") 5309 5310 return self.sql(generate_series) 5311 5312 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5313 if self.SUPPORTS_CONVERT_TIMEZONE: 5314 return self.function_fallback_sql(expression) 5315 5316 source_tz = expression.args.get("source_tz") 5317 target_tz = expression.args.get("target_tz") 5318 timestamp = expression.args.get("timestamp") 5319 5320 if source_tz and timestamp: 5321 timestamp = exp.AtTimeZone( 5322 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5323 ) 5324 5325 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5326 5327 return self.sql(expr) 5328 5329 def json_sql(self, expression: exp.JSON) -> str: 5330 this = self.sql(expression, "this") 5331 this = f" {this}" if this else "" 5332 5333 _with = expression.args.get("with_") 5334 5335 if _with is None: 5336 with_sql = "" 5337 elif not _with: 5338 with_sql = " WITHOUT" 5339 else: 5340 with_sql = " WITH" 5341 5342 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5343 5344 return f"JSON{this}{with_sql}{unique_sql}" 5345 5346 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5347 path = self.sql(expression, "path") 5348 returning = self.sql(expression, "returning") 5349 returning = f" RETURNING {returning}" if returning else "" 5350 5351 on_condition = self.sql(expression, "on_condition") 5352 on_condition = f" {on_condition}" if on_condition else "" 5353 5354 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5355 5356 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5357 regexp = " REGEXP" if expression.args.get("regexp") else "" 5358 return f"SKIP{regexp} {self.sql(expression.expression)}" 5359 5360 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5361 else_ = "ELSE " if expression.args.get("else_") else "" 5362 condition = self.sql(expression, "expression") 5363 condition = f"WHEN {condition} THEN " if condition else else_ 5364 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5365 return f"{condition}{insert}" 5366 5367 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5368 kind = self.sql(expression, "kind") 5369 expressions = self.seg(self.expressions(expression, sep=" ")) 5370 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5371 return res 5372 5373 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5374 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5375 empty = expression.args.get("empty") 5376 empty = ( 5377 f"DEFAULT {empty} ON EMPTY" 5378 if isinstance(empty, exp.Expr) 5379 else self.sql(expression, "empty") 5380 ) 5381 5382 error = expression.args.get("error") 5383 error = ( 5384 f"DEFAULT {error} ON ERROR" 5385 if isinstance(error, exp.Expr) 5386 else self.sql(expression, "error") 5387 ) 5388 5389 if error and empty: 5390 error = ( 5391 f"{empty} {error}" 5392 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5393 else f"{error} {empty}" 5394 ) 5395 empty = "" 5396 5397 null = self.sql(expression, "null") 5398 5399 return f"{empty}{error}{null}" 5400 5401 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5402 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5403 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5404 5405 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5406 this = self.sql(expression, "this") 5407 path = self.sql(expression, "path") 5408 5409 passing = self.expressions(expression, "passing") 5410 passing = f" PASSING {passing}" if passing else "" 5411 5412 on_condition = self.sql(expression, "on_condition") 5413 on_condition = f" {on_condition}" if on_condition else "" 5414 5415 path = f"{path}{passing}{on_condition}" 5416 5417 return self.func("JSON_EXISTS", this, path) 5418 5419 def _add_arrayagg_null_filter( 5420 self, 5421 array_agg_sql: str, 5422 array_agg_expr: exp.ArrayAgg, 5423 column_expr: exp.Expr, 5424 ) -> str: 5425 """ 5426 Add NULL filter to ARRAY_AGG if dialect requires it. 5427 5428 Args: 5429 array_agg_sql: The generated ARRAY_AGG SQL string 5430 array_agg_expr: The ArrayAgg expression node 5431 column_expr: The column/expression to filter (before ORDER BY wrapping) 5432 5433 Returns: 5434 SQL string with FILTER clause added if needed 5435 """ 5436 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5437 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5438 if not ( 5439 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5440 ): 5441 return array_agg_sql 5442 5443 parent = array_agg_expr.parent 5444 if isinstance(parent, exp.Filter): 5445 parent_cond = parent.expression.this 5446 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5447 elif column_expr.find(exp.Column): 5448 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5449 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5450 this_sql = ( 5451 self.expressions(column_expr) 5452 if isinstance(column_expr, exp.Distinct) 5453 else self.sql(column_expr) 5454 ) 5455 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5456 5457 return array_agg_sql 5458 5459 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5460 array_agg = self.function_fallback_sql(expression) 5461 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5462 5463 def slice_sql(self, expression: exp.Slice) -> str: 5464 step = self.sql(expression, "step") 5465 end = self.sql(expression.expression) 5466 begin = self.sql(expression.this) 5467 5468 sql = f"{end}:{step}" if step else end 5469 return f"{begin}:{sql}" if sql else f"{begin}:" 5470 5471 def apply_sql(self, expression: exp.Apply) -> str: 5472 this = self.sql(expression, "this") 5473 expr = self.sql(expression, "expression") 5474 5475 return f"{this} APPLY({expr})" 5476 5477 def _grant_or_revoke_sql( 5478 self, 5479 expression: exp.Grant | exp.Revoke, 5480 keyword: str, 5481 preposition: str, 5482 grant_option_prefix: str = "", 5483 grant_option_suffix: str = "", 5484 ) -> str: 5485 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5486 5487 kind = self.sql(expression, "kind") 5488 kind = f" {kind}" if kind else "" 5489 5490 securable = self.sql(expression, "securable") 5491 securable = f" {securable}" if securable else "" 5492 5493 principals = self.expressions(expression, key="principals", flat=True) 5494 5495 if not expression.args.get("grant_option"): 5496 grant_option_prefix = grant_option_suffix = "" 5497 5498 # cascade for revoke only 5499 cascade = self.sql(expression, "cascade") 5500 cascade = f" {cascade}" if cascade else "" 5501 5502 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5503 5504 def grant_sql(self, expression: exp.Grant) -> str: 5505 return self._grant_or_revoke_sql( 5506 expression, 5507 keyword="GRANT", 5508 preposition="TO", 5509 grant_option_suffix=" WITH GRANT OPTION", 5510 ) 5511 5512 def revoke_sql(self, expression: exp.Revoke) -> str: 5513 return self._grant_or_revoke_sql( 5514 expression, 5515 keyword="REVOKE", 5516 preposition="FROM", 5517 grant_option_prefix="GRANT OPTION FOR ", 5518 ) 5519 5520 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5521 this = self.sql(expression, "this") 5522 columns = self.expressions(expression, flat=True) 5523 columns = f"({columns})" if columns else "" 5524 5525 return f"{this}{columns}" 5526 5527 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5528 this = self.sql(expression, "this") 5529 5530 kind = self.sql(expression, "kind") 5531 kind = f"{kind} " if kind else "" 5532 5533 return f"{kind}{this}" 5534 5535 def columns_sql(self, expression: exp.Columns) -> str: 5536 func = self.function_fallback_sql(expression) 5537 if expression.args.get("unpack"): 5538 func = f"*{func}" 5539 5540 return func 5541 5542 def overlay_sql(self, expression: exp.Overlay) -> str: 5543 this = self.sql(expression, "this") 5544 expr = self.sql(expression, "expression") 5545 from_sql = self.sql(expression, "from_") 5546 for_sql = self.sql(expression, "for_") 5547 for_sql = f" FOR {for_sql}" if for_sql else "" 5548 5549 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5550 5551 @unsupported_args("format") 5552 def todouble_sql(self, expression: exp.ToDouble) -> str: 5553 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5554 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5555 5556 def string_sql(self, expression: exp.String) -> str: 5557 this = expression.this 5558 zone = expression.args.get("zone") 5559 5560 if zone: 5561 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5562 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5563 # set for source_tz to transpile the time conversion before the STRING cast 5564 this = exp.ConvertTimezone( 5565 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5566 ) 5567 5568 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5569 5570 def median_sql(self, expression: exp.Median) -> str: 5571 if not self.SUPPORTS_MEDIAN: 5572 return self.sql( 5573 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5574 ) 5575 5576 return self.function_fallback_sql(expression) 5577 5578 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5579 filler = self.sql(expression, "this") 5580 filler = f" {filler}" if filler else "" 5581 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5582 return f"TRUNCATE{filler} {with_count}" 5583 5584 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5585 if self.SUPPORTS_UNIX_SECONDS: 5586 return self.function_fallback_sql(expression) 5587 5588 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5589 5590 return self.sql( 5591 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5592 ) 5593 5594 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5595 dim = expression.expression 5596 5597 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5598 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5599 if not (dim.is_int and dim.name == "1"): 5600 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5601 dim = None 5602 5603 # If dimension is required but not specified, default initialize it 5604 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5605 dim = exp.Literal.number(1) 5606 5607 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5608 5609 def attach_sql(self, expression: exp.Attach) -> str: 5610 this = self.sql(expression, "this") 5611 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5612 expressions = self.expressions(expression) 5613 expressions = f" ({expressions})" if expressions else "" 5614 5615 return f"ATTACH{exists_sql} {this}{expressions}" 5616 5617 def detach_sql(self, expression: exp.Detach) -> str: 5618 kind = self.sql(expression, "kind") 5619 kind = f" {kind}" if kind else "" 5620 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5621 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5622 exists = " IF EXISTS" if expression.args.get("exists") else "" 5623 if exists: 5624 kind = kind or " DATABASE" 5625 5626 this = self.sql(expression, "this") 5627 this = f" {this}" if this else "" 5628 cluster = self.sql(expression, "cluster") 5629 cluster = f" {cluster}" if cluster else "" 5630 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5631 sync = " SYNC" if expression.args.get("sync") else "" 5632 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5633 5634 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5635 this = self.sql(expression, "this") 5636 value = self.sql(expression, "expression") 5637 value = f" {value}" if value else "" 5638 return f"{this}{value}" 5639 5640 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5641 return ( 5642 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5643 ) 5644 5645 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5646 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5647 encode = f"{encode} {self.sql(expression, 'this')}" 5648 5649 properties = expression.args.get("properties") 5650 if properties: 5651 encode = f"{encode} {self.properties(properties)}" 5652 5653 return encode 5654 5655 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5656 this = self.sql(expression, "this") 5657 include = f"INCLUDE {this}" 5658 5659 column_def = self.sql(expression, "column_def") 5660 if column_def: 5661 include = f"{include} {column_def}" 5662 5663 alias = self.sql(expression, "alias") 5664 if alias: 5665 include = f"{include} AS {alias}" 5666 5667 return include 5668 5669 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5670 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5671 name = f"{prefix} {self.sql(expression, 'this')}" 5672 return self.func("XMLELEMENT", name, *expression.expressions) 5673 5674 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5675 this = self.sql(expression, "this") 5676 expr = self.sql(expression, "expression") 5677 expr = f"({expr})" if expr else "" 5678 return f"{this}{expr}" 5679 5680 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5681 partitions = self.expressions(expression, "partition_expressions") 5682 create = self.expressions(expression, "create_expressions") 5683 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5684 5685 def partitionbyrangepropertydynamic_sql( 5686 self, expression: exp.PartitionByRangePropertyDynamic 5687 ) -> str: 5688 start = self.sql(expression, "start") 5689 end = self.sql(expression, "end") 5690 5691 every = expression.args["every"] 5692 if isinstance(every, exp.Interval) and every.this.is_string: 5693 every.this.replace(exp.Literal.number(every.name)) 5694 5695 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5696 5697 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5698 name = self.sql(expression, "this") 5699 values = self.expressions(expression, flat=True) 5700 5701 return f"NAME {name} VALUE {values}" 5702 5703 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5704 kind = self.sql(expression, "kind") 5705 sample = self.sql(expression, "sample") 5706 return f"SAMPLE {sample} {kind}" 5707 5708 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5709 kind = self.sql(expression, "kind") 5710 option = self.sql(expression, "option") 5711 option = f" {option}" if option else "" 5712 this = self.sql(expression, "this") 5713 this = f" {this}" if this else "" 5714 columns = self.expressions(expression) 5715 columns = f" {columns}" if columns else "" 5716 return f"{kind}{option} STATISTICS{this}{columns}" 5717 5718 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5719 this = self.sql(expression, "this") 5720 columns = self.expressions(expression) 5721 inner_expression = self.sql(expression, "expression") 5722 inner_expression = f" {inner_expression}" if inner_expression else "" 5723 update_options = self.sql(expression, "update_options") 5724 update_options = f" {update_options} UPDATE" if update_options else "" 5725 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5726 5727 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5728 kind = self.sql(expression, "kind") 5729 kind = f" {kind}" if kind else "" 5730 return f"DELETE{kind} STATISTICS" 5731 5732 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5733 inner_expression = self.sql(expression, "expression") 5734 return f"LIST CHAINED ROWS{inner_expression}" 5735 5736 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5737 kind = self.sql(expression, "kind") 5738 this = self.sql(expression, "this") 5739 this = f" {this}" if this else "" 5740 inner_expression = self.sql(expression, "expression") 5741 return f"VALIDATE {kind}{this}{inner_expression}" 5742 5743 def analyze_sql(self, expression: exp.Analyze) -> str: 5744 options = self.expressions(expression, key="options", sep=" ") 5745 options = f" {options}" if options else "" 5746 kind = self.sql(expression, "kind") 5747 kind = f" {kind}" if kind else "" 5748 this = self.sql(expression, "this") 5749 this = f" {this}" if this else "" 5750 mode = self.sql(expression, "mode") 5751 mode = f" {mode}" if mode else "" 5752 properties = self.sql(expression, "properties") 5753 properties = f" {properties}" if properties else "" 5754 partition = self.sql(expression, "partition") 5755 partition = f" {partition}" if partition else "" 5756 inner_expression = self.sql(expression, "expression") 5757 inner_expression = f" {inner_expression}" if inner_expression else "" 5758 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5759 5760 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5761 this = self.sql(expression, "this") 5762 namespaces = self.expressions(expression, key="namespaces") 5763 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5764 passing = self.expressions(expression, key="passing") 5765 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5766 columns = self.expressions(expression, key="columns") 5767 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5768 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5769 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5770 5771 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5772 this = self.sql(expression, "this") 5773 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5774 5775 def export_sql(self, expression: exp.Export) -> str: 5776 this = self.sql(expression, "this") 5777 connection = self.sql(expression, "connection") 5778 connection = f"WITH CONNECTION {connection} " if connection else "" 5779 options = self.sql(expression, "options") 5780 return f"EXPORT DATA {connection}{options} AS {this}" 5781 5782 def declare_sql(self, expression: exp.Declare) -> str: 5783 replace = "OR REPLACE " if expression.args.get("replace") else "" 5784 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 5785 5786 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5787 variables = self.expressions(expression, "this") 5788 default = self.sql(expression, "default") 5789 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5790 5791 kind = self.sql(expression, "kind") 5792 if isinstance(expression.args.get("kind"), exp.Schema): 5793 kind = f"TABLE {kind}" 5794 5795 kind = f" {kind}" if kind else "" 5796 5797 return f"{variables}{kind}{default}" 5798 5799 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5800 kind = self.sql(expression, "kind") 5801 this = self.sql(expression, "this") 5802 set = self.sql(expression, "expression") 5803 using = self.sql(expression, "using") 5804 using = f" USING {using}" if using else "" 5805 5806 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5807 5808 return f"{kind_sql} {this} SET {set}{using}" 5809 5810 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5811 params = self.expressions(expression, key="params", flat=True) 5812 return self.func(expression.name, *expression.expressions) + f"({params})" 5813 5814 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5815 return self.func(expression.name, *expression.expressions) 5816 5817 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5818 return self.anonymousaggfunc_sql(expression) 5819 5820 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5821 return self.parameterizedagg_sql(expression) 5822 5823 def show_sql(self, expression: exp.Show) -> str: 5824 self.unsupported("Unsupported SHOW statement") 5825 return "" 5826 5827 def install_sql(self, expression: exp.Install) -> str: 5828 self.unsupported("Unsupported INSTALL statement") 5829 return "" 5830 5831 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5832 # Snowflake GET/PUT statements: 5833 # PUT <file> <internalStage> <properties> 5834 # GET <internalStage> <file> <properties> 5835 props = expression.args.get("properties") 5836 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5837 this = self.sql(expression, "this") 5838 target = self.sql(expression, "target") 5839 5840 if isinstance(expression, exp.Put): 5841 return f"PUT {this} {target}{props_sql}" 5842 else: 5843 return f"GET {target} {this}{props_sql}" 5844 5845 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5846 this = self.sql(expression, "this") 5847 expr = self.sql(expression, "expression") 5848 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5849 return f"TRANSLATE({this} USING {expr}{with_error})" 5850 5851 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5852 if self.SUPPORTS_DECODE_CASE: 5853 return self.func("DECODE", *expression.expressions) 5854 5855 decode_expr, *expressions = expression.expressions 5856 5857 ifs = [] 5858 for search, result in zip(expressions[::2], expressions[1::2]): 5859 if isinstance(search, exp.Literal): 5860 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 5861 elif isinstance(search, exp.Null): 5862 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 5863 else: 5864 if isinstance(search, exp.Binary): 5865 search = exp.paren(search) 5866 5867 cond = exp.or_( 5868 decode_expr.eq(search), 5869 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5870 copy=False, 5871 ) 5872 ifs.append(exp.If(this=cond, true=result)) 5873 5874 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5875 return self.sql(case) 5876 5877 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5878 this = self.sql(expression, "this") 5879 this = self.seg(this, sep="") 5880 dimensions = self.expressions( 5881 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5882 ) 5883 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5884 metrics = self.expressions( 5885 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5886 ) 5887 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5888 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5889 facts = self.seg(f"FACTS {facts}") if facts else "" 5890 where = self.sql(expression, "where") 5891 where = self.seg(f"WHERE {where}") if where else "" 5892 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5893 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5894 5895 def getextract_sql(self, expression: exp.GetExtract) -> str: 5896 this = expression.this 5897 expr = expression.expression 5898 5899 if not this.type or not expression.type: 5900 import sqlglot.optimizer.annotate_types 5901 5902 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 5903 5904 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 5905 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5906 5907 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5908 5909 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5910 return self.sql( 5911 exp.DateAdd( 5912 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5913 expression=expression.this, 5914 unit=exp.var("DAY"), 5915 ) 5916 ) 5917 5918 def space_sql(self: Generator, expression: exp.Space) -> str: 5919 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5920 5921 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5922 return f"BUILD {self.sql(expression, 'this')}" 5923 5924 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5925 method = self.sql(expression, "method") 5926 kind = expression.args.get("kind") 5927 if not kind: 5928 return f"REFRESH {method}" 5929 5930 every = self.sql(expression, "every") 5931 unit = self.sql(expression, "unit") 5932 every = f" EVERY {every} {unit}" if every else "" 5933 starts = self.sql(expression, "starts") 5934 starts = f" STARTS {starts}" if starts else "" 5935 5936 return f"REFRESH {method} ON {kind}{every}{starts}" 5937 5938 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5939 self.unsupported("The model!attribute syntax is not supported") 5940 return "" 5941 5942 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5943 return self.func("DIRECTORY", expression.this) 5944 5945 def uuid_sql(self, expression: exp.Uuid) -> str: 5946 is_string = expression.args.get("is_string", False) 5947 uuid_func_sql = self.func("UUID") 5948 5949 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5950 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 5951 5952 return uuid_func_sql 5953 5954 def initcap_sql(self, expression: exp.Initcap) -> str: 5955 delimiters = expression.expression 5956 5957 if delimiters: 5958 # do not generate delimiters arg if we are round-tripping from default delimiters 5959 if ( 5960 delimiters.is_string 5961 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5962 ): 5963 delimiters = None 5964 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5965 self.unsupported("INITCAP does not support custom delimiters") 5966 delimiters = None 5967 5968 return self.func("INITCAP", expression.this, delimiters) 5969 5970 def localtime_sql(self, expression: exp.Localtime) -> str: 5971 this = expression.this 5972 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5973 5974 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 5975 this = expression.this 5976 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5977 5978 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5979 this = expression.this.name.upper() 5980 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5981 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5982 return "WEEK" 5983 5984 return self.func("WEEK", expression.this) 5985 5986 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5987 this = self.expressions(expression) 5988 charset = self.sql(expression, "charset") 5989 using = f" USING {charset}" if charset else "" 5990 return self.func(name, this + using) 5991 5992 def block_sql(self, expression: exp.Block) -> str: 5993 expressions = self.expressions(expression, sep="; ", flat=True) 5994 return f"{expressions}" if expressions else "" 5995 5996 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 5997 self.unsupported("Unsupported Stored Procedure syntax") 5998 return "" 5999 6000 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6001 self.unsupported("Unsupported If block syntax") 6002 return "" 6003 6004 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6005 self.unsupported("Unsupported While block syntax") 6006 return "" 6007 6008 def execute_sql(self, expression: exp.Execute) -> str: 6009 self.unsupported("Unsupported Execute syntax") 6010 return "" 6011 6012 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6013 self.unsupported("Unsupported Execute syntax") 6014 return "" 6015 6016 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6017 props = self.expressions(expression, sep=" ") 6018 return f"MODIFY {props}" 6019 6020 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6021 kind = expression.args.get("kind") 6022 return f"USING {kind} {self.sql(expression, 'this')}" 6023 6024 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6025 this = self.sql(expression, "this") 6026 to = self.sql(expression, "to") 6027 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.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 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1333 postalias_props_sql = "" 1334 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1335 postalias_props_sql = self.properties( 1336 exp.Properties( 1337 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1338 ), 1339 wrapped=False, 1340 ) 1341 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1342 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1343 1344 postindex_props_sql = "" 1345 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1346 postindex_props_sql = self.properties( 1347 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1348 wrapped=False, 1349 prefix=" ", 1350 ) 1351 1352 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1353 indexes = f" {indexes}" if indexes else "" 1354 index_sql = indexes + postindex_props_sql 1355 1356 replace = " OR REPLACE" if expression.args.get("replace") else "" 1357 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1358 unique = " UNIQUE" if expression.args.get("unique") else "" 1359 1360 clustered = expression.args.get("clustered") 1361 if clustered is None: 1362 clustered_sql = "" 1363 elif clustered: 1364 clustered_sql = " CLUSTERED COLUMNSTORE" 1365 else: 1366 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1367 1368 postcreate_props_sql = "" 1369 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1370 postcreate_props_sql = self.properties( 1371 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1372 sep=" ", 1373 prefix=" ", 1374 wrapped=False, 1375 ) 1376 1377 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1378 1379 postexpression_props_sql = "" 1380 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1381 postexpression_props_sql = self.properties( 1382 exp.Properties( 1383 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1384 ), 1385 sep=" ", 1386 prefix=" ", 1387 wrapped=False, 1388 ) 1389 1390 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1391 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1392 no_schema_binding = ( 1393 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1394 ) 1395 1396 clone = self.sql(expression, "clone") 1397 clone = f" {clone}" if clone else "" 1398 1399 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1400 properties_expression = f"{expression_sql}{properties_sql}" 1401 else: 1402 properties_expression = f"{properties_sql}{expression_sql}" 1403 1404 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1405 return self.prepend_ctes(expression, expression_sql) 1406 1407 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1408 start = self.sql(expression, "start") 1409 start = f"START WITH {start}" if start else "" 1410 increment = self.sql(expression, "increment") 1411 increment = f" INCREMENT BY {increment}" if increment else "" 1412 minvalue = self.sql(expression, "minvalue") 1413 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1414 maxvalue = self.sql(expression, "maxvalue") 1415 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1416 owned = self.sql(expression, "owned") 1417 owned = f" OWNED BY {owned}" if owned else "" 1418 1419 cache = expression.args.get("cache") 1420 if cache is None: 1421 cache_str = "" 1422 elif cache is True: 1423 cache_str = " CACHE" 1424 else: 1425 cache_str = f" CACHE {cache}" 1426 1427 options = self.expressions(expression, key="options", flat=True, sep=" ") 1428 options = f" {options}" if options else "" 1429 1430 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1431 1432 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1433 timing = expression.args.get("timing", "") 1434 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1435 timing_events = f"{timing} {events}".strip() if timing or events else "" 1436 1437 parts = [timing_events, "ON", self.sql(expression, "table")] 1438 1439 if referenced_table := expression.args.get("referenced_table"): 1440 parts.extend(["FROM", self.sql(referenced_table)]) 1441 1442 if deferrable := expression.args.get("deferrable"): 1443 parts.append(deferrable) 1444 1445 if initially := expression.args.get("initially"): 1446 parts.append(f"INITIALLY {initially}") 1447 1448 if referencing := expression.args.get("referencing"): 1449 parts.append(self.sql(referencing)) 1450 1451 if for_each := expression.args.get("for_each"): 1452 parts.append(f"FOR EACH {for_each}") 1453 1454 if when := expression.args.get("when"): 1455 parts.append(f"WHEN ({self.sql(when)})") 1456 1457 parts.append(self.sql(expression, "execute")) 1458 1459 return self.sep().join(parts) 1460 1461 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1462 parts = [] 1463 1464 if old_alias := expression.args.get("old"): 1465 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1466 1467 if new_alias := expression.args.get("new"): 1468 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1469 1470 return f"REFERENCING {' '.join(parts)}" 1471 1472 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1473 columns = expression.args.get("columns") 1474 if columns: 1475 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1476 1477 return self.sql(expression, "this") 1478 1479 def clone_sql(self, expression: exp.Clone) -> str: 1480 this = self.sql(expression, "this") 1481 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1482 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1483 return f"{shallow}{keyword} {this}" 1484 1485 def describe_sql(self, expression: exp.Describe) -> str: 1486 style = expression.args.get("style") 1487 style = f" {style}" if style else "" 1488 partition = self.sql(expression, "partition") 1489 partition = f" {partition}" if partition else "" 1490 format = self.sql(expression, "format") 1491 format = f" {format}" if format else "" 1492 as_json = " AS JSON" if expression.args.get("as_json") else "" 1493 1494 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1495 1496 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1497 tag = self.sql(expression, "tag") 1498 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1499 1500 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1501 with_ = self.sql(expression, "with_") 1502 if with_: 1503 sql = f"{with_}{self.sep()}{sql}" 1504 return sql 1505 1506 def with_sql(self, expression: exp.With) -> str: 1507 sql = self.expressions(expression, flat=True) 1508 recursive = ( 1509 "RECURSIVE " 1510 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1511 else "" 1512 ) 1513 search = self.sql(expression, "search") 1514 search = f" {search}" if search else "" 1515 1516 return f"WITH {recursive}{sql}{search}" 1517 1518 def cte_sql(self, expression: exp.CTE) -> str: 1519 alias = expression.args.get("alias") 1520 if alias: 1521 alias.add_comments(expression.pop_comments()) 1522 1523 alias_sql = self.sql(expression, "alias") 1524 1525 materialized = expression.args.get("materialized") 1526 if materialized is False: 1527 materialized = "NOT MATERIALIZED " 1528 elif materialized: 1529 materialized = "MATERIALIZED " 1530 1531 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1532 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1533 1534 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1535 1536 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1537 alias = self.sql(expression, "this") 1538 columns = self.expressions(expression, key="columns", flat=True) 1539 columns = f"({columns})" if columns else "" 1540 1541 if ( 1542 columns 1543 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1544 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1545 ): 1546 columns = "" 1547 self.unsupported("Named columns are not supported in table alias.") 1548 1549 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1550 alias = self._next_name() 1551 1552 return f"{alias}{columns}" 1553 1554 def bitstring_sql(self, expression: exp.BitString) -> str: 1555 this = self.sql(expression, "this") 1556 if self.dialect.BIT_START: 1557 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1558 return f"{int(this, 2)}" 1559 1560 def hexstring_sql( 1561 self, expression: exp.HexString, binary_function_repr: str | None = None 1562 ) -> str: 1563 this = self.sql(expression, "this") 1564 is_integer_type = expression.args.get("is_integer") 1565 1566 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1567 not self.dialect.HEX_START and not binary_function_repr 1568 ): 1569 # Integer representation will be returned if: 1570 # - The read dialect treats the hex value as integer literal but not the write 1571 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1572 return f"{int(this, 16)}" 1573 1574 if not is_integer_type: 1575 # Read dialect treats the hex value as BINARY/BLOB 1576 if binary_function_repr: 1577 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1578 return self.func(binary_function_repr, exp.Literal.string(this)) 1579 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1580 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1581 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1582 1583 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1584 1585 def bytestring_sql(self, expression: exp.ByteString) -> str: 1586 this = self.sql(expression, "this") 1587 if self.dialect.BYTE_START: 1588 escaped_byte_string = self.escape_str( 1589 this, 1590 escape_backslash=False, 1591 delimiter=self.dialect.BYTE_END, 1592 escaped_delimiter=self._escaped_byte_quote_end, 1593 is_byte_string=True, 1594 ) 1595 is_bytes = expression.args.get("is_bytes", False) 1596 delimited_byte_string = ( 1597 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1598 ) 1599 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1600 return self.sql( 1601 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1602 ) 1603 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1604 return self.sql( 1605 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1606 ) 1607 1608 return delimited_byte_string 1609 return this 1610 1611 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1612 this = self.sql(expression, "this") 1613 escape = expression.args.get("escape") 1614 1615 if self.dialect.UNICODE_START: 1616 escape_substitute = r"\\\1" 1617 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1618 else: 1619 escape_substitute = r"\\u\1" 1620 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1621 1622 if escape: 1623 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1624 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1625 else: 1626 escape_pattern = ESCAPED_UNICODE_RE 1627 escape_sql = "" 1628 1629 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1630 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1631 1632 return f"{left_quote}{this}{right_quote}{escape_sql}" 1633 1634 def rawstring_sql(self, expression: exp.RawString) -> str: 1635 string = expression.this 1636 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1637 string = string.replace("\\", "\\\\") 1638 1639 string = self.escape_str(string, escape_backslash=False) 1640 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1641 1642 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1643 this = self.sql(expression, "this") 1644 specifier = self.sql(expression, "expression") 1645 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1646 return f"{this}{specifier}" 1647 1648 def datatype_param_bound_limiter( 1649 self, 1650 expression: exp.DataType, 1651 type_value: exp.DType, 1652 defaults: tuple[int, ...], 1653 bounds: tuple[int | None, ...], 1654 ) -> exp.DataType: 1655 params = expression.expressions 1656 1657 if not params: 1658 if defaults: 1659 expression.set( 1660 "expressions", 1661 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1662 ) 1663 return expression 1664 1665 if not bounds: 1666 return expression 1667 1668 for i, param in enumerate(params): 1669 bound = bounds[i] if i < len(bounds) else None 1670 if bound is None: 1671 continue 1672 1673 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1674 if ( 1675 isinstance(param_value, exp.Literal) 1676 and param_value.is_number 1677 and int(param_value.to_py()) > bound 1678 ): 1679 self.unsupported( 1680 f"{type_value.value} parameter {param_value.name} exceeds " 1681 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1682 ) 1683 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1684 1685 return expression 1686 1687 def datatype_sql(self, expression: exp.DataType) -> str: 1688 nested = "" 1689 values = "" 1690 1691 expr_nested = expression.args.get("nested") 1692 type_value = expression.this 1693 1694 if ( 1695 not expr_nested 1696 and isinstance(type_value, exp.DType) 1697 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1698 ): 1699 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1700 1701 interior = ( 1702 self.expressions( 1703 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1704 ) 1705 if expr_nested and self.pretty 1706 else self.expressions(expression, flat=True) 1707 ) 1708 1709 if type_value in self.UNSUPPORTED_TYPES: 1710 self.unsupported( 1711 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1712 ) 1713 1714 type_sql: t.Any = "" 1715 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1716 type_sql = self.sql(expression, "kind") 1717 elif type_value == exp.DType.CHARACTER_SET: 1718 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1719 else: 1720 type_sql = ( 1721 self.TYPE_MAPPING.get(type_value, type_value.value) 1722 if isinstance(type_value, exp.DType) 1723 else type_value 1724 ) 1725 1726 if interior: 1727 if expr_nested: 1728 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1729 if expression.args.get("values") is not None: 1730 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1731 values = self.expressions(expression, key="values", flat=True) 1732 values = f"{delimiters[0]}{values}{delimiters[1]}" 1733 elif type_value == exp.DType.INTERVAL: 1734 nested = f" {interior}" 1735 else: 1736 nested = f"({interior})" 1737 1738 type_sql = f"{type_sql}{nested}{values}" 1739 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1740 exp.DType.TIMETZ, 1741 exp.DType.TIMESTAMPTZ, 1742 ): 1743 type_sql = f"{type_sql} WITH TIME ZONE" 1744 1745 collate = self.sql(expression, "collate") 1746 if collate: 1747 type_sql = f"{type_sql} COLLATE {collate}" 1748 1749 return type_sql 1750 1751 def directory_sql(self, expression: exp.Directory) -> str: 1752 local = "LOCAL " if expression.args.get("local") else "" 1753 row_format = self.sql(expression, "row_format") 1754 row_format = f" {row_format}" if row_format else "" 1755 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1756 1757 def delete_sql(self, expression: exp.Delete) -> str: 1758 hint = self.sql(expression, "hint") 1759 this = self.sql(expression, "this") 1760 this = f" FROM {this}" if this else "" 1761 using = self.expressions(expression, key="using") 1762 using = f" USING {using}" if using else "" 1763 cluster = self.sql(expression, "cluster") 1764 cluster = f" {cluster}" if cluster else "" 1765 where = self.sql(expression, "where") 1766 returning = self.sql(expression, "returning") 1767 order = self.sql(expression, "order") 1768 limit = self.sql(expression, "limit") 1769 tables = self.expressions(expression, key="tables") 1770 tables = f" {tables}" if tables else "" 1771 if self.RETURNING_END: 1772 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1773 else: 1774 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1775 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1776 1777 def drop_sql(self, expression: exp.Drop) -> str: 1778 this = self.sql(expression, "this") 1779 expressions = self.expressions(expression, flat=True) 1780 expressions = f" ({expressions})" if expressions else "" 1781 kind = expression.args["kind"] 1782 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1783 iceberg = ( 1784 " ICEBERG" 1785 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1786 else "" 1787 ) 1788 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1789 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1790 on_cluster = self.sql(expression, "cluster") 1791 on_cluster = f" {on_cluster}" if on_cluster else "" 1792 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1793 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1794 cascade = " CASCADE" if expression.args.get("cascade") else "" 1795 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1796 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1797 purge = " PURGE" if expression.args.get("purge") else "" 1798 sync = " SYNC" if expression.args.get("sync") else "" 1799 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}" 1800 1801 def set_operation(self, expression: exp.SetOperation) -> str: 1802 op_type = type(expression) 1803 op_name = op_type.key.upper() 1804 1805 distinct = expression.args.get("distinct") 1806 if ( 1807 distinct is False 1808 and op_type in (exp.Except, exp.Intersect) 1809 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1810 ): 1811 self.unsupported(f"{op_name} ALL is not supported") 1812 1813 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1814 1815 if distinct is None: 1816 distinct = default_distinct 1817 if distinct is None: 1818 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1819 1820 if distinct is default_distinct: 1821 distinct_or_all = "" 1822 else: 1823 distinct_or_all = " DISTINCT" if distinct else " ALL" 1824 1825 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1826 side_kind = f"{side_kind} " if side_kind else "" 1827 1828 by_name = " BY NAME" if expression.args.get("by_name") else "" 1829 on = self.expressions(expression, key="on", flat=True) 1830 on = f" ON ({on})" if on else "" 1831 1832 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1833 1834 def set_operations(self, expression: exp.SetOperation) -> str: 1835 if not self.SET_OP_MODIFIERS: 1836 limit = expression.args.get("limit") 1837 order = expression.args.get("order") 1838 1839 if limit or order: 1840 select = self._move_ctes_to_top_level( 1841 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1842 ) 1843 1844 if limit: 1845 select = select.limit(limit.pop(), copy=False) 1846 if order: 1847 select = select.order_by(order.pop(), copy=False) 1848 return self.sql(select) 1849 1850 sqls: list[str] = [] 1851 stack: list[str | exp.Expr] = [expression] 1852 1853 while stack: 1854 node = stack.pop() 1855 1856 if isinstance(node, exp.SetOperation): 1857 stack.append(node.expression) 1858 stack.append( 1859 self.maybe_comment( 1860 self.set_operation(node), comments=node.comments, separated=True 1861 ) 1862 ) 1863 stack.append(node.this) 1864 else: 1865 sqls.append(self.sql(node)) 1866 1867 this = self.sep().join(sqls) 1868 this = self.query_modifiers(expression, this) 1869 return self.prepend_ctes(expression, this) 1870 1871 def fetch_sql(self, expression: exp.Fetch) -> str: 1872 direction = expression.args.get("direction") 1873 direction = f" {direction}" if direction else "" 1874 count = self.sql(expression, "count") 1875 count = f" {count}" if count else "" 1876 limit_options = self.sql(expression, "limit_options") 1877 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1878 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1879 1880 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1881 percent = " PERCENT" if expression.args.get("percent") else "" 1882 rows = " ROWS" if expression.args.get("rows") else "" 1883 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1884 if not with_ties and rows: 1885 with_ties = " ONLY" 1886 return f"{percent}{rows}{with_ties}" 1887 1888 def filter_sql(self, expression: exp.Filter) -> str: 1889 if self.AGGREGATE_FILTER_SUPPORTED: 1890 this = self.sql(expression, "this") 1891 where = self.sql(expression, "expression").strip() 1892 return f"{this} FILTER({where})" 1893 1894 agg = expression.this 1895 agg_arg = agg.this 1896 cond = expression.expression.this 1897 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1898 return self.sql(agg) 1899 1900 def hint_sql(self, expression: exp.Hint) -> str: 1901 if not self.QUERY_HINTS: 1902 self.unsupported("Hints are not supported") 1903 return "" 1904 1905 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1906 1907 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1908 using = self.sql(expression, "using") 1909 using = f" USING {using}" if using else "" 1910 columns = self.expressions(expression, key="columns", flat=True) 1911 columns = f"({columns})" if columns else "" 1912 partition_by = self.expressions(expression, key="partition_by", flat=True) 1913 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1914 where = self.sql(expression, "where") 1915 include = self.expressions(expression, key="include", flat=True) 1916 if include: 1917 include = f" INCLUDE ({include})" 1918 with_storage = self.expressions(expression, key="with_storage", flat=True) 1919 with_storage = f" WITH ({with_storage})" if with_storage else "" 1920 tablespace = self.sql(expression, "tablespace") 1921 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1922 on = self.sql(expression, "on") 1923 on = f" ON {on}" if on else "" 1924 1925 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1926 1927 def index_sql(self, expression: exp.Index) -> str: 1928 unique = "UNIQUE " if expression.args.get("unique") else "" 1929 primary = "PRIMARY " if expression.args.get("primary") else "" 1930 amp = "AMP " if expression.args.get("amp") else "" 1931 name = self.sql(expression, "this") 1932 name = f"{name} " if name else "" 1933 table = self.sql(expression, "table") 1934 table = f"{self.INDEX_ON} {table}" if table else "" 1935 1936 index = "INDEX " if not table else "" 1937 1938 params = self.sql(expression, "params") 1939 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1940 1941 def identifier_sql(self, expression: exp.Identifier) -> str: 1942 text = expression.name 1943 lower = text.lower() 1944 quoted = expression.quoted 1945 text = lower if self.normalize and not quoted else text 1946 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1947 if ( 1948 quoted 1949 or self.dialect.can_quote(expression, self.identify) 1950 or lower in self.RESERVED_KEYWORDS 1951 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1952 ): 1953 text = ( 1954 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1955 ) 1956 return text 1957 1958 def hex_sql(self, expression: exp.Hex) -> str: 1959 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1960 if self.dialect.HEX_LOWERCASE: 1961 text = self.func("LOWER", text) 1962 1963 return text 1964 1965 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1966 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1967 if not self.dialect.HEX_LOWERCASE: 1968 text = self.func("LOWER", text) 1969 return text 1970 1971 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1972 input_format = self.sql(expression, "input_format") 1973 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1974 output_format = self.sql(expression, "output_format") 1975 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1976 return self.sep().join((input_format, output_format)) 1977 1978 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1979 string = self.sql(exp.Literal.string(expression.name)) 1980 return f"{prefix}{string}" 1981 1982 def partition_sql(self, expression: exp.Partition) -> str: 1983 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1984 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1985 1986 def properties_sql(self, expression: exp.Properties) -> str: 1987 root_properties = [] 1988 with_properties = [] 1989 1990 for p in expression.expressions: 1991 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1992 if p_loc == exp.Properties.Location.POST_WITH: 1993 with_properties.append(p) 1994 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1995 root_properties.append(p) 1996 1997 root_props_ast = exp.Properties(expressions=root_properties) 1998 root_props_ast.parent = expression.parent 1999 2000 with_props_ast = exp.Properties(expressions=with_properties) 2001 with_props_ast.parent = expression.parent 2002 2003 root_props = self.root_properties(root_props_ast) 2004 with_props = self.with_properties(with_props_ast) 2005 2006 if root_props and with_props and not self.pretty: 2007 with_props = " " + with_props 2008 2009 return root_props + with_props 2010 2011 def root_properties(self, properties: exp.Properties) -> str: 2012 if properties.expressions: 2013 return self.expressions(properties, indent=False, sep=" ") 2014 return "" 2015 2016 def properties( 2017 self, 2018 properties: exp.Properties, 2019 prefix: str = "", 2020 sep: str = ", ", 2021 suffix: str = "", 2022 wrapped: bool = True, 2023 ) -> str: 2024 if properties.expressions: 2025 expressions = self.expressions(properties, sep=sep, indent=False) 2026 if expressions: 2027 expressions = self.wrap(expressions) if wrapped else expressions 2028 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2029 return "" 2030 2031 def with_properties(self, properties: exp.Properties) -> str: 2032 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 2033 2034 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2035 properties_locs = defaultdict(list) 2036 for p in properties.expressions: 2037 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2038 if p_loc != exp.Properties.Location.UNSUPPORTED: 2039 properties_locs[p_loc].append(p) 2040 else: 2041 self.unsupported(f"Unsupported property {p.key}") 2042 2043 return properties_locs 2044 2045 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 2046 if isinstance(expression.this, exp.Dot): 2047 return self.sql(expression, "this") 2048 return f"'{expression.name}'" if string_key else expression.name 2049 2050 def property_sql(self, expression: exp.Property) -> str: 2051 property_cls = expression.__class__ 2052 if property_cls == exp.Property: 2053 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2054 2055 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2056 if not property_name: 2057 self.unsupported(f"Unsupported property {expression.key}") 2058 2059 return f"{property_name}={self.sql(expression, 'this')}" 2060 2061 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 2062 return f"UUID {self.sql(expression, 'this')}" 2063 2064 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2065 if self.SUPPORTS_CREATE_TABLE_LIKE: 2066 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2067 options = f" {options}" if options else "" 2068 2069 like = f"LIKE {self.sql(expression, 'this')}{options}" 2070 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2071 like = f"({like})" 2072 2073 return like 2074 2075 if expression.expressions: 2076 self.unsupported("Transpilation of LIKE property options is unsupported") 2077 2078 select = exp.select("*").from_(expression.this).limit(0) 2079 return f"AS {self.sql(select)}" 2080 2081 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2082 no = "NO " if expression.args.get("no") else "" 2083 protection = " PROTECTION" if expression.args.get("protection") else "" 2084 return f"{no}FALLBACK{protection}" 2085 2086 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2087 no = "NO " if expression.args.get("no") else "" 2088 local = expression.args.get("local") 2089 local = f"{local} " if local else "" 2090 dual = "DUAL " if expression.args.get("dual") else "" 2091 before = "BEFORE " if expression.args.get("before") else "" 2092 after = "AFTER " if expression.args.get("after") else "" 2093 return f"{no}{local}{dual}{before}{after}JOURNAL" 2094 2095 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2096 freespace = self.sql(expression, "this") 2097 percent = " PERCENT" if expression.args.get("percent") else "" 2098 return f"FREESPACE={freespace}{percent}" 2099 2100 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2101 if expression.args.get("default"): 2102 property = "DEFAULT" 2103 elif expression.args.get("on"): 2104 property = "ON" 2105 else: 2106 property = "OFF" 2107 return f"CHECKSUM={property}" 2108 2109 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2110 if expression.args.get("no"): 2111 return "NO MERGEBLOCKRATIO" 2112 if expression.args.get("default"): 2113 return "DEFAULT MERGEBLOCKRATIO" 2114 2115 percent = " PERCENT" if expression.args.get("percent") else "" 2116 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2117 2118 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2119 expressions = self.expressions(expression, flat=True) 2120 expressions = f"({expressions})" if expressions else "" 2121 return f"USING {self.sql(expression, 'this')}{expressions}" 2122 2123 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2124 default = expression.args.get("default") 2125 minimum = expression.args.get("minimum") 2126 maximum = expression.args.get("maximum") 2127 if default or minimum or maximum: 2128 if default: 2129 prop = "DEFAULT" 2130 elif minimum: 2131 prop = "MINIMUM" 2132 else: 2133 prop = "MAXIMUM" 2134 return f"{prop} DATABLOCKSIZE" 2135 units = expression.args.get("units") 2136 units = f" {units}" if units else "" 2137 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2138 2139 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2140 autotemp = expression.args.get("autotemp") 2141 always = expression.args.get("always") 2142 default = expression.args.get("default") 2143 manual = expression.args.get("manual") 2144 never = expression.args.get("never") 2145 2146 if autotemp is not None: 2147 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2148 elif always: 2149 prop = "ALWAYS" 2150 elif default: 2151 prop = "DEFAULT" 2152 elif manual: 2153 prop = "MANUAL" 2154 elif never: 2155 prop = "NEVER" 2156 return f"BLOCKCOMPRESSION={prop}" 2157 2158 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2159 no = expression.args.get("no") 2160 no = " NO" if no else "" 2161 concurrent = expression.args.get("concurrent") 2162 concurrent = " CONCURRENT" if concurrent else "" 2163 target = self.sql(expression, "target") 2164 target = f" {target}" if target else "" 2165 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2166 2167 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2168 if isinstance(expression.this, list): 2169 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2170 if expression.this: 2171 modulus = self.sql(expression, "this") 2172 remainder = self.sql(expression, "expression") 2173 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2174 2175 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2176 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2177 return f"FROM ({from_expressions}) TO ({to_expressions})" 2178 2179 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2180 this = self.sql(expression, "this") 2181 2182 for_values_or_default = expression.expression 2183 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2184 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2185 else: 2186 for_values_or_default = " DEFAULT" 2187 2188 return f"PARTITION OF {this}{for_values_or_default}" 2189 2190 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2191 kind = expression.args.get("kind") 2192 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2193 for_or_in = expression.args.get("for_or_in") 2194 for_or_in = f" {for_or_in}" if for_or_in else "" 2195 lock_type = expression.args.get("lock_type") 2196 override = " OVERRIDE" if expression.args.get("override") else "" 2197 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2198 2199 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2200 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2201 statistics = expression.args.get("statistics") 2202 statistics_sql = "" 2203 if statistics is not None: 2204 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2205 return f"{data_sql}{statistics_sql}" 2206 2207 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2208 this = self.sql(expression, "this") 2209 this = f"HISTORY_TABLE={this}" if this else "" 2210 data_consistency: str | None = self.sql(expression, "data_consistency") 2211 data_consistency = ( 2212 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2213 ) 2214 retention_period: str | None = self.sql(expression, "retention_period") 2215 retention_period = ( 2216 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2217 ) 2218 2219 if this: 2220 on_sql = self.func("ON", this, data_consistency, retention_period) 2221 else: 2222 on_sql = "ON" if expression.args.get("on") else "OFF" 2223 2224 sql = f"SYSTEM_VERSIONING={on_sql}" 2225 2226 return f"WITH({sql})" if expression.args.get("with_") else sql 2227 2228 def insert_sql(self, expression: exp.Insert) -> str: 2229 hint = self.sql(expression, "hint") 2230 overwrite = expression.args.get("overwrite") 2231 2232 if isinstance(expression.this, exp.Directory): 2233 this = " OVERWRITE" if overwrite else " INTO" 2234 else: 2235 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2236 2237 stored = self.sql(expression, "stored") 2238 stored = f" {stored}" if stored else "" 2239 alternative = expression.args.get("alternative") 2240 alternative = f" OR {alternative}" if alternative else "" 2241 ignore = " IGNORE" if expression.args.get("ignore") else "" 2242 is_function = expression.args.get("is_function") 2243 if is_function: 2244 this = f"{this} FUNCTION" 2245 this = f"{this} {self.sql(expression, 'this')}" 2246 2247 exists = " IF EXISTS" if expression.args.get("exists") else "" 2248 where = self.sql(expression, "where") 2249 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2250 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2251 on_conflict = self.sql(expression, "conflict") 2252 on_conflict = f" {on_conflict}" if on_conflict else "" 2253 by_name = " BY NAME" if expression.args.get("by_name") else "" 2254 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2255 returning = self.sql(expression, "returning") 2256 2257 if self.RETURNING_END: 2258 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2259 else: 2260 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2261 2262 partition_by = self.sql(expression, "partition") 2263 partition_by = f" {partition_by}" if partition_by else "" 2264 settings = self.sql(expression, "settings") 2265 settings = f" {settings}" if settings else "" 2266 2267 source = self.sql(expression, "source") 2268 source = f"TABLE {source}" if source else "" 2269 2270 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2271 return self.prepend_ctes(expression, sql) 2272 2273 def introducer_sql(self, expression: exp.Introducer) -> str: 2274 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2275 2276 def kill_sql(self, expression: exp.Kill) -> str: 2277 kind = self.sql(expression, "kind") 2278 kind = f" {kind}" if kind else "" 2279 this = self.sql(expression, "this") 2280 this = f" {this}" if this else "" 2281 return f"KILL{kind}{this}" 2282 2283 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2284 return expression.name 2285 2286 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2287 return expression.name 2288 2289 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2290 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2291 2292 constraint = self.sql(expression, "constraint") 2293 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2294 2295 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2296 if conflict_keys: 2297 conflict_keys = f"({conflict_keys})" 2298 2299 index_predicate = self.sql(expression, "index_predicate") 2300 conflict_keys = f"{conflict_keys}{index_predicate} " 2301 2302 action = self.sql(expression, "action") 2303 2304 expressions = self.expressions(expression, flat=True) 2305 if expressions: 2306 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2307 expressions = f" {set_keyword}{expressions}" 2308 2309 where = self.sql(expression, "where") 2310 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2311 2312 def returning_sql(self, expression: exp.Returning) -> str: 2313 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2314 2315 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2316 fields = self.sql(expression, "fields") 2317 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2318 escaped = self.sql(expression, "escaped") 2319 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2320 items = self.sql(expression, "collection_items") 2321 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2322 keys = self.sql(expression, "map_keys") 2323 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2324 lines = self.sql(expression, "lines") 2325 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2326 null = self.sql(expression, "null") 2327 null = f" NULL DEFINED AS {null}" if null else "" 2328 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2329 2330 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2331 return f"WITH ({self.expressions(expression, flat=True)})" 2332 2333 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2334 this = f"{self.sql(expression, 'this')} INDEX" 2335 target = self.sql(expression, "target") 2336 target = f" FOR {target}" if target else "" 2337 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2338 2339 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2340 this = self.sql(expression, "this") 2341 kind = self.sql(expression, "kind") 2342 expr = self.sql(expression, "expression") 2343 return f"{this} ({kind} => {expr})" 2344 2345 def table_parts(self, expression: exp.Table) -> str: 2346 return ".".join( 2347 self.sql(part) 2348 for part in ( 2349 expression.args.get("catalog"), 2350 expression.args.get("db"), 2351 expression.args.get("this"), 2352 ) 2353 if part is not None 2354 ) 2355 2356 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2357 table = self.table_parts(expression) 2358 only = "ONLY " if expression.args.get("only") else "" 2359 partition = self.sql(expression, "partition") 2360 partition = f" {partition}" if partition else "" 2361 version = self.sql(expression, "version") 2362 version = f" {version}" if version else "" 2363 alias = self.sql(expression, "alias") 2364 alias = f"{sep}{alias}" if alias else "" 2365 2366 sample = self.sql(expression, "sample") 2367 post_alias = "" 2368 pre_alias = "" 2369 2370 if self.dialect.ALIAS_POST_TABLESAMPLE: 2371 pre_alias = sample 2372 else: 2373 post_alias = sample 2374 2375 if self.dialect.ALIAS_POST_VERSION: 2376 pre_alias = f"{pre_alias}{version}" 2377 else: 2378 post_alias = f"{post_alias}{version}" 2379 2380 hints = self.expressions(expression, key="hints", sep=" ") 2381 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2382 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2383 joins = self.indent( 2384 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2385 ) 2386 laterals = self.expressions(expression, key="laterals", sep="") 2387 2388 file_format = self.sql(expression, "format") 2389 if file_format: 2390 pattern = self.sql(expression, "pattern") 2391 pattern = f", PATTERN => {pattern}" if pattern else "" 2392 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2393 2394 ordinality = expression.args.get("ordinality") or "" 2395 if ordinality: 2396 ordinality = f" WITH ORDINALITY{alias}" 2397 alias = "" 2398 2399 when = self.sql(expression, "when") 2400 if when: 2401 table = f"{table} {when}" 2402 2403 changes = self.sql(expression, "changes") 2404 changes = f" {changes}" if changes else "" 2405 2406 rows_from = self.expressions(expression, key="rows_from") 2407 if rows_from: 2408 table = f"ROWS FROM {self.wrap(rows_from)}" 2409 2410 indexed = expression.args.get("indexed") 2411 if indexed is not None: 2412 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2413 else: 2414 indexed = "" 2415 2416 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2417 2418 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2419 table = self.func("TABLE", expression.this) 2420 alias = self.sql(expression, "alias") 2421 alias = f" AS {alias}" if alias else "" 2422 sample = self.sql(expression, "sample") 2423 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2424 joins = self.indent( 2425 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2426 ) 2427 return f"{table}{alias}{pivots}{sample}{joins}" 2428 2429 def tablesample_sql( 2430 self, 2431 expression: exp.TableSample, 2432 tablesample_keyword: str | None = None, 2433 ) -> str: 2434 method = self.sql(expression, "method") 2435 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2436 numerator = self.sql(expression, "bucket_numerator") 2437 denominator = self.sql(expression, "bucket_denominator") 2438 field = self.sql(expression, "bucket_field") 2439 field = f" ON {field}" if field else "" 2440 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2441 seed = self.sql(expression, "seed") 2442 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2443 2444 size = self.sql(expression, "size") 2445 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2446 size = f"{size} ROWS" 2447 2448 percent = self.sql(expression, "percent") 2449 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2450 percent = f"{percent} PERCENT" 2451 2452 expr = f"{bucket}{percent}{size}" 2453 if self.TABLESAMPLE_REQUIRES_PARENS: 2454 expr = f"({expr})" 2455 2456 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2457 2458 def pivot_sql(self, expression: exp.Pivot) -> str: 2459 expressions = self.expressions(expression, flat=True) 2460 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2461 2462 group = self.sql(expression, "group") 2463 2464 if expression.this: 2465 this = self.sql(expression, "this") 2466 if not expressions: 2467 sql = f"UNPIVOT {this}" 2468 else: 2469 on = f"{self.seg('ON')} {expressions}" 2470 into = self.sql(expression, "into") 2471 into = f"{self.seg('INTO')} {into}" if into else "" 2472 using = self.expressions(expression, key="using", flat=True) 2473 using = f"{self.seg('USING')} {using}" if using else "" 2474 sql = f"{direction} {this}{on}{into}{using}{group}" 2475 return self.prepend_ctes(expression, sql) 2476 2477 alias = self.sql(expression, "alias") 2478 alias = f" AS {alias}" if alias else "" 2479 2480 fields = self.expressions( 2481 expression, 2482 "fields", 2483 sep=" ", 2484 dynamic=True, 2485 new_line=True, 2486 skip_first=True, 2487 skip_last=True, 2488 ) 2489 2490 include_nulls = expression.args.get("include_nulls") 2491 if include_nulls is not None: 2492 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2493 else: 2494 nulls = "" 2495 2496 default_on_null = self.sql(expression, "default_on_null") 2497 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2498 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2499 return self.prepend_ctes(expression, sql) 2500 2501 def version_sql(self, expression: exp.Version) -> str: 2502 this = f"FOR {expression.name}" 2503 kind = expression.text("kind") 2504 expr = self.sql(expression, "expression") 2505 return f"{this} {kind} {expr}" 2506 2507 def tuple_sql(self, expression: exp.Tuple) -> str: 2508 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2509 2510 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2511 """ 2512 Returns (join_sql, from_sql) for UPDATE statements. 2513 - join_sql: placed after UPDATE table, before SET 2514 - from_sql: placed after SET clause (standard position) 2515 Dialects like MySQL need to convert FROM to JOIN syntax. 2516 """ 2517 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2518 return ("", self.sql(expression, "from_")) 2519 2520 # Qualify unqualified columns in SET clause with the target table 2521 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2522 target_table = expression.this 2523 if isinstance(target_table, exp.Table): 2524 target_name = exp.to_identifier(target_table.alias_or_name) 2525 for eq in expression.expressions: 2526 col = eq.this 2527 if isinstance(col, exp.Column) and not col.table: 2528 col.set("table", target_name) 2529 2530 table = from_expr.this 2531 if nested_joins := table.args.get("joins", []): 2532 table.set("joins", None) 2533 2534 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2535 for nested in nested_joins: 2536 if not nested.args.get("on") and not nested.args.get("using"): 2537 nested.set("on", exp.true()) 2538 join_sql += self.sql(nested) 2539 2540 return (join_sql, "") 2541 2542 def update_sql(self, expression: exp.Update) -> str: 2543 hint = self.sql(expression, "hint") 2544 this = self.sql(expression, "this") 2545 join_sql, from_sql = self._update_from_joins_sql(expression) 2546 set_sql = self.expressions(expression, flat=True) 2547 where_sql = self.sql(expression, "where") 2548 returning = self.sql(expression, "returning") 2549 order = self.sql(expression, "order") 2550 limit = self.sql(expression, "limit") 2551 if self.RETURNING_END: 2552 expression_sql = f"{from_sql}{where_sql}{returning}" 2553 else: 2554 expression_sql = f"{returning}{from_sql}{where_sql}" 2555 options = self.expressions(expression, key="options") 2556 options = f" OPTION({options})" if options else "" 2557 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2558 return self.prepend_ctes(expression, sql) 2559 2560 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2561 values_as_table = values_as_table and self.VALUES_AS_TABLE 2562 2563 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2564 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2565 args = self.expressions(expression) 2566 alias = self.sql(expression, "alias") 2567 values = f"VALUES{self.seg('')}{args}" 2568 values = ( 2569 f"({values})" 2570 if self.WRAP_DERIVED_VALUES 2571 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2572 else values 2573 ) 2574 values = self.query_modifiers(expression, values) 2575 return f"{values} AS {alias}" if alias else values 2576 2577 # Converts `VALUES...` expression into a series of select unions. 2578 alias_node = expression.args.get("alias") 2579 column_names = alias_node and alias_node.columns 2580 2581 selects: list[exp.Query] = [] 2582 2583 for i, tup in enumerate(expression.expressions): 2584 row = tup.expressions 2585 2586 if i == 0 and column_names: 2587 row = [ 2588 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2589 ] 2590 2591 selects.append(exp.Select(expressions=row)) 2592 2593 if self.pretty: 2594 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2595 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2596 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2597 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2598 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2599 2600 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2601 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2602 return f"({unions}){alias}" 2603 2604 def var_sql(self, expression: exp.Var) -> str: 2605 return self.sql(expression, "this") 2606 2607 @unsupported_args("expressions") 2608 def into_sql(self, expression: exp.Into) -> str: 2609 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2610 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2611 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2612 2613 def from_sql(self, expression: exp.From) -> str: 2614 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2615 2616 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2617 grouping_sets = self.expressions(expression, indent=False) 2618 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2619 2620 def rollup_sql(self, expression: exp.Rollup) -> str: 2621 expressions = self.expressions(expression, indent=False) 2622 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2623 2624 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2625 this = self.sql(expression, "this") 2626 2627 columns = self.expressions(expression, flat=True) 2628 2629 from_sql = self.sql(expression, "from_index") 2630 from_sql = f" FROM {from_sql}" if from_sql else "" 2631 2632 properties = expression.args.get("properties") 2633 properties_sql = ( 2634 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2635 ) 2636 2637 return f"{this}({columns}){from_sql}{properties_sql}" 2638 2639 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2640 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2641 2642 def cube_sql(self, expression: exp.Cube) -> str: 2643 expressions = self.expressions(expression, indent=False) 2644 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2645 2646 def group_sql(self, expression: exp.Group) -> str: 2647 group_by_all = expression.args.get("all") 2648 if group_by_all is True: 2649 modifier = " ALL" 2650 elif group_by_all is False: 2651 modifier = " DISTINCT" 2652 else: 2653 modifier = "" 2654 2655 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2656 2657 grouping_sets = self.expressions(expression, key="grouping_sets") 2658 cube = self.expressions(expression, key="cube") 2659 rollup = self.expressions(expression, key="rollup") 2660 2661 groupings = csv( 2662 self.seg(grouping_sets) if grouping_sets else "", 2663 self.seg(cube) if cube else "", 2664 self.seg(rollup) if rollup else "", 2665 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2666 sep=self.GROUPINGS_SEP, 2667 ) 2668 2669 if ( 2670 expression.expressions 2671 and groupings 2672 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2673 ): 2674 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2675 2676 return f"{group_by}{groupings}" 2677 2678 def having_sql(self, expression: exp.Having) -> str: 2679 this = self.indent(self.sql(expression, "this")) 2680 return f"{self.seg('HAVING')}{self.sep()}{this}" 2681 2682 def connect_sql(self, expression: exp.Connect) -> str: 2683 start = self.sql(expression, "start") 2684 start = self.seg(f"START WITH {start}") if start else "" 2685 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2686 connect = self.sql(expression, "connect") 2687 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2688 return start + connect 2689 2690 def prior_sql(self, expression: exp.Prior) -> str: 2691 return f"PRIOR {self.sql(expression, 'this')}" 2692 2693 def join_sql(self, expression: exp.Join) -> str: 2694 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2695 side = None 2696 else: 2697 side = expression.side 2698 2699 op_sql = " ".join( 2700 op 2701 for op in ( 2702 expression.method, 2703 "GLOBAL" if expression.args.get("global_") else None, 2704 side, 2705 expression.kind, 2706 expression.hint if self.JOIN_HINTS else None, 2707 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2708 ) 2709 if op 2710 ) 2711 match_cond = self.sql(expression, "match_condition") 2712 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2713 on_sql = self.sql(expression, "on") 2714 using = expression.args.get("using") 2715 2716 if not on_sql and using: 2717 on_sql = csv(*(self.sql(column) for column in using)) 2718 2719 this = expression.this 2720 this_sql = self.sql(this) 2721 2722 exprs = self.expressions(expression) 2723 if exprs: 2724 this_sql = f"{this_sql},{self.seg(exprs)}" 2725 2726 if on_sql: 2727 on_sql = self.indent(on_sql, skip_first=True) 2728 space = self.seg(" " * self.pad) if self.pretty else " " 2729 if using: 2730 on_sql = f"{space}USING ({on_sql})" 2731 else: 2732 on_sql = f"{space}ON {on_sql}" 2733 elif not op_sql: 2734 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2735 return f" {this_sql}" 2736 2737 return f", {this_sql}" 2738 2739 if op_sql != "STRAIGHT_JOIN": 2740 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2741 2742 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2743 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2744 2745 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2746 args = self.expressions(expression, flat=True) 2747 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2748 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2749 2750 def lateral_op(self, expression: exp.Lateral) -> str: 2751 cross_apply = expression.args.get("cross_apply") 2752 2753 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2754 if cross_apply is True: 2755 op = "INNER JOIN " 2756 elif cross_apply is False: 2757 op = "LEFT JOIN " 2758 else: 2759 op = "" 2760 2761 return f"{op}LATERAL" 2762 2763 def lateral_sql(self, expression: exp.Lateral) -> str: 2764 this = self.sql(expression, "this") 2765 2766 if expression.args.get("view"): 2767 alias = expression.args["alias"] 2768 columns = self.expressions(alias, key="columns", flat=True) 2769 table = f" {alias.name}" if alias.name else "" 2770 columns = f" AS {columns}" if columns else "" 2771 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2772 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2773 2774 alias = self.sql(expression, "alias") 2775 alias = f" AS {alias}" if alias else "" 2776 2777 ordinality = expression.args.get("ordinality") or "" 2778 if ordinality: 2779 ordinality = f" WITH ORDINALITY{alias}" 2780 alias = "" 2781 2782 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2783 2784 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2785 this = self.sql(expression, "this") 2786 2787 args = [ 2788 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2789 for e in (expression.args.get(k) for k in ("offset", "expression")) 2790 if e 2791 ] 2792 2793 args_sql = ", ".join(self.sql(e) for e in args) 2794 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2795 expressions = self.expressions(expression, flat=True) 2796 limit_options = self.sql(expression, "limit_options") 2797 expressions = f" BY {expressions}" if expressions else "" 2798 2799 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2800 2801 def offset_sql(self, expression: exp.Offset) -> str: 2802 this = self.sql(expression, "this") 2803 value = expression.expression 2804 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2805 expressions = self.expressions(expression, flat=True) 2806 expressions = f" BY {expressions}" if expressions else "" 2807 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2808 2809 def setitem_sql(self, expression: exp.SetItem) -> str: 2810 kind = self.sql(expression, "kind") 2811 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2812 kind = "" 2813 else: 2814 kind = f"{kind} " if kind else "" 2815 this = self.sql(expression, "this") 2816 expressions = self.expressions(expression) 2817 collate = self.sql(expression, "collate") 2818 collate = f" COLLATE {collate}" if collate else "" 2819 global_ = "GLOBAL " if expression.args.get("global_") else "" 2820 return f"{global_}{kind}{this}{expressions}{collate}" 2821 2822 def set_sql(self, expression: exp.Set) -> str: 2823 expressions = f" {self.expressions(expression, flat=True)}" 2824 tag = " TAG" if expression.args.get("tag") else "" 2825 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2826 2827 def queryband_sql(self, expression: exp.QueryBand) -> str: 2828 this = self.sql(expression, "this") 2829 update = " UPDATE" if expression.args.get("update") else "" 2830 scope = self.sql(expression, "scope") 2831 scope = f" FOR {scope}" if scope else "" 2832 2833 return f"QUERY_BAND = {this}{update}{scope}" 2834 2835 def pragma_sql(self, expression: exp.Pragma) -> str: 2836 return f"PRAGMA {self.sql(expression, 'this')}" 2837 2838 def lock_sql(self, expression: exp.Lock) -> str: 2839 if not self.LOCKING_READS_SUPPORTED: 2840 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2841 return "" 2842 2843 update = expression.args["update"] 2844 key = expression.args.get("key") 2845 if update: 2846 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2847 else: 2848 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2849 expressions = self.expressions(expression, flat=True) 2850 expressions = f" OF {expressions}" if expressions else "" 2851 wait = expression.args.get("wait") 2852 2853 if wait is not None: 2854 if isinstance(wait, exp.Literal): 2855 wait = f" WAIT {self.sql(wait)}" 2856 else: 2857 wait = " NOWAIT" if wait else " SKIP LOCKED" 2858 2859 return f"{lock_type}{expressions}{wait or ''}" 2860 2861 def literal_sql(self, expression: exp.Literal) -> str: 2862 text = expression.this or "" 2863 if expression.is_string: 2864 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2865 return text 2866 2867 def escape_str( 2868 self, 2869 text: str, 2870 escape_backslash: bool = True, 2871 delimiter: str | None = None, 2872 escaped_delimiter: str | None = None, 2873 is_byte_string: bool = False, 2874 ) -> str: 2875 if is_byte_string: 2876 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2877 else: 2878 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2879 2880 if supports_escape_sequences: 2881 text = "".join( 2882 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2883 for ch in text 2884 ) 2885 2886 delimiter = delimiter or self.dialect.QUOTE_END 2887 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2888 2889 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2890 2891 def loaddata_sql(self, expression: exp.LoadData) -> str: 2892 is_overwrite = expression.args.get("overwrite") 2893 overwrite = " OVERWRITE" if is_overwrite else "" 2894 this = self.sql(expression, "this") 2895 2896 files = expression.args.get("files") 2897 if files: 2898 files_sql = self.expressions(files, flat=True) 2899 files_sql = f"FILES{self.wrap(files_sql)}" 2900 this = f" {this}" if is_overwrite else f" INTO TABLE {this}" 2901 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2902 2903 local = " LOCAL" if expression.args.get("local") else "" 2904 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2905 this = f" INTO TABLE {this}" 2906 partition = self.sql(expression, "partition") 2907 partition = f" {partition}" if partition else "" 2908 input_format = self.sql(expression, "input_format") 2909 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2910 serde = self.sql(expression, "serde") 2911 serde = f" SERDE {serde}" if serde else "" 2912 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2913 2914 def null_sql(self, *_) -> str: 2915 return "NULL" 2916 2917 def boolean_sql(self, expression: exp.Boolean) -> str: 2918 return "TRUE" if expression.this else "FALSE" 2919 2920 def booland_sql(self, expression: exp.Booland) -> str: 2921 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2922 2923 def boolor_sql(self, expression: exp.Boolor) -> str: 2924 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2925 2926 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2927 this = self.sql(expression, "this") 2928 this = f"{this} " if this else this 2929 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2930 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 2931 2932 def withfill_sql(self, expression: exp.WithFill) -> str: 2933 from_sql = self.sql(expression, "from_") 2934 from_sql = f" FROM {from_sql}" if from_sql else "" 2935 to_sql = self.sql(expression, "to") 2936 to_sql = f" TO {to_sql}" if to_sql else "" 2937 step_sql = self.sql(expression, "step") 2938 step_sql = f" STEP {step_sql}" if step_sql else "" 2939 interpolated_values = [ 2940 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2941 if isinstance(e, exp.Alias) 2942 else self.sql(e, "this") 2943 for e in expression.args.get("interpolate") or [] 2944 ] 2945 interpolate = ( 2946 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2947 ) 2948 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2949 2950 def cluster_sql(self, expression: exp.Cluster) -> str: 2951 return self.op_expressions("CLUSTER BY", expression) 2952 2953 def distribute_sql(self, expression: exp.Distribute) -> str: 2954 return self.op_expressions("DISTRIBUTE BY", expression) 2955 2956 def sort_sql(self, expression: exp.Sort) -> str: 2957 return self.op_expressions("SORT BY", expression) 2958 2959 def ordered_sql(self, expression: exp.Ordered) -> str: 2960 desc = expression.args.get("desc") 2961 asc = not desc 2962 2963 nulls_first = expression.args.get("nulls_first") 2964 nulls_last = not nulls_first 2965 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2966 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2967 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2968 2969 this = self.sql(expression, "this") 2970 2971 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2972 nulls_sort_change = "" 2973 if nulls_first and ( 2974 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2975 ): 2976 nulls_sort_change = " NULLS FIRST" 2977 elif ( 2978 nulls_last 2979 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2980 and not nulls_are_last 2981 ): 2982 nulls_sort_change = " NULLS LAST" 2983 2984 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2985 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2986 window = expression.find_ancestor(exp.Window, exp.Select) 2987 2988 if isinstance(window, exp.Window): 2989 window_this = window.this 2990 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 2991 window_this = window_this.this 2992 spec = window.args.get("spec") 2993 else: 2994 window_this = None 2995 spec = None 2996 2997 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 2998 # without a spec or with a ROWS spec, but not with RANGE 2999 if not ( 3000 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3001 and (not spec or spec.text("kind").upper() == "ROWS") 3002 ): 3003 if window_this and spec: 3004 self.unsupported( 3005 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3006 ) 3007 nulls_sort_change = "" 3008 elif self.NULL_ORDERING_SUPPORTED is False and ( 3009 (asc and nulls_sort_change == " NULLS LAST") 3010 or (desc and nulls_sort_change == " NULLS FIRST") 3011 ): 3012 # BigQuery does not allow these ordering/nulls combinations when used under 3013 # an aggregation func or under a window containing one 3014 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3015 3016 if isinstance(ancestor, exp.Window): 3017 ancestor = ancestor.this 3018 if isinstance(ancestor, exp.AggFunc): 3019 self.unsupported( 3020 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3021 ) 3022 nulls_sort_change = "" 3023 elif self.NULL_ORDERING_SUPPORTED is None: 3024 if expression.this.is_int: 3025 self.unsupported( 3026 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3027 ) 3028 elif not isinstance(expression.this, exp.Rand): 3029 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3030 this = ( 3031 f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 3032 ) 3033 nulls_sort_change = "" 3034 3035 with_fill = self.sql(expression, "with_fill") 3036 with_fill = f" {with_fill}" if with_fill else "" 3037 3038 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 3039 3040 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 3041 window_frame = self.sql(expression, "window_frame") 3042 window_frame = f"{window_frame} " if window_frame else "" 3043 3044 this = self.sql(expression, "this") 3045 3046 return f"{window_frame}{this}" 3047 3048 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3049 partition = self.partition_by_sql(expression) 3050 order = self.sql(expression, "order") 3051 measures = self.expressions(expression, key="measures") 3052 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3053 rows = self.sql(expression, "rows") 3054 rows = self.seg(rows) if rows else "" 3055 after = self.sql(expression, "after") 3056 after = self.seg(after) if after else "" 3057 pattern = self.sql(expression, "pattern") 3058 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3059 definition_sqls = [ 3060 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3061 for definition in expression.args.get("define", []) 3062 ] 3063 definitions = self.expressions(sqls=definition_sqls) 3064 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3065 body = "".join( 3066 ( 3067 partition, 3068 order, 3069 measures, 3070 rows, 3071 after, 3072 pattern, 3073 define, 3074 ) 3075 ) 3076 alias = self.sql(expression, "alias") 3077 alias = f" {alias}" if alias else "" 3078 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3079 3080 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3081 limit = expression.args.get("limit") 3082 3083 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3084 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3085 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3086 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3087 3088 return csv( 3089 *sqls, 3090 *[self.sql(join) for join in expression.args.get("joins") or []], 3091 self.sql(expression, "match"), 3092 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3093 self.sql(expression, "prewhere"), 3094 self.sql(expression, "where"), 3095 self.sql(expression, "connect"), 3096 self.sql(expression, "group"), 3097 self.sql(expression, "having"), 3098 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3099 self.sql(expression, "order"), 3100 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3101 *self.after_limit_modifiers(expression), 3102 self.options_modifier(expression), 3103 self.for_modifiers(expression), 3104 sep="", 3105 ) 3106 3107 def options_modifier(self, expression: exp.Expr) -> str: 3108 options = self.expressions(expression, key="options") 3109 return f" {options}" if options else "" 3110 3111 def for_modifiers(self, expression: exp.Expr) -> str: 3112 for_modifiers = self.expressions(expression, key="for_") 3113 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 3114 3115 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3116 self.unsupported("Unsupported query option.") 3117 return "" 3118 3119 def offset_limit_modifiers( 3120 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3121 ) -> list[str]: 3122 return [ 3123 self.sql(expression, "offset") if fetch else self.sql(limit), 3124 self.sql(limit) if fetch else self.sql(expression, "offset"), 3125 ] 3126 3127 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3128 locks = self.expressions(expression, key="locks", sep=" ") 3129 locks = f" {locks}" if locks else "" 3130 return [locks, self.sql(expression, "sample")] 3131 3132 def select_sql(self, expression: exp.Select) -> str: 3133 into = expression.args.get("into") 3134 if not self.SUPPORTS_SELECT_INTO and into: 3135 into.pop() 3136 3137 hint = self.sql(expression, "hint") 3138 distinct = self.sql(expression, "distinct") 3139 distinct = f" {distinct}" if distinct else "" 3140 kind = self.sql(expression, "kind") 3141 3142 limit = expression.args.get("limit") 3143 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3144 top = self.limit_sql(limit, top=True) 3145 limit.pop() 3146 else: 3147 top = "" 3148 3149 expressions = self.expressions(expression) 3150 3151 if kind: 3152 if kind in self.SELECT_KINDS: 3153 kind = f" AS {kind}" 3154 else: 3155 if kind == "STRUCT": 3156 expressions = self.expressions( 3157 sqls=[ 3158 self.sql( 3159 exp.Struct( 3160 expressions=[ 3161 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3162 if isinstance(e, exp.Alias) 3163 else e 3164 for e in expression.expressions 3165 ] 3166 ) 3167 ) 3168 ] 3169 ) 3170 kind = "" 3171 3172 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3173 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3174 3175 exclude = expression.args.get("exclude") 3176 3177 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3178 exclude_sql = self.expressions(sqls=exclude, flat=True) 3179 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3180 3181 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3182 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3183 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3184 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3185 sql = self.query_modifiers( 3186 expression, 3187 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3188 self.sql(expression, "into", comment=False), 3189 self.sql(expression, "from_", comment=False), 3190 ) 3191 3192 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3193 if expression.args.get("with_"): 3194 sql = self.maybe_comment(sql, expression) 3195 expression.pop_comments() 3196 3197 sql = self.prepend_ctes(expression, sql) 3198 3199 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3200 expression.set("exclude", None) 3201 subquery = expression.subquery(copy=False) 3202 star = exp.Star(except_=exclude) 3203 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3204 3205 if not self.SUPPORTS_SELECT_INTO and into: 3206 if into.args.get("temporary"): 3207 table_kind = " TEMPORARY" 3208 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3209 table_kind = " UNLOGGED" 3210 else: 3211 table_kind = "" 3212 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3213 3214 return sql 3215 3216 def schema_sql(self, expression: exp.Schema) -> str: 3217 this = self.sql(expression, "this") 3218 sql = self.schema_columns_sql(expression) 3219 return f"{this} {sql}" if this and sql else this or sql 3220 3221 def schema_columns_sql(self, expression: exp.Expr) -> str: 3222 if expression.expressions: 3223 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3224 return "" 3225 3226 def star_sql(self, expression: exp.Star) -> str: 3227 except_ = self.expressions(expression, key="except_", flat=True) 3228 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3229 replace = self.expressions(expression, key="replace", flat=True) 3230 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3231 rename = self.expressions(expression, key="rename", flat=True) 3232 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3233 return f"*{except_}{replace}{rename}" 3234 3235 def parameter_sql(self, expression: exp.Parameter) -> str: 3236 this = self.sql(expression, "this") 3237 return f"{self.PARAMETER_TOKEN}{this}" 3238 3239 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3240 this = self.sql(expression, "this") 3241 kind = expression.text("kind") 3242 if kind: 3243 kind = f"{kind}." 3244 return f"@@{kind}{this}" 3245 3246 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3247 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3248 3249 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3250 alias = self.sql(expression, "alias") 3251 alias = f"{sep}{alias}" if alias else "" 3252 sample = self.sql(expression, "sample") 3253 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3254 alias = f"{sample}{alias}" 3255 3256 # Set to None so it's not generated again by self.query_modifiers() 3257 expression.set("sample", None) 3258 3259 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3260 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3261 return self.prepend_ctes(expression, sql) 3262 3263 def qualify_sql(self, expression: exp.Qualify) -> str: 3264 this = self.indent(self.sql(expression, "this")) 3265 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3266 3267 def unnest_sql(self, expression: exp.Unnest) -> str: 3268 args = self.expressions(expression, flat=True) 3269 3270 alias = expression.args.get("alias") 3271 offset = expression.args.get("offset") 3272 3273 if self.UNNEST_WITH_ORDINALITY: 3274 if alias and isinstance(offset, exp.Expr): 3275 alias.append("columns", offset) 3276 expression.set("offset", None) 3277 3278 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3279 columns = alias.columns 3280 alias = self.sql(columns[0]) if columns else "" 3281 else: 3282 alias = self.sql(alias) 3283 3284 alias = f" AS {alias}" if alias else alias 3285 if self.UNNEST_WITH_ORDINALITY: 3286 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3287 else: 3288 if isinstance(offset, exp.Expr): 3289 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3290 elif offset: 3291 suffix = f"{alias} WITH OFFSET" 3292 else: 3293 suffix = alias 3294 3295 return f"UNNEST({args}){suffix}" 3296 3297 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3298 return "" 3299 3300 def where_sql(self, expression: exp.Where) -> str: 3301 this = self.indent(self.sql(expression, "this")) 3302 return f"{self.seg('WHERE')}{self.sep()}{this}" 3303 3304 def window_sql(self, expression: exp.Window) -> str: 3305 this = self.sql(expression, "this") 3306 partition = self.partition_by_sql(expression) 3307 order = expression.args.get("order") 3308 order = self.order_sql(order, flat=True) if order else "" 3309 spec = self.sql(expression, "spec") 3310 alias = self.sql(expression, "alias") 3311 over = self.sql(expression, "over") or "OVER" 3312 3313 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3314 3315 first = expression.args.get("first") 3316 if first is None: 3317 first = "" 3318 else: 3319 first = "FIRST" if first else "LAST" 3320 3321 if not partition and not order and not spec and alias: 3322 return f"{this} {alias}" 3323 3324 args = self.format_args( 3325 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3326 ) 3327 return f"{this} ({args})" 3328 3329 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3330 partition = self.expressions(expression, key="partition_by", flat=True) 3331 return f"PARTITION BY {partition}" if partition else "" 3332 3333 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3334 kind = self.sql(expression, "kind") 3335 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3336 end = ( 3337 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3338 or "CURRENT ROW" 3339 ) 3340 3341 window_spec = f"{kind} BETWEEN {start} AND {end}" 3342 3343 exclude = self.sql(expression, "exclude") 3344 if exclude: 3345 if self.SUPPORTS_WINDOW_EXCLUDE: 3346 window_spec += f" EXCLUDE {exclude}" 3347 else: 3348 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3349 3350 return window_spec 3351 3352 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3353 this = self.sql(expression, "this") 3354 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3355 return f"{this} WITHIN GROUP ({expression_sql})" 3356 3357 def between_sql(self, expression: exp.Between) -> str: 3358 this = self.sql(expression, "this") 3359 low = self.sql(expression, "low") 3360 high = self.sql(expression, "high") 3361 symmetric = expression.args.get("symmetric") 3362 3363 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3364 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3365 3366 flag = ( 3367 " SYMMETRIC" 3368 if symmetric 3369 else " ASYMMETRIC" 3370 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3371 else "" # silently drop ASYMMETRIC – semantics identical 3372 ) 3373 return f"{this} BETWEEN{flag} {low} AND {high}" 3374 3375 def bracket_offset_expressions( 3376 self, expression: exp.Bracket, index_offset: int | None = None 3377 ) -> list[exp.Expr]: 3378 if expression.args.get("json_access"): 3379 return expression.expressions 3380 3381 return apply_index_offset( 3382 expression.this, 3383 expression.expressions, 3384 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3385 dialect=self.dialect, 3386 ) 3387 3388 def bracket_sql(self, expression: exp.Bracket) -> str: 3389 expressions = self.bracket_offset_expressions(expression) 3390 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3391 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3392 3393 def all_sql(self, expression: exp.All) -> str: 3394 this = self.sql(expression, "this") 3395 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3396 this = self.wrap(this) 3397 return f"ALL {this}" 3398 3399 def any_sql(self, expression: exp.Any) -> str: 3400 this = self.sql(expression, "this") 3401 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3402 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3403 this = self.wrap(this) 3404 return f"ANY{this}" 3405 return f"ANY {this}" 3406 3407 def exists_sql(self, expression: exp.Exists) -> str: 3408 return f"EXISTS{self.wrap(expression)}" 3409 3410 def case_sql(self, expression: exp.Case) -> str: 3411 this = self.sql(expression, "this") 3412 statements = [f"CASE {this}" if this else "CASE"] 3413 3414 for e in expression.args["ifs"]: 3415 statements.append(f"WHEN {self.sql(e, 'this')}") 3416 statements.append(f"THEN {self.sql(e, 'true')}") 3417 3418 default = self.sql(expression, "default") 3419 3420 if default: 3421 statements.append(f"ELSE {default}") 3422 3423 statements.append("END") 3424 3425 if self.pretty and self.too_wide(statements): 3426 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3427 3428 return " ".join(statements) 3429 3430 def constraint_sql(self, expression: exp.Constraint) -> str: 3431 this = self.sql(expression, "this") 3432 expressions = self.expressions(expression, flat=True) 3433 return f"CONSTRAINT {this} {expressions}" 3434 3435 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3436 order = expression.args.get("order") 3437 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3438 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3439 3440 def extract_sql(self, expression: exp.Extract) -> str: 3441 import sqlglot.dialects.dialect 3442 3443 this = ( 3444 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3445 if self.NORMALIZE_EXTRACT_DATE_PARTS 3446 else expression.this 3447 ) 3448 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3449 expression_sql = self.sql(expression, "expression") 3450 3451 return f"EXTRACT({this_sql} FROM {expression_sql})" 3452 3453 def trim_sql(self, expression: exp.Trim) -> str: 3454 trim_type = self.sql(expression, "position") 3455 3456 if trim_type == "LEADING": 3457 func_name = "LTRIM" 3458 elif trim_type == "TRAILING": 3459 func_name = "RTRIM" 3460 else: 3461 func_name = "TRIM" 3462 3463 return self.func(func_name, expression.this, expression.expression) 3464 3465 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3466 args = expression.expressions 3467 if isinstance(expression, exp.ConcatWs): 3468 args = args[1:] # Skip the delimiter 3469 3470 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3471 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3472 3473 concat_coalesce = ( 3474 self.dialect.CONCAT_WS_COALESCE 3475 if isinstance(expression, exp.ConcatWs) 3476 else self.dialect.CONCAT_COALESCE 3477 ) 3478 3479 if not concat_coalesce and expression.args.get("coalesce"): 3480 3481 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3482 if not e.type: 3483 import sqlglot.optimizer.annotate_types 3484 3485 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3486 3487 if e.is_string or e.is_type(exp.DType.ARRAY): 3488 return e 3489 3490 return exp.func("coalesce", e, exp.Literal.string("")) 3491 3492 args = [_wrap_with_coalesce(e) for e in args] 3493 3494 return args 3495 3496 def concat_sql(self, expression: exp.Concat) -> str: 3497 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3498 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3499 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3500 # instead of coalescing them to empty string. 3501 import sqlglot.dialects.dialect 3502 3503 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3504 3505 expressions = self.convert_concat_args(expression) 3506 3507 # Some dialects don't allow a single-argument CONCAT call 3508 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3509 return self.sql(expressions[0]) 3510 3511 return self.func("CONCAT", *expressions) 3512 3513 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3514 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3515 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3516 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3517 all_args = expression.expressions 3518 expression.set("coalesce", True) 3519 return self.sql( 3520 exp.case() 3521 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3522 .else_(expression) 3523 ) 3524 3525 return self.func( 3526 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3527 ) 3528 3529 def check_sql(self, expression: exp.Check) -> str: 3530 this = self.sql(expression, key="this") 3531 return f"CHECK ({this})" 3532 3533 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3534 expressions = self.expressions(expression, flat=True) 3535 expressions = f" ({expressions})" if expressions else "" 3536 reference = self.sql(expression, "reference") 3537 reference = f" {reference}" if reference else "" 3538 delete = self.sql(expression, "delete") 3539 delete = f" ON DELETE {delete}" if delete else "" 3540 update = self.sql(expression, "update") 3541 update = f" ON UPDATE {update}" if update else "" 3542 options = self.expressions(expression, key="options", flat=True, sep=" ") 3543 options = f" {options}" if options else "" 3544 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3545 3546 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3547 this = self.sql(expression, "this") 3548 this = f" {this}" if this else "" 3549 expressions = self.expressions(expression, flat=True) 3550 include = self.sql(expression, "include") 3551 options = self.expressions(expression, key="options", flat=True, sep=" ") 3552 options = f" {options}" if options else "" 3553 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3554 3555 def if_sql(self, expression: exp.If) -> str: 3556 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3557 3558 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3559 if self.MATCH_AGAINST_TABLE_PREFIX: 3560 expressions = [] 3561 for expr in expression.expressions: 3562 if isinstance(expr, exp.Table): 3563 expressions.append(f"TABLE {self.sql(expr)}") 3564 else: 3565 expressions.append(expr) 3566 else: 3567 expressions = expression.expressions 3568 3569 modifier = expression.args.get("modifier") 3570 modifier = f" {modifier}" if modifier else "" 3571 return ( 3572 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3573 ) 3574 3575 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3576 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3577 3578 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3579 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3580 3581 if expression.args.get("escape"): 3582 path = self.escape_str(path) 3583 3584 if self.QUOTE_JSON_PATH: 3585 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3586 3587 return path 3588 3589 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3590 if isinstance(expression, exp.JSONPathPart): 3591 transform = self.TRANSFORMS.get(expression.__class__) 3592 if not callable(transform): 3593 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3594 return "" 3595 3596 return transform(self, expression) 3597 3598 if isinstance(expression, int): 3599 return str(expression) 3600 3601 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3602 escaped = expression.replace("'", "\\'") 3603 escaped = f"\\'{expression}\\'" 3604 else: 3605 escaped = expression.replace('"', '\\"') 3606 escaped = f'"{escaped}"' 3607 3608 return escaped 3609 3610 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3611 return f"{self.sql(expression, 'this')} FORMAT JSON" 3612 3613 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3614 # Output the Teradata column FORMAT override. 3615 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3616 this = self.sql(expression, "this") 3617 fmt = self.sql(expression, "format") 3618 return f"{this} (FORMAT {fmt})" 3619 3620 def _jsonobject_sql( 3621 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3622 ) -> str: 3623 null_handling = expression.args.get("null_handling") 3624 null_handling = f" {null_handling}" if null_handling else "" 3625 3626 unique_keys = expression.args.get("unique_keys") 3627 if unique_keys is not None: 3628 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3629 else: 3630 unique_keys = "" 3631 3632 return_type = self.sql(expression, "return_type") 3633 return_type = f" RETURNING {return_type}" if return_type else "" 3634 encoding = self.sql(expression, "encoding") 3635 encoding = f" ENCODING {encoding}" if encoding else "" 3636 3637 if not name: 3638 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3639 3640 return self.func( 3641 name, 3642 *expression.expressions, 3643 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3644 ) 3645 3646 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3647 null_handling = expression.args.get("null_handling") 3648 null_handling = f" {null_handling}" if null_handling else "" 3649 return_type = self.sql(expression, "return_type") 3650 return_type = f" RETURNING {return_type}" if return_type else "" 3651 strict = " STRICT" if expression.args.get("strict") else "" 3652 return self.func( 3653 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3654 ) 3655 3656 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3657 this = self.sql(expression, "this") 3658 order = self.sql(expression, "order") 3659 null_handling = expression.args.get("null_handling") 3660 null_handling = f" {null_handling}" if null_handling else "" 3661 return_type = self.sql(expression, "return_type") 3662 return_type = f" RETURNING {return_type}" if return_type else "" 3663 strict = " STRICT" if expression.args.get("strict") else "" 3664 return self.func( 3665 "JSON_ARRAYAGG", 3666 this, 3667 suffix=f"{order}{null_handling}{return_type}{strict})", 3668 ) 3669 3670 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3671 path = self.sql(expression, "path") 3672 path = f" PATH {path}" if path else "" 3673 nested_schema = self.sql(expression, "nested_schema") 3674 3675 if nested_schema: 3676 return f"NESTED{path} {nested_schema}" 3677 3678 this = self.sql(expression, "this") 3679 kind = self.sql(expression, "kind") 3680 kind = f" {kind}" if kind else "" 3681 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3682 3683 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3684 return f"{this}{kind}{format_json}{path}{ordinality}" 3685 3686 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3687 return self.func("COLUMNS", *expression.expressions) 3688 3689 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3690 this = self.sql(expression, "this") 3691 path = self.sql(expression, "path") 3692 path = f", {path}" if path else "" 3693 error_handling = expression.args.get("error_handling") 3694 error_handling = f" {error_handling}" if error_handling else "" 3695 empty_handling = expression.args.get("empty_handling") 3696 empty_handling = f" {empty_handling}" if empty_handling else "" 3697 schema = self.sql(expression, "schema") 3698 return self.func( 3699 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3700 ) 3701 3702 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3703 this = self.sql(expression, "this") 3704 kind = self.sql(expression, "kind") 3705 path = self.sql(expression, "path") 3706 path = f" {path}" if path else "" 3707 as_json = " AS JSON" if expression.args.get("as_json") else "" 3708 return f"{this} {kind}{path}{as_json}" 3709 3710 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3711 this = self.sql(expression, "this") 3712 path = self.sql(expression, "path") 3713 path = f", {path}" if path else "" 3714 expressions = self.expressions(expression) 3715 with_ = ( 3716 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3717 if expressions 3718 else "" 3719 ) 3720 return f"OPENJSON({this}{path}){with_}" 3721 3722 def in_sql(self, expression: exp.In) -> str: 3723 query = expression.args.get("query") 3724 unnest = expression.args.get("unnest") 3725 field = expression.args.get("field") 3726 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3727 3728 if query: 3729 in_sql = self.sql(query) 3730 elif unnest: 3731 in_sql = self.in_unnest_op(unnest) 3732 elif field: 3733 in_sql = self.sql(field) 3734 else: 3735 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3736 3737 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3738 3739 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3740 return f"(SELECT {self.sql(unnest)})" 3741 3742 def interval_sql(self, expression: exp.Interval) -> str: 3743 unit_expression = expression.args.get("unit") 3744 unit = self.sql(unit_expression) if unit_expression else "" 3745 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3746 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3747 unit = f" {unit}" if unit else "" 3748 3749 if self.SINGLE_STRING_INTERVAL: 3750 this = expression.this.name if expression.this else "" 3751 if this: 3752 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3753 return f"INTERVAL '{this}'{unit}" 3754 return f"INTERVAL '{this}{unit}'" 3755 return f"INTERVAL{unit}" 3756 3757 this = self.sql(expression, "this") 3758 if this: 3759 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3760 this = f" {this}" if unwrapped else f" ({this})" 3761 3762 return f"INTERVAL{this}{unit}" 3763 3764 def return_sql(self, expression: exp.Return) -> str: 3765 return f"RETURN {self.sql(expression, 'this')}" 3766 3767 def reference_sql(self, expression: exp.Reference) -> str: 3768 this = self.sql(expression, "this") 3769 expressions = self.expressions(expression, flat=True) 3770 expressions = f"({expressions})" if expressions else "" 3771 options = self.expressions(expression, key="options", flat=True, sep=" ") 3772 options = f" {options}" if options else "" 3773 return f"REFERENCES {this}{expressions}{options}" 3774 3775 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3776 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3777 parent = expression.parent 3778 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3779 3780 return self.func( 3781 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3782 ) 3783 3784 def paren_sql(self, expression: exp.Paren) -> str: 3785 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3786 return f"({sql}{self.seg(')', sep='')}" 3787 3788 def neg_sql(self, expression: exp.Neg) -> str: 3789 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3790 this_sql = self.sql(expression, "this") 3791 sep = " " if this_sql[0] == "-" else "" 3792 return f"-{sep}{this_sql}" 3793 3794 def not_sql(self, expression: exp.Not) -> str: 3795 return f"NOT {self.sql(expression, 'this')}" 3796 3797 def alias_sql(self, expression: exp.Alias) -> str: 3798 alias = self.sql(expression, "alias") 3799 alias = f" AS {alias}" if alias else "" 3800 return f"{self.sql(expression, 'this')}{alias}" 3801 3802 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3803 alias = expression.args["alias"] 3804 3805 parent = expression.parent 3806 pivot = parent and parent.parent 3807 3808 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3809 identifier_alias = isinstance(alias, exp.Identifier) 3810 literal_alias = isinstance(alias, exp.Literal) 3811 3812 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3813 alias.replace(exp.Literal.string(alias.output_name)) 3814 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3815 alias.replace(exp.to_identifier(alias.output_name)) 3816 3817 return self.alias_sql(expression) 3818 3819 def aliases_sql(self, expression: exp.Aliases) -> str: 3820 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3821 3822 def atindex_sql(self, expression: exp.AtIndex) -> str: 3823 this = self.sql(expression, "this") 3824 index = self.sql(expression, "expression") 3825 return f"{this} AT {index}" 3826 3827 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3828 this = self.sql(expression, "this") 3829 zone = self.sql(expression, "zone") 3830 return f"{this} AT TIME ZONE {zone}" 3831 3832 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3833 this = self.sql(expression, "this") 3834 zone = self.sql(expression, "zone") 3835 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3836 3837 def add_sql(self, expression: exp.Add) -> str: 3838 return self.binary(expression, "+") 3839 3840 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 3841 return self.connector_sql(expression, "AND", stack) 3842 3843 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 3844 return self.connector_sql(expression, "OR", stack) 3845 3846 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 3847 return self.connector_sql(expression, "XOR", stack) 3848 3849 def connector_sql( 3850 self, 3851 expression: exp.Connector, 3852 op: str, 3853 stack: list[str | exp.Expr] | None = None, 3854 ) -> str: 3855 if stack is not None: 3856 if expression.expressions: 3857 stack.append(self.expressions(expression, sep=f" {op} ")) 3858 else: 3859 stack.append(expression.right) 3860 if expression.comments and self.comments: 3861 for comment in expression.comments: 3862 if comment: 3863 op += f" /*{self.sanitize_comment(comment)}*/" 3864 stack.extend((op, expression.left)) 3865 return op 3866 3867 stack = [expression] 3868 sqls: list[str] = [] 3869 ops = set() 3870 3871 while stack: 3872 node = stack.pop() 3873 if isinstance(node, exp.Connector): 3874 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3875 else: 3876 sql = self.sql(node) 3877 if sqls and sqls[-1] in ops: 3878 sqls[-1] += f" {sql}" 3879 else: 3880 sqls.append(sql) 3881 3882 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3883 return sep.join(sqls) 3884 3885 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3886 return self.binary(expression, "&") 3887 3888 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3889 return self.binary(expression, "<<") 3890 3891 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3892 return f"~{self.sql(expression, 'this')}" 3893 3894 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3895 return self.binary(expression, "|") 3896 3897 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3898 return self.binary(expression, ">>") 3899 3900 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3901 return self.binary(expression, "^") 3902 3903 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 3904 format_sql = self.sql(expression, "format") 3905 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3906 to_sql = self.sql(expression, "to") 3907 to_sql = f" {to_sql}" if to_sql else "" 3908 action = self.sql(expression, "action") 3909 action = f" {action}" if action else "" 3910 default = self.sql(expression, "default") 3911 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3912 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3913 3914 # Base implementation that excludes safe, zone, and target_type metadata args 3915 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3916 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3917 3918 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3919 zone = self.sql(expression, "this") 3920 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3921 3922 def collate_sql(self, expression: exp.Collate) -> str: 3923 if self.COLLATE_IS_FUNC: 3924 return self.function_fallback_sql(expression) 3925 return self.binary(expression, "COLLATE") 3926 3927 def command_sql(self, expression: exp.Command) -> str: 3928 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3929 3930 def comment_sql(self, expression: exp.Comment) -> str: 3931 this = self.sql(expression, "this") 3932 kind = expression.args["kind"] 3933 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3934 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3935 expression_sql = self.sql(expression, "expression") 3936 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3937 3938 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3939 this = self.sql(expression, "this") 3940 delete = " DELETE" if expression.args.get("delete") else "" 3941 recompress = self.sql(expression, "recompress") 3942 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3943 to_disk = self.sql(expression, "to_disk") 3944 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3945 to_volume = self.sql(expression, "to_volume") 3946 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3947 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3948 3949 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3950 where = self.sql(expression, "where") 3951 group = self.sql(expression, "group") 3952 aggregates = self.expressions(expression, key="aggregates") 3953 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3954 3955 if not (where or group or aggregates) and len(expression.expressions) == 1: 3956 return f"TTL {self.expressions(expression, flat=True)}" 3957 3958 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3959 3960 def transaction_sql(self, expression: exp.Transaction) -> str: 3961 modes = self.expressions(expression, key="modes") 3962 modes = f" {modes}" if modes else "" 3963 return f"BEGIN{modes}" 3964 3965 def commit_sql(self, expression: exp.Commit) -> str: 3966 chain = expression.args.get("chain") 3967 if chain is not None: 3968 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3969 3970 return f"COMMIT{chain or ''}" 3971 3972 def rollback_sql(self, expression: exp.Rollback) -> str: 3973 savepoint = expression.args.get("savepoint") 3974 savepoint = f" TO {savepoint}" if savepoint else "" 3975 return f"ROLLBACK{savepoint}" 3976 3977 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3978 this = self.sql(expression, "this") 3979 3980 dtype = self.sql(expression, "dtype") 3981 if dtype: 3982 collate = self.sql(expression, "collate") 3983 collate = f" COLLATE {collate}" if collate else "" 3984 using = self.sql(expression, "using") 3985 using = f" USING {using}" if using else "" 3986 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3987 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3988 3989 default = self.sql(expression, "default") 3990 if default: 3991 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3992 3993 comment = self.sql(expression, "comment") 3994 if comment: 3995 return f"ALTER COLUMN {this} COMMENT {comment}" 3996 3997 visible = expression.args.get("visible") 3998 if visible: 3999 return f"ALTER COLUMN {this} SET {visible}" 4000 4001 allow_null = expression.args.get("allow_null") 4002 drop = expression.args.get("drop") 4003 4004 if not drop and not allow_null: 4005 self.unsupported("Unsupported ALTER COLUMN syntax") 4006 4007 if allow_null is not None: 4008 keyword = "DROP" if drop else "SET" 4009 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4010 4011 return f"ALTER COLUMN {this} DROP DEFAULT" 4012 4013 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4014 this = self.sql(expression, "this") 4015 rename_from = self.sql(expression, "rename_from") 4016 if rename_from: 4017 if not self.SUPPORTS_CHANGE_COLUMN: 4018 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4019 return f"CHANGE COLUMN {rename_from} {this}" 4020 if not self.SUPPORTS_MODIFY_COLUMN: 4021 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4022 return f"MODIFY COLUMN {this}" 4023 4024 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 4025 this = self.sql(expression, "this") 4026 4027 visible = expression.args.get("visible") 4028 visible_sql = "VISIBLE" if visible else "INVISIBLE" 4029 4030 return f"ALTER INDEX {this} {visible_sql}" 4031 4032 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 4033 this = self.sql(expression, "this") 4034 if not isinstance(expression.this, exp.Var): 4035 this = f"KEY DISTKEY {this}" 4036 return f"ALTER DISTSTYLE {this}" 4037 4038 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4039 compound = " COMPOUND" if expression.args.get("compound") else "" 4040 this = self.sql(expression, "this") 4041 expressions = self.expressions(expression, flat=True) 4042 expressions = f"({expressions})" if expressions else "" 4043 return f"ALTER{compound} SORTKEY {this or expressions}" 4044 4045 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4046 if not self.RENAME_TABLE_WITH_DB: 4047 # Remove db from tables 4048 expression = expression.transform( 4049 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4050 ).assert_is(exp.AlterRename) 4051 this = self.sql(expression, "this") 4052 to_kw = " TO" if include_to else "" 4053 return f"RENAME{to_kw} {this}" 4054 4055 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 4056 exists = " IF EXISTS" if expression.args.get("exists") else "" 4057 old_column = self.sql(expression, "this") 4058 new_column = self.sql(expression, "to") 4059 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 4060 4061 def alterset_sql(self, expression: exp.AlterSet) -> str: 4062 exprs = self.expressions(expression, flat=True) 4063 if self.ALTER_SET_WRAPPED: 4064 exprs = f"({exprs})" 4065 4066 return f"SET {exprs}" 4067 4068 def alter_sql(self, expression: exp.Alter) -> str: 4069 actions = expression.args["actions"] 4070 4071 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4072 actions[0], exp.ColumnDef 4073 ): 4074 actions_sql = self.expressions(expression, key="actions", flat=True) 4075 actions_sql = f"ADD {actions_sql}" 4076 else: 4077 actions_list = [] 4078 for action in actions: 4079 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4080 action_sql = self.add_column_sql(action) 4081 else: 4082 action_sql = self.sql(action) 4083 if isinstance(action, exp.Query): 4084 action_sql = f"AS {action_sql}" 4085 4086 actions_list.append(action_sql) 4087 4088 actions_sql = self.format_args(*actions_list).lstrip("\n") 4089 4090 iceberg = ( 4091 "ICEBERG " 4092 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4093 else "" 4094 ) 4095 exists = " IF EXISTS" if expression.args.get("exists") else "" 4096 on_cluster = self.sql(expression, "cluster") 4097 on_cluster = f" {on_cluster}" if on_cluster else "" 4098 only = " ONLY" if expression.args.get("only") else "" 4099 options = self.expressions(expression, key="options") 4100 options = f", {options}" if options else "" 4101 kind = self.sql(expression, "kind") 4102 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4103 check = " WITH CHECK" if expression.args.get("check") else "" 4104 cascade = ( 4105 " CASCADE" 4106 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4107 else "" 4108 ) 4109 this = self.sql(expression, "this") 4110 this = f" {this}" if this else "" 4111 4112 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4113 4114 def altersession_sql(self, expression: exp.AlterSession) -> str: 4115 items_sql = self.expressions(expression, flat=True) 4116 keyword = "UNSET" if expression.args.get("unset") else "SET" 4117 return f"{keyword} {items_sql}" 4118 4119 def add_column_sql(self, expression: exp.Expr) -> str: 4120 sql = self.sql(expression) 4121 if isinstance(expression, exp.Schema): 4122 column_text = " COLUMNS" 4123 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4124 column_text = " COLUMN" 4125 else: 4126 column_text = "" 4127 4128 return f"ADD{column_text} {sql}" 4129 4130 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4131 expressions = self.expressions(expression) 4132 exists = " IF EXISTS " if expression.args.get("exists") else " " 4133 return f"DROP{exists}{expressions}" 4134 4135 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4136 return "DROP PRIMARY KEY" 4137 4138 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4139 return f"ADD {self.expressions(expression, indent=False)}" 4140 4141 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4142 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4143 location = self.sql(expression, "location") 4144 location = f" {location}" if location else "" 4145 return f"ADD {exists}{self.sql(expression.this)}{location}" 4146 4147 def distinct_sql(self, expression: exp.Distinct) -> str: 4148 this = self.expressions(expression, flat=True) 4149 4150 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4151 case = exp.case() 4152 for arg in expression.expressions: 4153 case = case.when(arg.is_(exp.null()), exp.null()) 4154 this = self.sql(case.else_(f"({this})")) 4155 4156 this = f" {this}" if this else "" 4157 4158 on = self.sql(expression, "on") 4159 on = f" ON {on}" if on else "" 4160 return f"DISTINCT{this}{on}" 4161 4162 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4163 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4164 4165 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4166 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4167 4168 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4169 this_sql = self.sql(expression, "this") 4170 expression_sql = self.sql(expression, "expression") 4171 kind = "MAX" if expression.args.get("max") else "MIN" 4172 return f"{this_sql} HAVING {kind} {expression_sql}" 4173 4174 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4175 return self.sql( 4176 exp.Cast( 4177 this=exp.Div(this=expression.this, expression=expression.expression), 4178 to=exp.DataType(this=exp.DType.INT), 4179 ) 4180 ) 4181 4182 def dpipe_sql(self, expression: exp.DPipe) -> str: 4183 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4184 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4185 return self.binary(expression, "||") 4186 4187 def div_sql(self, expression: exp.Div) -> str: 4188 l, r = expression.left, expression.right 4189 4190 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4191 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4192 4193 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4194 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4195 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4196 4197 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4198 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4199 return self.sql( 4200 exp.cast( 4201 l / r, 4202 to=exp.DType.BIGINT, 4203 ) 4204 ) 4205 4206 return self.binary(expression, "/") 4207 4208 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4209 n = exp._wrap(expression.this, exp.Binary) 4210 d = exp._wrap(expression.expression, exp.Binary) 4211 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4212 4213 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4214 return self.binary(expression, "OVERLAPS") 4215 4216 def distance_sql(self, expression: exp.Distance) -> str: 4217 return self.binary(expression, "<->") 4218 4219 def dot_sql(self, expression: exp.Dot) -> str: 4220 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4221 4222 def eq_sql(self, expression: exp.EQ) -> str: 4223 return self.binary(expression, "=") 4224 4225 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4226 return self.binary(expression, ":=") 4227 4228 def escape_sql(self, expression: exp.Escape) -> str: 4229 this = expression.this 4230 if ( 4231 isinstance(this, (exp.Like, exp.ILike)) 4232 and isinstance(this.expression, (exp.All, exp.Any)) 4233 and not self.SUPPORTS_LIKE_QUANTIFIERS 4234 ): 4235 return self._like_sql(this, escape=expression) 4236 return self.binary(expression, "ESCAPE") 4237 4238 def glob_sql(self, expression: exp.Glob) -> str: 4239 return self.binary(expression, "GLOB") 4240 4241 def gt_sql(self, expression: exp.GT) -> str: 4242 return self.binary(expression, ">") 4243 4244 def gte_sql(self, expression: exp.GTE) -> str: 4245 return self.binary(expression, ">=") 4246 4247 def is_sql(self, expression: exp.Is) -> str: 4248 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4249 return self.sql( 4250 expression.this if expression.expression.this else exp.not_(expression.this) 4251 ) 4252 return self.binary(expression, "IS") 4253 4254 def _like_sql( 4255 self, 4256 expression: exp.Like | exp.ILike, 4257 escape: exp.Escape | None = None, 4258 ) -> str: 4259 this = expression.this 4260 rhs = expression.expression 4261 4262 if isinstance(expression, exp.Like): 4263 exp_class: type[exp.Like | exp.ILike] = exp.Like 4264 op = "LIKE" 4265 else: 4266 exp_class = exp.ILike 4267 op = "ILIKE" 4268 4269 if expression.args.get("negate"): 4270 op = f"NOT {op}" 4271 4272 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4273 exprs = rhs.this.unnest() 4274 4275 if isinstance(exprs, exp.Tuple): 4276 exprs = exprs.expressions 4277 else: 4278 exprs = [exprs] 4279 4280 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4281 4282 def _make_like(expr: exp.Expression) -> exp.Expression: 4283 like: exp.Expression = exp_class( 4284 this=this, expression=expr, negate=expression.args.get("negate") 4285 ) 4286 if escape: 4287 like = exp.Escape(this=like, expression=escape.expression.copy()) 4288 return like 4289 4290 like_expr: exp.Expr = _make_like(exprs[0]) 4291 for expr in exprs[1:]: 4292 like_expr = connective(like_expr, _make_like(expr), copy=False) 4293 4294 parent = escape.parent if escape else expression.parent 4295 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4296 parent, exp.Condition 4297 ): 4298 like_expr = exp.paren(like_expr, copy=False) 4299 4300 return self.sql(like_expr) 4301 4302 return self.binary(expression, op) 4303 4304 def like_sql(self, expression: exp.Like) -> str: 4305 return self._like_sql(expression) 4306 4307 def ilike_sql(self, expression: exp.ILike) -> str: 4308 return self._like_sql(expression) 4309 4310 def match_sql(self, expression: exp.Match) -> str: 4311 return self.binary(expression, "MATCH") 4312 4313 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4314 return self.binary(expression, "SIMILAR TO") 4315 4316 def lt_sql(self, expression: exp.LT) -> str: 4317 return self.binary(expression, "<") 4318 4319 def lte_sql(self, expression: exp.LTE) -> str: 4320 return self.binary(expression, "<=") 4321 4322 def mod_sql(self, expression: exp.Mod) -> str: 4323 return self.binary(expression, "%") 4324 4325 def mul_sql(self, expression: exp.Mul) -> str: 4326 return self.binary(expression, "*") 4327 4328 def neq_sql(self, expression: exp.NEQ) -> str: 4329 return self.binary(expression, "<>") 4330 4331 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4332 return self.binary(expression, "IS NOT DISTINCT FROM") 4333 4334 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4335 return self.binary(expression, "IS DISTINCT FROM") 4336 4337 def sub_sql(self, expression: exp.Sub) -> str: 4338 return self.binary(expression, "-") 4339 4340 def trycast_sql(self, expression: exp.TryCast) -> str: 4341 return self.cast_sql(expression, safe_prefix="TRY_") 4342 4343 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4344 return self.cast_sql(expression) 4345 4346 def try_sql(self, expression: exp.Try) -> str: 4347 if not self.TRY_SUPPORTED: 4348 self.unsupported("Unsupported TRY function") 4349 return self.sql(expression, "this") 4350 4351 return self.func("TRY", expression.this) 4352 4353 def log_sql(self, expression: exp.Log) -> str: 4354 this = expression.this 4355 expr = expression.expression 4356 4357 if self.dialect.LOG_BASE_FIRST is False: 4358 this, expr = expr, this 4359 elif self.dialect.LOG_BASE_FIRST is None and expr: 4360 if this.name in ("2", "10"): 4361 return self.func(f"LOG{this.name}", expr) 4362 4363 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4364 4365 return self.func("LOG", this, expr) 4366 4367 def use_sql(self, expression: exp.Use) -> str: 4368 kind = self.sql(expression, "kind") 4369 kind = f" {kind}" if kind else "" 4370 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4371 this = f" {this}" if this else "" 4372 return f"USE{kind}{this}" 4373 4374 def binary(self, expression: exp.Binary, op: str) -> str: 4375 sqls: list[str] = [] 4376 stack: list[None | str | exp.Expr] = [expression] 4377 binary_type = type(expression) 4378 4379 while stack: 4380 node = stack.pop() 4381 4382 if type(node) is binary_type: 4383 op_func = node.args.get("operator") 4384 if op_func: 4385 op = f"OPERATOR({self.sql(op_func)})" 4386 4387 stack.append(node.args.get("expression")) 4388 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4389 stack.append(node.args.get("this")) 4390 else: 4391 sqls.append(self.sql(node)) 4392 4393 return "".join(sqls) 4394 4395 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4396 to_clause = self.sql(expression, "to") 4397 if to_clause: 4398 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4399 4400 return self.function_fallback_sql(expression) 4401 4402 def function_fallback_sql(self, expression: exp.Func) -> str: 4403 args = [] 4404 4405 for key in expression.arg_types: 4406 arg_value = expression.args.get(key) 4407 4408 if isinstance(arg_value, list): 4409 for value in arg_value: 4410 args.append(value) 4411 elif arg_value is not None: 4412 args.append(arg_value) 4413 4414 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4415 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4416 else: 4417 name = expression.sql_name() 4418 4419 return self.func(name, *args) 4420 4421 def func( 4422 self, 4423 name: str, 4424 *args: t.Any, 4425 prefix: str = "(", 4426 suffix: str = ")", 4427 normalize: bool = True, 4428 ) -> str: 4429 name = self.normalize_func(name) if normalize else name 4430 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4431 4432 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4433 arg_sqls = tuple( 4434 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4435 ) 4436 if self.pretty and self.too_wide(arg_sqls): 4437 return self.indent( 4438 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4439 ) 4440 return sep.join(arg_sqls) 4441 4442 def too_wide(self, args: t.Iterable) -> bool: 4443 return sum(len(arg) for arg in args) > self.max_text_width 4444 4445 def format_time( 4446 self, 4447 expression: exp.Expr, 4448 inverse_time_mapping: dict[str, str] | None = None, 4449 inverse_time_trie: dict | None = None, 4450 ) -> str | None: 4451 return format_time( 4452 self.sql(expression, "format"), 4453 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4454 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4455 ) 4456 4457 def expressions( 4458 self, 4459 expression: exp.Expr | None = None, 4460 key: str | None = None, 4461 sqls: t.Collection[str | exp.Expr] | None = None, 4462 flat: bool = False, 4463 indent: bool = True, 4464 skip_first: bool = False, 4465 skip_last: bool = False, 4466 sep: str = ", ", 4467 prefix: str = "", 4468 dynamic: bool = False, 4469 new_line: bool = False, 4470 ) -> str: 4471 expressions = expression.args.get(key or "expressions") if expression else sqls 4472 4473 if not expressions: 4474 return "" 4475 4476 if flat: 4477 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4478 4479 num_sqls = len(expressions) 4480 result_sqls = [] 4481 4482 for i, e in enumerate(expressions): 4483 sql = self.sql(e, comment=False) 4484 if not sql: 4485 continue 4486 4487 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4488 4489 if self.pretty: 4490 if self.leading_comma: 4491 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4492 else: 4493 result_sqls.append( 4494 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4495 ) 4496 else: 4497 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4498 4499 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4500 if new_line: 4501 result_sqls.insert(0, "") 4502 result_sqls.append("") 4503 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4504 else: 4505 result_sql = "".join(result_sqls) 4506 4507 return ( 4508 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4509 if indent 4510 else result_sql 4511 ) 4512 4513 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4514 flat = flat or isinstance(expression.parent, exp.Properties) 4515 expressions_sql = self.expressions(expression, flat=flat) 4516 if flat: 4517 return f"{op} {expressions_sql}" 4518 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4519 4520 def naked_property(self, expression: exp.Property) -> str: 4521 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4522 if not property_name: 4523 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4524 return f"{property_name} {self.sql(expression, 'this')}" 4525 4526 def tag_sql(self, expression: exp.Tag) -> str: 4527 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4528 4529 def token_sql(self, token_type: TokenType) -> str: 4530 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4531 4532 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4533 this = self.sql(expression, "this") 4534 expressions = self.no_identify(self.expressions, expression) 4535 expressions = ( 4536 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4537 ) 4538 return f"{this}{expressions}" if expressions.strip() != "" else this 4539 4540 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4541 this = self.sql(expression, "this") 4542 expressions = self.expressions(expression, flat=True) 4543 return f"{this}({expressions})" 4544 4545 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4546 return self.binary(expression, "=>") 4547 4548 def when_sql(self, expression: exp.When) -> str: 4549 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4550 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4551 condition = self.sql(expression, "condition") 4552 condition = f" AND {condition}" if condition else "" 4553 4554 then_expression = expression.args.get("then") 4555 if isinstance(then_expression, exp.Insert): 4556 this = self.sql(then_expression, "this") 4557 this = f"INSERT {this}" if this else "INSERT" 4558 then = self.sql(then_expression, "expression") 4559 then = f"{this} VALUES {then}" if then else this 4560 elif isinstance(then_expression, exp.Update): 4561 if isinstance(then_expression.args.get("expressions"), exp.Star): 4562 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4563 else: 4564 expressions_sql = self.expressions(then_expression) 4565 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4566 else: 4567 then = self.sql(then_expression) 4568 4569 if isinstance(then_expression, (exp.Insert, exp.Update)): 4570 where = self.sql(then_expression, "where") 4571 if where and not self.SUPPORTS_MERGE_WHERE: 4572 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4573 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4574 where = "" 4575 then = f"{then}{where}" 4576 return f"WHEN {matched}{source}{condition} THEN {then}" 4577 4578 def whens_sql(self, expression: exp.Whens) -> str: 4579 return self.expressions(expression, sep=" ", indent=False) 4580 4581 def merge_sql(self, expression: exp.Merge) -> str: 4582 table = expression.this 4583 table_alias = "" 4584 4585 hints = table.args.get("hints") 4586 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4587 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4588 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4589 4590 this = self.sql(table) 4591 using = f"USING {self.sql(expression, 'using')}" 4592 whens = self.sql(expression, "whens") 4593 4594 on = self.sql(expression, "on") 4595 on = f"ON {on}" if on else "" 4596 4597 if not on: 4598 on = self.expressions(expression, key="using_cond") 4599 on = f"USING ({on})" if on else "" 4600 4601 returning = self.sql(expression, "returning") 4602 if returning: 4603 whens = f"{whens}{returning}" 4604 4605 sep = self.sep() 4606 4607 return self.prepend_ctes( 4608 expression, 4609 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4610 ) 4611 4612 @unsupported_args("format") 4613 def tochar_sql(self, expression: exp.ToChar) -> str: 4614 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4615 4616 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4617 if not self.SUPPORTS_TO_NUMBER: 4618 self.unsupported("Unsupported TO_NUMBER function") 4619 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4620 4621 fmt = expression.args.get("format") 4622 if not fmt: 4623 self.unsupported("Conversion format is required for TO_NUMBER") 4624 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4625 4626 return self.func("TO_NUMBER", expression.this, fmt) 4627 4628 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4629 this = self.sql(expression, "this") 4630 kind = self.sql(expression, "kind") 4631 settings_sql = self.expressions(expression, key="settings", sep=" ") 4632 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4633 return f"{this}({kind}{args})" 4634 4635 def dictrange_sql(self, expression: exp.DictRange) -> str: 4636 this = self.sql(expression, "this") 4637 max = self.sql(expression, "max") 4638 min = self.sql(expression, "min") 4639 return f"{this}(MIN {min} MAX {max})" 4640 4641 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4642 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4643 4644 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4645 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4646 4647 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4648 def uniquekeyproperty_sql( 4649 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4650 ) -> str: 4651 return f"{prefix} ({self.expressions(expression, flat=True)})" 4652 4653 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4654 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4655 expressions = self.expressions(expression, flat=True) 4656 expressions = f" {self.wrap(expressions)}" if expressions else "" 4657 buckets = self.sql(expression, "buckets") 4658 kind = self.sql(expression, "kind") 4659 buckets = f" BUCKETS {buckets}" if buckets else "" 4660 order = self.sql(expression, "order") 4661 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4662 4663 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4664 return "" 4665 4666 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4667 expressions = self.expressions(expression, key="expressions", flat=True) 4668 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4669 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4670 buckets = self.sql(expression, "buckets") 4671 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4672 4673 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4674 this = self.sql(expression, "this") 4675 having = self.sql(expression, "having") 4676 4677 if having: 4678 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4679 4680 return self.func("ANY_VALUE", this) 4681 4682 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4683 transform = self.func("TRANSFORM", *expression.expressions) 4684 row_format_before = self.sql(expression, "row_format_before") 4685 row_format_before = f" {row_format_before}" if row_format_before else "" 4686 record_writer = self.sql(expression, "record_writer") 4687 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4688 using = f" USING {self.sql(expression, 'command_script')}" 4689 schema = self.sql(expression, "schema") 4690 schema = f" AS {schema}" if schema else "" 4691 row_format_after = self.sql(expression, "row_format_after") 4692 row_format_after = f" {row_format_after}" if row_format_after else "" 4693 record_reader = self.sql(expression, "record_reader") 4694 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4695 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4696 4697 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4698 key_block_size = self.sql(expression, "key_block_size") 4699 if key_block_size: 4700 return f"KEY_BLOCK_SIZE = {key_block_size}" 4701 4702 using = self.sql(expression, "using") 4703 if using: 4704 return f"USING {using}" 4705 4706 parser = self.sql(expression, "parser") 4707 if parser: 4708 return f"WITH PARSER {parser}" 4709 4710 comment = self.sql(expression, "comment") 4711 if comment: 4712 return f"COMMENT {comment}" 4713 4714 visible = expression.args.get("visible") 4715 if visible is not None: 4716 return "VISIBLE" if visible else "INVISIBLE" 4717 4718 engine_attr = self.sql(expression, "engine_attr") 4719 if engine_attr: 4720 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4721 4722 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4723 if secondary_engine_attr: 4724 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4725 4726 self.unsupported("Unsupported index constraint option.") 4727 return "" 4728 4729 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4730 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4731 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4732 4733 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4734 kind = self.sql(expression, "kind") 4735 kind = f"{kind} INDEX" if kind else "INDEX" 4736 this = self.sql(expression, "this") 4737 this = f" {this}" if this else "" 4738 index_type = self.sql(expression, "index_type") 4739 index_type = f" USING {index_type}" if index_type else "" 4740 expressions = self.expressions(expression, flat=True) 4741 expressions = f" ({expressions})" if expressions else "" 4742 options = self.expressions(expression, key="options", sep=" ") 4743 options = f" {options}" if options else "" 4744 return f"{kind}{this}{index_type}{expressions}{options}" 4745 4746 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4747 if self.NVL2_SUPPORTED: 4748 return self.function_fallback_sql(expression) 4749 4750 case = exp.Case().when( 4751 expression.this.is_(exp.null()).not_(copy=False), 4752 expression.args["true"], 4753 copy=False, 4754 ) 4755 else_cond = expression.args.get("false") 4756 if else_cond: 4757 case.else_(else_cond, copy=False) 4758 4759 return self.sql(case) 4760 4761 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4762 this = self.sql(expression, "this") 4763 expr = self.sql(expression, "expression") 4764 position = self.sql(expression, "position") 4765 position = f", {position}" if position else "" 4766 iterator = self.sql(expression, "iterator") 4767 condition = self.sql(expression, "condition") 4768 condition = f" IF {condition}" if condition else "" 4769 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4770 4771 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4772 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4773 4774 def opclass_sql(self, expression: exp.Opclass) -> str: 4775 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4776 4777 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4778 model = self.sql(expression, "this") 4779 model = f"MODEL {model}" 4780 expr = expression.expression 4781 if expr: 4782 expr_sql = self.sql(expression, "expression") 4783 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 4784 else: 4785 expr_sql = None 4786 4787 parameters = self.sql(expression, "params_struct") or None 4788 4789 return self.func(name, model, expr_sql, parameters) 4790 4791 def predict_sql(self, expression: exp.Predict) -> str: 4792 return self._ml_sql(expression, "PREDICT") 4793 4794 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4795 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4796 return self._ml_sql(expression, name) 4797 4798 def generatetext_sql(self, expression: exp.GenerateText) -> str: 4799 return self._ml_sql(expression, "GENERATE_TEXT") 4800 4801 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 4802 return self._ml_sql(expression, "GENERATE_TABLE") 4803 4804 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 4805 return self._ml_sql(expression, "GENERATE_BOOL") 4806 4807 def generateint_sql(self, expression: exp.GenerateInt) -> str: 4808 return self._ml_sql(expression, "GENERATE_INT") 4809 4810 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 4811 return self._ml_sql(expression, "GENERATE_DOUBLE") 4812 4813 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4814 return self._ml_sql(expression, "TRANSLATE") 4815 4816 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4817 return self._ml_sql(expression, "FORECAST") 4818 4819 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4820 this_sql = self.sql(expression, "this") 4821 if isinstance(expression.this, exp.Table): 4822 this_sql = f"TABLE {this_sql}" 4823 4824 return self.func( 4825 "FORECAST", 4826 this_sql, 4827 expression.args.get("data_col"), 4828 expression.args.get("timestamp_col"), 4829 expression.args.get("model"), 4830 expression.args.get("id_cols"), 4831 expression.args.get("horizon"), 4832 expression.args.get("forecast_end_timestamp"), 4833 expression.args.get("confidence_level"), 4834 expression.args.get("output_historical_time_series"), 4835 expression.args.get("context_window"), 4836 ) 4837 4838 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4839 this_sql = self.sql(expression, "this") 4840 if isinstance(expression.this, exp.Table): 4841 this_sql = f"TABLE {this_sql}" 4842 4843 return self.func( 4844 "FEATURES_AT_TIME", 4845 this_sql, 4846 expression.args.get("time"), 4847 expression.args.get("num_rows"), 4848 expression.args.get("ignore_feature_nulls"), 4849 ) 4850 4851 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4852 this_sql = self.sql(expression, "this") 4853 if isinstance(expression.this, exp.Table): 4854 this_sql = f"TABLE {this_sql}" 4855 4856 query_table = self.sql(expression, "query_table") 4857 if isinstance(expression.args["query_table"], exp.Table): 4858 query_table = f"TABLE {query_table}" 4859 4860 return self.func( 4861 "VECTOR_SEARCH", 4862 this_sql, 4863 expression.args.get("column_to_search"), 4864 query_table, 4865 expression.args.get("query_column_to_search"), 4866 expression.args.get("top_k"), 4867 expression.args.get("distance_type"), 4868 expression.args.get("options"), 4869 ) 4870 4871 def forin_sql(self, expression: exp.ForIn) -> str: 4872 this = self.sql(expression, "this") 4873 expression_sql = self.sql(expression, "expression") 4874 return f"FOR {this} DO {expression_sql}" 4875 4876 def refresh_sql(self, expression: exp.Refresh) -> str: 4877 this = self.sql(expression, "this") 4878 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4879 return f"REFRESH {kind}{this}" 4880 4881 def toarray_sql(self, expression: exp.ToArray) -> str: 4882 arg = expression.this 4883 if not arg.type: 4884 import sqlglot.optimizer.annotate_types 4885 4886 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 4887 4888 if arg.is_type(exp.DType.ARRAY): 4889 return self.sql(arg) 4890 4891 cond_for_null = arg.is_(exp.null()) 4892 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4893 4894 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4895 this = expression.this 4896 time_format = self.format_time(expression) 4897 4898 if time_format: 4899 return self.sql( 4900 exp.cast( 4901 exp.StrToTime(this=this, format=expression.args["format"]), 4902 exp.DType.TIME, 4903 ) 4904 ) 4905 4906 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 4907 return self.sql(this) 4908 4909 return self.sql(exp.cast(this, exp.DType.TIME)) 4910 4911 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4912 this = expression.this 4913 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 4914 return self.sql(this) 4915 4916 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 4917 4918 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4919 this = expression.this 4920 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 4921 return self.sql(this) 4922 4923 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 4924 4925 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4926 this = expression.this 4927 time_format = self.format_time(expression) 4928 safe = expression.args.get("safe") 4929 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4930 return self.sql( 4931 exp.cast( 4932 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4933 exp.DType.DATE, 4934 ) 4935 ) 4936 4937 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 4938 return self.sql(this) 4939 4940 if safe: 4941 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 4942 4943 return self.sql(exp.cast(this, exp.DType.DATE)) 4944 4945 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4946 return self.sql( 4947 exp.func( 4948 "DATEDIFF", 4949 expression.this, 4950 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 4951 "day", 4952 ) 4953 ) 4954 4955 def lastday_sql(self, expression: exp.LastDay) -> str: 4956 if self.LAST_DAY_SUPPORTS_DATE_PART: 4957 return self.function_fallback_sql(expression) 4958 4959 unit = expression.text("unit") 4960 if unit and unit != "MONTH": 4961 self.unsupported("Date parts are not supported in LAST_DAY.") 4962 4963 return self.func("LAST_DAY", expression.this) 4964 4965 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4966 import sqlglot.dialects.dialect 4967 4968 return self.func( 4969 "DATE_ADD", 4970 expression.this, 4971 expression.expression, 4972 sqlglot.dialects.dialect.unit_to_str(expression), 4973 ) 4974 4975 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4976 if self.CAN_IMPLEMENT_ARRAY_ANY: 4977 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4978 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4979 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4980 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4981 4982 import sqlglot.dialects.dialect 4983 4984 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4985 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 4986 self.unsupported("ARRAY_ANY is unsupported") 4987 4988 return self.function_fallback_sql(expression) 4989 4990 def struct_sql(self, expression: exp.Struct) -> str: 4991 expression.set( 4992 "expressions", 4993 [ 4994 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4995 if isinstance(e, exp.PropertyEQ) 4996 else e 4997 for e in expression.expressions 4998 ], 4999 ) 5000 5001 return self.function_fallback_sql(expression) 5002 5003 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 5004 low = self.sql(expression, "this") 5005 high = self.sql(expression, "expression") 5006 5007 return f"{low} TO {high}" 5008 5009 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5010 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5011 tables = f" {self.expressions(expression)}" 5012 5013 exists = " IF EXISTS" if expression.args.get("exists") else "" 5014 5015 on_cluster = self.sql(expression, "cluster") 5016 on_cluster = f" {on_cluster}" if on_cluster else "" 5017 5018 identity = self.sql(expression, "identity") 5019 identity = f" {identity} IDENTITY" if identity else "" 5020 5021 option = self.sql(expression, "option") 5022 option = f" {option}" if option else "" 5023 5024 partition = self.sql(expression, "partition") 5025 partition = f" {partition}" if partition else "" 5026 5027 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 5028 5029 # This transpiles T-SQL's CONVERT function 5030 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 5031 def convert_sql(self, expression: exp.Convert) -> str: 5032 to = expression.this 5033 value = expression.expression 5034 style = expression.args.get("style") 5035 safe = expression.args.get("safe") 5036 strict = expression.args.get("strict") 5037 5038 if not to or not value: 5039 return "" 5040 5041 # Retrieve length of datatype and override to default if not specified 5042 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5043 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5044 5045 transformed: exp.Expr | None = None 5046 cast = exp.Cast if strict else exp.TryCast 5047 5048 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5049 if isinstance(style, exp.Literal) and style.is_int: 5050 import sqlglot.dialects.tsql 5051 5052 style_value = style.name 5053 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5054 if not converted_style: 5055 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5056 5057 fmt = exp.Literal.string(converted_style) 5058 5059 if to.this == exp.DType.DATE: 5060 transformed = exp.StrToDate(this=value, format=fmt) 5061 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5062 transformed = exp.StrToTime(this=value, format=fmt) 5063 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5064 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5065 elif to.this == exp.DType.TEXT: 5066 transformed = exp.TimeToStr(this=value, format=fmt) 5067 5068 if not transformed: 5069 transformed = cast(this=value, to=to, safe=safe) 5070 5071 return self.sql(transformed) 5072 5073 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 5074 this = expression.this 5075 if isinstance(this, exp.JSONPathWildcard): 5076 this = self.json_path_part(this) 5077 return f".{this}" if this else "" 5078 5079 if self.SAFE_JSON_PATH_KEY_RE.match(this): 5080 return f".{this}" 5081 5082 this = self.json_path_part(this) 5083 return ( 5084 f"[{this}]" 5085 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5086 else f".{this}" 5087 ) 5088 5089 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5090 this = self.json_path_part(expression.this) 5091 return f"[{this}]" if this else "" 5092 5093 def _simplify_unless_literal(self, expression: E) -> E: 5094 if not isinstance(expression, exp.Literal): 5095 import sqlglot.optimizer.simplify 5096 5097 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5098 5099 return expression 5100 5101 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5102 this = expression.this 5103 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5104 self.unsupported( 5105 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5106 ) 5107 return self.sql(this) 5108 5109 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 5110 if self.IGNORE_NULLS_BEFORE_ORDER: 5111 # The first modifier here will be the one closest to the AggFunc's arg 5112 mods = sorted( 5113 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5114 key=lambda x: ( 5115 0 5116 if isinstance(x, exp.HavingMax) 5117 else (1 if isinstance(x, exp.Order) else 2) 5118 ), 5119 ) 5120 5121 if mods: 5122 mod = mods[0] 5123 this = expression.__class__(this=mod.this.copy()) 5124 this.meta["inline"] = True 5125 mod.this.replace(this) 5126 return self.sql(expression.this) 5127 5128 agg_func = expression.find(exp.AggFunc) 5129 5130 if agg_func: 5131 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5132 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5133 5134 return f"{self.sql(expression, 'this')} {text}" 5135 5136 def _replace_line_breaks(self, string: str) -> str: 5137 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5138 if self.pretty: 5139 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5140 return string 5141 5142 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5143 option = self.sql(expression, "this") 5144 5145 if expression.expressions: 5146 upper = option.upper() 5147 5148 # Snowflake FILE_FORMAT options are separated by whitespace 5149 sep = " " if upper == "FILE_FORMAT" else ", " 5150 5151 # Databricks copy/format options do not set their list of values with EQ 5152 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5153 values = self.expressions(expression, flat=True, sep=sep) 5154 return f"{option}{op}({values})" 5155 5156 value = self.sql(expression, "expression") 5157 5158 if not value: 5159 return option 5160 5161 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5162 5163 return f"{option}{op}{value}" 5164 5165 def credentials_sql(self, expression: exp.Credentials) -> str: 5166 cred_expr = expression.args.get("credentials") 5167 if isinstance(cred_expr, exp.Literal): 5168 # Redshift case: CREDENTIALS <string> 5169 credentials = self.sql(expression, "credentials") 5170 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5171 else: 5172 # Snowflake case: CREDENTIALS = (...) 5173 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5174 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5175 5176 storage = self.sql(expression, "storage") 5177 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5178 5179 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5180 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5181 5182 iam_role = self.sql(expression, "iam_role") 5183 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5184 5185 region = self.sql(expression, "region") 5186 region = f" REGION {region}" if region else "" 5187 5188 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5189 5190 def copy_sql(self, expression: exp.Copy) -> str: 5191 this = self.sql(expression, "this") 5192 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5193 5194 credentials = self.sql(expression, "credentials") 5195 credentials = self.seg(credentials) if credentials else "" 5196 files = self.expressions(expression, key="files", flat=True) 5197 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5198 5199 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5200 params = self.expressions( 5201 expression, 5202 key="params", 5203 sep=sep, 5204 new_line=True, 5205 skip_last=True, 5206 skip_first=True, 5207 indent=self.COPY_PARAMS_ARE_WRAPPED, 5208 ) 5209 5210 if params: 5211 if self.COPY_PARAMS_ARE_WRAPPED: 5212 params = f" WITH ({params})" 5213 elif not self.pretty and (files or credentials): 5214 params = f" {params}" 5215 5216 return f"COPY{this}{kind} {files}{credentials}{params}" 5217 5218 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5219 return "" 5220 5221 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5222 on_sql = "ON" if expression.args.get("on") else "OFF" 5223 filter_col: str | None = self.sql(expression, "filter_column") 5224 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5225 retention_period: str | None = self.sql(expression, "retention_period") 5226 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5227 5228 if filter_col or retention_period: 5229 on_sql = self.func("ON", filter_col, retention_period) 5230 5231 return f"DATA_DELETION={on_sql}" 5232 5233 def maskingpolicycolumnconstraint_sql( 5234 self, expression: exp.MaskingPolicyColumnConstraint 5235 ) -> str: 5236 this = self.sql(expression, "this") 5237 expressions = self.expressions(expression, flat=True) 5238 expressions = f" USING ({expressions})" if expressions else "" 5239 return f"MASKING POLICY {this}{expressions}" 5240 5241 def gapfill_sql(self, expression: exp.GapFill) -> str: 5242 this = self.sql(expression, "this") 5243 this = f"TABLE {this}" 5244 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5245 5246 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5247 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5248 5249 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5250 this = self.sql(expression, "this") 5251 expr = expression.expression 5252 5253 if isinstance(expr, exp.Func): 5254 # T-SQL's CLR functions are case sensitive 5255 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5256 else: 5257 expr = self.sql(expression, "expression") 5258 5259 return self.scope_resolution(expr, this) 5260 5261 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5262 if self.PARSE_JSON_NAME is None: 5263 return self.sql(expression.this) 5264 5265 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5266 5267 def rand_sql(self, expression: exp.Rand) -> str: 5268 lower = self.sql(expression, "lower") 5269 upper = self.sql(expression, "upper") 5270 5271 if lower and upper: 5272 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5273 return self.func("RAND", expression.this) 5274 5275 def changes_sql(self, expression: exp.Changes) -> str: 5276 information = self.sql(expression, "information") 5277 information = f"INFORMATION => {information}" 5278 at_before = self.sql(expression, "at_before") 5279 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5280 end = self.sql(expression, "end") 5281 end = f"{self.seg('')}{end}" if end else "" 5282 5283 return f"CHANGES ({information}){at_before}{end}" 5284 5285 def pad_sql(self, expression: exp.Pad) -> str: 5286 prefix = "L" if expression.args.get("is_left") else "R" 5287 5288 fill_pattern = self.sql(expression, "fill_pattern") or None 5289 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5290 fill_pattern = "' '" 5291 5292 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5293 5294 def summarize_sql(self, expression: exp.Summarize) -> str: 5295 table = " TABLE" if expression.args.get("table") else "" 5296 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5297 5298 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5299 generate_series = exp.GenerateSeries(**expression.args) 5300 5301 parent = expression.parent 5302 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5303 parent = parent.parent 5304 5305 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5306 return self.sql(exp.Unnest(expressions=[generate_series])) 5307 5308 if isinstance(parent, exp.Select): 5309 self.unsupported("GenerateSeries projection unnesting is not supported.") 5310 5311 return self.sql(generate_series) 5312 5313 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5314 if self.SUPPORTS_CONVERT_TIMEZONE: 5315 return self.function_fallback_sql(expression) 5316 5317 source_tz = expression.args.get("source_tz") 5318 target_tz = expression.args.get("target_tz") 5319 timestamp = expression.args.get("timestamp") 5320 5321 if source_tz and timestamp: 5322 timestamp = exp.AtTimeZone( 5323 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5324 ) 5325 5326 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5327 5328 return self.sql(expr) 5329 5330 def json_sql(self, expression: exp.JSON) -> str: 5331 this = self.sql(expression, "this") 5332 this = f" {this}" if this else "" 5333 5334 _with = expression.args.get("with_") 5335 5336 if _with is None: 5337 with_sql = "" 5338 elif not _with: 5339 with_sql = " WITHOUT" 5340 else: 5341 with_sql = " WITH" 5342 5343 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5344 5345 return f"JSON{this}{with_sql}{unique_sql}" 5346 5347 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5348 path = self.sql(expression, "path") 5349 returning = self.sql(expression, "returning") 5350 returning = f" RETURNING {returning}" if returning else "" 5351 5352 on_condition = self.sql(expression, "on_condition") 5353 on_condition = f" {on_condition}" if on_condition else "" 5354 5355 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5356 5357 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5358 regexp = " REGEXP" if expression.args.get("regexp") else "" 5359 return f"SKIP{regexp} {self.sql(expression.expression)}" 5360 5361 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5362 else_ = "ELSE " if expression.args.get("else_") else "" 5363 condition = self.sql(expression, "expression") 5364 condition = f"WHEN {condition} THEN " if condition else else_ 5365 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5366 return f"{condition}{insert}" 5367 5368 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5369 kind = self.sql(expression, "kind") 5370 expressions = self.seg(self.expressions(expression, sep=" ")) 5371 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5372 return res 5373 5374 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5375 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5376 empty = expression.args.get("empty") 5377 empty = ( 5378 f"DEFAULT {empty} ON EMPTY" 5379 if isinstance(empty, exp.Expr) 5380 else self.sql(expression, "empty") 5381 ) 5382 5383 error = expression.args.get("error") 5384 error = ( 5385 f"DEFAULT {error} ON ERROR" 5386 if isinstance(error, exp.Expr) 5387 else self.sql(expression, "error") 5388 ) 5389 5390 if error and empty: 5391 error = ( 5392 f"{empty} {error}" 5393 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5394 else f"{error} {empty}" 5395 ) 5396 empty = "" 5397 5398 null = self.sql(expression, "null") 5399 5400 return f"{empty}{error}{null}" 5401 5402 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5403 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5404 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5405 5406 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5407 this = self.sql(expression, "this") 5408 path = self.sql(expression, "path") 5409 5410 passing = self.expressions(expression, "passing") 5411 passing = f" PASSING {passing}" if passing else "" 5412 5413 on_condition = self.sql(expression, "on_condition") 5414 on_condition = f" {on_condition}" if on_condition else "" 5415 5416 path = f"{path}{passing}{on_condition}" 5417 5418 return self.func("JSON_EXISTS", this, path) 5419 5420 def _add_arrayagg_null_filter( 5421 self, 5422 array_agg_sql: str, 5423 array_agg_expr: exp.ArrayAgg, 5424 column_expr: exp.Expr, 5425 ) -> str: 5426 """ 5427 Add NULL filter to ARRAY_AGG if dialect requires it. 5428 5429 Args: 5430 array_agg_sql: The generated ARRAY_AGG SQL string 5431 array_agg_expr: The ArrayAgg expression node 5432 column_expr: The column/expression to filter (before ORDER BY wrapping) 5433 5434 Returns: 5435 SQL string with FILTER clause added if needed 5436 """ 5437 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5438 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5439 if not ( 5440 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5441 ): 5442 return array_agg_sql 5443 5444 parent = array_agg_expr.parent 5445 if isinstance(parent, exp.Filter): 5446 parent_cond = parent.expression.this 5447 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5448 elif column_expr.find(exp.Column): 5449 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5450 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5451 this_sql = ( 5452 self.expressions(column_expr) 5453 if isinstance(column_expr, exp.Distinct) 5454 else self.sql(column_expr) 5455 ) 5456 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5457 5458 return array_agg_sql 5459 5460 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5461 array_agg = self.function_fallback_sql(expression) 5462 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5463 5464 def slice_sql(self, expression: exp.Slice) -> str: 5465 step = self.sql(expression, "step") 5466 end = self.sql(expression.expression) 5467 begin = self.sql(expression.this) 5468 5469 sql = f"{end}:{step}" if step else end 5470 return f"{begin}:{sql}" if sql else f"{begin}:" 5471 5472 def apply_sql(self, expression: exp.Apply) -> str: 5473 this = self.sql(expression, "this") 5474 expr = self.sql(expression, "expression") 5475 5476 return f"{this} APPLY({expr})" 5477 5478 def _grant_or_revoke_sql( 5479 self, 5480 expression: exp.Grant | exp.Revoke, 5481 keyword: str, 5482 preposition: str, 5483 grant_option_prefix: str = "", 5484 grant_option_suffix: str = "", 5485 ) -> str: 5486 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5487 5488 kind = self.sql(expression, "kind") 5489 kind = f" {kind}" if kind else "" 5490 5491 securable = self.sql(expression, "securable") 5492 securable = f" {securable}" if securable else "" 5493 5494 principals = self.expressions(expression, key="principals", flat=True) 5495 5496 if not expression.args.get("grant_option"): 5497 grant_option_prefix = grant_option_suffix = "" 5498 5499 # cascade for revoke only 5500 cascade = self.sql(expression, "cascade") 5501 cascade = f" {cascade}" if cascade else "" 5502 5503 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5504 5505 def grant_sql(self, expression: exp.Grant) -> str: 5506 return self._grant_or_revoke_sql( 5507 expression, 5508 keyword="GRANT", 5509 preposition="TO", 5510 grant_option_suffix=" WITH GRANT OPTION", 5511 ) 5512 5513 def revoke_sql(self, expression: exp.Revoke) -> str: 5514 return self._grant_or_revoke_sql( 5515 expression, 5516 keyword="REVOKE", 5517 preposition="FROM", 5518 grant_option_prefix="GRANT OPTION FOR ", 5519 ) 5520 5521 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5522 this = self.sql(expression, "this") 5523 columns = self.expressions(expression, flat=True) 5524 columns = f"({columns})" if columns else "" 5525 5526 return f"{this}{columns}" 5527 5528 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5529 this = self.sql(expression, "this") 5530 5531 kind = self.sql(expression, "kind") 5532 kind = f"{kind} " if kind else "" 5533 5534 return f"{kind}{this}" 5535 5536 def columns_sql(self, expression: exp.Columns) -> str: 5537 func = self.function_fallback_sql(expression) 5538 if expression.args.get("unpack"): 5539 func = f"*{func}" 5540 5541 return func 5542 5543 def overlay_sql(self, expression: exp.Overlay) -> str: 5544 this = self.sql(expression, "this") 5545 expr = self.sql(expression, "expression") 5546 from_sql = self.sql(expression, "from_") 5547 for_sql = self.sql(expression, "for_") 5548 for_sql = f" FOR {for_sql}" if for_sql else "" 5549 5550 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5551 5552 @unsupported_args("format") 5553 def todouble_sql(self, expression: exp.ToDouble) -> str: 5554 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5555 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5556 5557 def string_sql(self, expression: exp.String) -> str: 5558 this = expression.this 5559 zone = expression.args.get("zone") 5560 5561 if zone: 5562 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5563 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5564 # set for source_tz to transpile the time conversion before the STRING cast 5565 this = exp.ConvertTimezone( 5566 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5567 ) 5568 5569 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5570 5571 def median_sql(self, expression: exp.Median) -> str: 5572 if not self.SUPPORTS_MEDIAN: 5573 return self.sql( 5574 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5575 ) 5576 5577 return self.function_fallback_sql(expression) 5578 5579 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5580 filler = self.sql(expression, "this") 5581 filler = f" {filler}" if filler else "" 5582 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5583 return f"TRUNCATE{filler} {with_count}" 5584 5585 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5586 if self.SUPPORTS_UNIX_SECONDS: 5587 return self.function_fallback_sql(expression) 5588 5589 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5590 5591 return self.sql( 5592 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5593 ) 5594 5595 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5596 dim = expression.expression 5597 5598 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5599 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5600 if not (dim.is_int and dim.name == "1"): 5601 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5602 dim = None 5603 5604 # If dimension is required but not specified, default initialize it 5605 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5606 dim = exp.Literal.number(1) 5607 5608 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5609 5610 def attach_sql(self, expression: exp.Attach) -> str: 5611 this = self.sql(expression, "this") 5612 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5613 expressions = self.expressions(expression) 5614 expressions = f" ({expressions})" if expressions else "" 5615 5616 return f"ATTACH{exists_sql} {this}{expressions}" 5617 5618 def detach_sql(self, expression: exp.Detach) -> str: 5619 kind = self.sql(expression, "kind") 5620 kind = f" {kind}" if kind else "" 5621 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5622 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5623 exists = " IF EXISTS" if expression.args.get("exists") else "" 5624 if exists: 5625 kind = kind or " DATABASE" 5626 5627 this = self.sql(expression, "this") 5628 this = f" {this}" if this else "" 5629 cluster = self.sql(expression, "cluster") 5630 cluster = f" {cluster}" if cluster else "" 5631 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5632 sync = " SYNC" if expression.args.get("sync") else "" 5633 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5634 5635 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5636 this = self.sql(expression, "this") 5637 value = self.sql(expression, "expression") 5638 value = f" {value}" if value else "" 5639 return f"{this}{value}" 5640 5641 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5642 return ( 5643 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5644 ) 5645 5646 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5647 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5648 encode = f"{encode} {self.sql(expression, 'this')}" 5649 5650 properties = expression.args.get("properties") 5651 if properties: 5652 encode = f"{encode} {self.properties(properties)}" 5653 5654 return encode 5655 5656 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5657 this = self.sql(expression, "this") 5658 include = f"INCLUDE {this}" 5659 5660 column_def = self.sql(expression, "column_def") 5661 if column_def: 5662 include = f"{include} {column_def}" 5663 5664 alias = self.sql(expression, "alias") 5665 if alias: 5666 include = f"{include} AS {alias}" 5667 5668 return include 5669 5670 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5671 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5672 name = f"{prefix} {self.sql(expression, 'this')}" 5673 return self.func("XMLELEMENT", name, *expression.expressions) 5674 5675 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5676 this = self.sql(expression, "this") 5677 expr = self.sql(expression, "expression") 5678 expr = f"({expr})" if expr else "" 5679 return f"{this}{expr}" 5680 5681 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5682 partitions = self.expressions(expression, "partition_expressions") 5683 create = self.expressions(expression, "create_expressions") 5684 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5685 5686 def partitionbyrangepropertydynamic_sql( 5687 self, expression: exp.PartitionByRangePropertyDynamic 5688 ) -> str: 5689 start = self.sql(expression, "start") 5690 end = self.sql(expression, "end") 5691 5692 every = expression.args["every"] 5693 if isinstance(every, exp.Interval) and every.this.is_string: 5694 every.this.replace(exp.Literal.number(every.name)) 5695 5696 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5697 5698 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5699 name = self.sql(expression, "this") 5700 values = self.expressions(expression, flat=True) 5701 5702 return f"NAME {name} VALUE {values}" 5703 5704 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5705 kind = self.sql(expression, "kind") 5706 sample = self.sql(expression, "sample") 5707 return f"SAMPLE {sample} {kind}" 5708 5709 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5710 kind = self.sql(expression, "kind") 5711 option = self.sql(expression, "option") 5712 option = f" {option}" if option else "" 5713 this = self.sql(expression, "this") 5714 this = f" {this}" if this else "" 5715 columns = self.expressions(expression) 5716 columns = f" {columns}" if columns else "" 5717 return f"{kind}{option} STATISTICS{this}{columns}" 5718 5719 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5720 this = self.sql(expression, "this") 5721 columns = self.expressions(expression) 5722 inner_expression = self.sql(expression, "expression") 5723 inner_expression = f" {inner_expression}" if inner_expression else "" 5724 update_options = self.sql(expression, "update_options") 5725 update_options = f" {update_options} UPDATE" if update_options else "" 5726 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5727 5728 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5729 kind = self.sql(expression, "kind") 5730 kind = f" {kind}" if kind else "" 5731 return f"DELETE{kind} STATISTICS" 5732 5733 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5734 inner_expression = self.sql(expression, "expression") 5735 return f"LIST CHAINED ROWS{inner_expression}" 5736 5737 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5738 kind = self.sql(expression, "kind") 5739 this = self.sql(expression, "this") 5740 this = f" {this}" if this else "" 5741 inner_expression = self.sql(expression, "expression") 5742 return f"VALIDATE {kind}{this}{inner_expression}" 5743 5744 def analyze_sql(self, expression: exp.Analyze) -> str: 5745 options = self.expressions(expression, key="options", sep=" ") 5746 options = f" {options}" if options else "" 5747 kind = self.sql(expression, "kind") 5748 kind = f" {kind}" if kind else "" 5749 this = self.sql(expression, "this") 5750 this = f" {this}" if this else "" 5751 mode = self.sql(expression, "mode") 5752 mode = f" {mode}" if mode else "" 5753 properties = self.sql(expression, "properties") 5754 properties = f" {properties}" if properties else "" 5755 partition = self.sql(expression, "partition") 5756 partition = f" {partition}" if partition else "" 5757 inner_expression = self.sql(expression, "expression") 5758 inner_expression = f" {inner_expression}" if inner_expression else "" 5759 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5760 5761 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5762 this = self.sql(expression, "this") 5763 namespaces = self.expressions(expression, key="namespaces") 5764 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5765 passing = self.expressions(expression, key="passing") 5766 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5767 columns = self.expressions(expression, key="columns") 5768 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5769 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5770 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5771 5772 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5773 this = self.sql(expression, "this") 5774 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5775 5776 def export_sql(self, expression: exp.Export) -> str: 5777 this = self.sql(expression, "this") 5778 connection = self.sql(expression, "connection") 5779 connection = f"WITH CONNECTION {connection} " if connection else "" 5780 options = self.sql(expression, "options") 5781 return f"EXPORT DATA {connection}{options} AS {this}" 5782 5783 def declare_sql(self, expression: exp.Declare) -> str: 5784 replace = "OR REPLACE " if expression.args.get("replace") else "" 5785 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 5786 5787 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5788 variables = self.expressions(expression, "this") 5789 default = self.sql(expression, "default") 5790 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5791 5792 kind = self.sql(expression, "kind") 5793 if isinstance(expression.args.get("kind"), exp.Schema): 5794 kind = f"TABLE {kind}" 5795 5796 kind = f" {kind}" if kind else "" 5797 5798 return f"{variables}{kind}{default}" 5799 5800 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5801 kind = self.sql(expression, "kind") 5802 this = self.sql(expression, "this") 5803 set = self.sql(expression, "expression") 5804 using = self.sql(expression, "using") 5805 using = f" USING {using}" if using else "" 5806 5807 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5808 5809 return f"{kind_sql} {this} SET {set}{using}" 5810 5811 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5812 params = self.expressions(expression, key="params", flat=True) 5813 return self.func(expression.name, *expression.expressions) + f"({params})" 5814 5815 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5816 return self.func(expression.name, *expression.expressions) 5817 5818 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5819 return self.anonymousaggfunc_sql(expression) 5820 5821 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5822 return self.parameterizedagg_sql(expression) 5823 5824 def show_sql(self, expression: exp.Show) -> str: 5825 self.unsupported("Unsupported SHOW statement") 5826 return "" 5827 5828 def install_sql(self, expression: exp.Install) -> str: 5829 self.unsupported("Unsupported INSTALL statement") 5830 return "" 5831 5832 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5833 # Snowflake GET/PUT statements: 5834 # PUT <file> <internalStage> <properties> 5835 # GET <internalStage> <file> <properties> 5836 props = expression.args.get("properties") 5837 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5838 this = self.sql(expression, "this") 5839 target = self.sql(expression, "target") 5840 5841 if isinstance(expression, exp.Put): 5842 return f"PUT {this} {target}{props_sql}" 5843 else: 5844 return f"GET {target} {this}{props_sql}" 5845 5846 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5847 this = self.sql(expression, "this") 5848 expr = self.sql(expression, "expression") 5849 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5850 return f"TRANSLATE({this} USING {expr}{with_error})" 5851 5852 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5853 if self.SUPPORTS_DECODE_CASE: 5854 return self.func("DECODE", *expression.expressions) 5855 5856 decode_expr, *expressions = expression.expressions 5857 5858 ifs = [] 5859 for search, result in zip(expressions[::2], expressions[1::2]): 5860 if isinstance(search, exp.Literal): 5861 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 5862 elif isinstance(search, exp.Null): 5863 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 5864 else: 5865 if isinstance(search, exp.Binary): 5866 search = exp.paren(search) 5867 5868 cond = exp.or_( 5869 decode_expr.eq(search), 5870 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5871 copy=False, 5872 ) 5873 ifs.append(exp.If(this=cond, true=result)) 5874 5875 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5876 return self.sql(case) 5877 5878 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5879 this = self.sql(expression, "this") 5880 this = self.seg(this, sep="") 5881 dimensions = self.expressions( 5882 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5883 ) 5884 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5885 metrics = self.expressions( 5886 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5887 ) 5888 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5889 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5890 facts = self.seg(f"FACTS {facts}") if facts else "" 5891 where = self.sql(expression, "where") 5892 where = self.seg(f"WHERE {where}") if where else "" 5893 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5894 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5895 5896 def getextract_sql(self, expression: exp.GetExtract) -> str: 5897 this = expression.this 5898 expr = expression.expression 5899 5900 if not this.type or not expression.type: 5901 import sqlglot.optimizer.annotate_types 5902 5903 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 5904 5905 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 5906 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5907 5908 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5909 5910 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5911 return self.sql( 5912 exp.DateAdd( 5913 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5914 expression=expression.this, 5915 unit=exp.var("DAY"), 5916 ) 5917 ) 5918 5919 def space_sql(self: Generator, expression: exp.Space) -> str: 5920 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5921 5922 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5923 return f"BUILD {self.sql(expression, 'this')}" 5924 5925 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5926 method = self.sql(expression, "method") 5927 kind = expression.args.get("kind") 5928 if not kind: 5929 return f"REFRESH {method}" 5930 5931 every = self.sql(expression, "every") 5932 unit = self.sql(expression, "unit") 5933 every = f" EVERY {every} {unit}" if every else "" 5934 starts = self.sql(expression, "starts") 5935 starts = f" STARTS {starts}" if starts else "" 5936 5937 return f"REFRESH {method} ON {kind}{every}{starts}" 5938 5939 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5940 self.unsupported("The model!attribute syntax is not supported") 5941 return "" 5942 5943 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5944 return self.func("DIRECTORY", expression.this) 5945 5946 def uuid_sql(self, expression: exp.Uuid) -> str: 5947 is_string = expression.args.get("is_string", False) 5948 uuid_func_sql = self.func("UUID") 5949 5950 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5951 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 5952 5953 return uuid_func_sql 5954 5955 def initcap_sql(self, expression: exp.Initcap) -> str: 5956 delimiters = expression.expression 5957 5958 if delimiters: 5959 # do not generate delimiters arg if we are round-tripping from default delimiters 5960 if ( 5961 delimiters.is_string 5962 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5963 ): 5964 delimiters = None 5965 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5966 self.unsupported("INITCAP does not support custom delimiters") 5967 delimiters = None 5968 5969 return self.func("INITCAP", expression.this, delimiters) 5970 5971 def localtime_sql(self, expression: exp.Localtime) -> str: 5972 this = expression.this 5973 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5974 5975 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 5976 this = expression.this 5977 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5978 5979 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5980 this = expression.this.name.upper() 5981 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5982 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5983 return "WEEK" 5984 5985 return self.func("WEEK", expression.this) 5986 5987 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5988 this = self.expressions(expression) 5989 charset = self.sql(expression, "charset") 5990 using = f" USING {charset}" if charset else "" 5991 return self.func(name, this + using) 5992 5993 def block_sql(self, expression: exp.Block) -> str: 5994 expressions = self.expressions(expression, sep="; ", flat=True) 5995 return f"{expressions}" if expressions else "" 5996 5997 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 5998 self.unsupported("Unsupported Stored Procedure syntax") 5999 return "" 6000 6001 def ifblock_sql(self, expression: exp.IfBlock) -> str: 6002 self.unsupported("Unsupported If block syntax") 6003 return "" 6004 6005 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 6006 self.unsupported("Unsupported While block syntax") 6007 return "" 6008 6009 def execute_sql(self, expression: exp.Execute) -> str: 6010 self.unsupported("Unsupported Execute syntax") 6011 return "" 6012 6013 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 6014 self.unsupported("Unsupported Execute syntax") 6015 return "" 6016 6017 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 6018 props = self.expressions(expression, sep=" ") 6019 return f"MODIFY {props}" 6020 6021 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 6022 kind = expression.args.get("kind") 6023 return f"USING {kind} {self.sql(expression, 'this')}" 6024 6025 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 6026 this = self.sql(expression, "this") 6027 to = self.sql(expression, "to") 6028 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)
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
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.JSONPathSubscript'>, <class 'sqlglot.expressions.query.JSONPathSelector'>, <class 'sqlglot.expressions.query.JSONPathWildcard'>, <class 'sqlglot.expressions.query.JSONPathSlice'>, <class 'sqlglot.expressions.query.JSONPathScript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>, <class 'sqlglot.expressions.query.JSONPathUnion'>, <class 'sqlglot.expressions.query.JSONPathRecursive'>, <class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathFilter'>}
TYPE_MAPPING: ClassVar =
{<DType.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <DType.NCHAR: 'NCHAR'>: 'CHAR', <DType.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <DType.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <DType.LONGTEXT: 'LONGTEXT'>: 'TEXT', <DType.TINYTEXT: 'TINYTEXT'>: 'TEXT', <DType.BLOB: 'BLOB'>: 'VARBINARY', <DType.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <DType.LONGBLOB: 'LONGBLOB'>: 'BLOB', <DType.TINYBLOB: 'TINYBLOB'>: 'BLOB', <DType.INET: 'INET'>: 'INET', <DType.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <DType.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TYPE_PARAM_SETTINGS: ClassVar[dict[sqlglot.expressions.datatypes.DType, tuple[tuple[int, ...], tuple[int | None, ...]]]] =
{}
TIME_PART_SINGULARS: ClassVar =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS: ClassVar =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function <lambda>>, 'qualify': <function <lambda>>}
PROPERTIES_LOCATION: ClassVar =
{<class 'sqlglot.expressions.properties.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.DistributedByProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DuplicateKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DataBlocksizeProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.DatabaseProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DataDeletionProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DefinerProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DictRange'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DynamicProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.DistKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.DistStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EmptyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EncodeProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.EngineProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.EnviromentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.HandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ParameterStyleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExecuteAsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ExternalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.FallbackProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.FileFormatProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.FreespaceProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.GlobalProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.HeapProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.HybridProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.InheritsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IcebergProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.IncludeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.InputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.IsolatedLoadingProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.JournalProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.LanguageProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LikeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LocationProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.LockingProperty'>: <PropertiesLocation.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.properties.LogProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.MaskingProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MaterializedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.MergeBlockRatioProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.ModuleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.NetworkProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.NoPrimaryIndexProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.OnProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OnCommitProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.query.Order'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.OutputModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.PartitionedByProperty'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.PartitionedOfProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.constraints.PrimaryKey'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Property'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.RefreshTriggerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RemoteWithConnectionModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ReturnsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RollupProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowAccessProperty'>: <PropertiesLocation.UNSUPPORTED: 'UNSUPPORTED'>, <class 'sqlglot.expressions.properties.RowFormatProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatDelimitedProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.RowFormatSerdeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SampleProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SchemaCommentProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SecureProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SecurityIntegrationProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SerdeProperties'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.Set'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SettingsProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SetProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.SetConfigProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SharingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.SequenceProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.ddl.TriggerProperties'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.SortKeyProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlReadWriteProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.SqlSecurityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StabilityProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StorageHandlerProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.StreamingTableProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.StrictProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.Tags'>: <PropertiesLocation.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.properties.TemporaryProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.ToTableProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.TransientProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.TransformModelProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ddl.MergeTreeTTL'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.UnloggedProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.UsingProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.UsingTemplateProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ViewAttributeProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.VirtualProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.VolatileProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.properties.WithDataProperty'>: <PropertiesLocation.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.properties.WithJournalTableProperty'>: <PropertiesLocation.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.properties.WithProcedureOptions'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSchemaBindingProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.WithSystemVersioningProperty'>: <PropertiesLocation.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.properties.ForceProperty'>: <PropertiesLocation.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.ddl.Command'>, <class 'sqlglot.expressions.ddl.Create'>, <class 'sqlglot.expressions.ddl.Describe'>, <class 'sqlglot.expressions.dml.Delete'>, <class 'sqlglot.expressions.ddl.Drop'>, <class 'sqlglot.expressions.query.From'>, <class 'sqlglot.expressions.dml.Insert'>, <class 'sqlglot.expressions.query.Join'>, <class 'sqlglot.expressions.query.MultitableInserts'>, <class 'sqlglot.expressions.query.Order'>, <class 'sqlglot.expressions.query.Group'>, <class 'sqlglot.expressions.query.Having'>, <class 'sqlglot.expressions.query.Select'>, <class 'sqlglot.expressions.query.SetOperation'>, <class 'sqlglot.expressions.dml.Update'>, <class 'sqlglot.expressions.query.Where'>, <class 'sqlglot.expressions.query.With'>)
EXCLUDE_COMMENTS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Binary'>, <class 'sqlglot.expressions.query.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
(<class 'sqlglot.expressions.core.Column'>, <class 'sqlglot.expressions.core.Literal'>, <class 'sqlglot.expressions.core.Neg'>, <class 'sqlglot.expressions.core.Paren'>)
PARAMETERIZABLE_TEXT_TYPES: ClassVar =
{<DType.VARCHAR: 'VARCHAR'>, <DType.NVARCHAR: 'NVARCHAR'>, <DType.NCHAR: 'NCHAR'>, <DType.CHAR: 'CHAR'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
()
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
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.
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
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
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
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
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}"
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='')}"
def
indent( self, sql: str, level: int = 0, pad: int | None = None, skip_first: bool = False, skip_last: bool = False) -> str:
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 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
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
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)
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}"
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}"
def
columnconstraint_sql( self, expression: sqlglot.expressions.constraints.ColumnConstraint) -> str:
def
computedcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.ComputedColumnConstraint) -> str:
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}"
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:
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}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
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}"
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:
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}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
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}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
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 ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
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 self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1333 postalias_props_sql = "" 1334 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1335 postalias_props_sql = self.properties( 1336 exp.Properties( 1337 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1338 ), 1339 wrapped=False, 1340 ) 1341 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1342 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1343 1344 postindex_props_sql = "" 1345 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1346 postindex_props_sql = self.properties( 1347 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1348 wrapped=False, 1349 prefix=" ", 1350 ) 1351 1352 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1353 indexes = f" {indexes}" if indexes else "" 1354 index_sql = indexes + postindex_props_sql 1355 1356 replace = " OR REPLACE" if expression.args.get("replace") else "" 1357 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1358 unique = " UNIQUE" if expression.args.get("unique") else "" 1359 1360 clustered = expression.args.get("clustered") 1361 if clustered is None: 1362 clustered_sql = "" 1363 elif clustered: 1364 clustered_sql = " CLUSTERED COLUMNSTORE" 1365 else: 1366 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1367 1368 postcreate_props_sql = "" 1369 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1370 postcreate_props_sql = self.properties( 1371 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1372 sep=" ", 1373 prefix=" ", 1374 wrapped=False, 1375 ) 1376 1377 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1378 1379 postexpression_props_sql = "" 1380 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1381 postexpression_props_sql = self.properties( 1382 exp.Properties( 1383 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1384 ), 1385 sep=" ", 1386 prefix=" ", 1387 wrapped=False, 1388 ) 1389 1390 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1391 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1392 no_schema_binding = ( 1393 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1394 ) 1395 1396 clone = self.sql(expression, "clone") 1397 clone = f" {clone}" if clone else "" 1398 1399 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1400 properties_expression = f"{expression_sql}{properties_sql}" 1401 else: 1402 properties_expression = f"{properties_sql}{expression_sql}" 1403 1404 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1405 return self.prepend_ctes(expression, expression_sql)
1407 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1408 start = self.sql(expression, "start") 1409 start = f"START WITH {start}" if start else "" 1410 increment = self.sql(expression, "increment") 1411 increment = f" INCREMENT BY {increment}" if increment else "" 1412 minvalue = self.sql(expression, "minvalue") 1413 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1414 maxvalue = self.sql(expression, "maxvalue") 1415 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1416 owned = self.sql(expression, "owned") 1417 owned = f" OWNED BY {owned}" if owned else "" 1418 1419 cache = expression.args.get("cache") 1420 if cache is None: 1421 cache_str = "" 1422 elif cache is True: 1423 cache_str = " CACHE" 1424 else: 1425 cache_str = f" CACHE {cache}" 1426 1427 options = self.expressions(expression, key="options", flat=True, sep=" ") 1428 options = f" {options}" if options else "" 1429 1430 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1432 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1433 timing = expression.args.get("timing", "") 1434 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1435 timing_events = f"{timing} {events}".strip() if timing or events else "" 1436 1437 parts = [timing_events, "ON", self.sql(expression, "table")] 1438 1439 if referenced_table := expression.args.get("referenced_table"): 1440 parts.extend(["FROM", self.sql(referenced_table)]) 1441 1442 if deferrable := expression.args.get("deferrable"): 1443 parts.append(deferrable) 1444 1445 if initially := expression.args.get("initially"): 1446 parts.append(f"INITIALLY {initially}") 1447 1448 if referencing := expression.args.get("referencing"): 1449 parts.append(self.sql(referencing)) 1450 1451 if for_each := expression.args.get("for_each"): 1452 parts.append(f"FOR EACH {for_each}") 1453 1454 if when := expression.args.get("when"): 1455 parts.append(f"WHEN ({self.sql(when)})") 1456 1457 parts.append(self.sql(expression, "execute")) 1458 1459 return self.sep().join(parts)
1461 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1462 parts = [] 1463 1464 if old_alias := expression.args.get("old"): 1465 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1466 1467 if new_alias := expression.args.get("new"): 1468 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1469 1470 return f"REFERENCING {' '.join(parts)}"
1479 def clone_sql(self, expression: exp.Clone) -> str: 1480 this = self.sql(expression, "this") 1481 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1482 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1483 return f"{shallow}{keyword} {this}"
1485 def describe_sql(self, expression: exp.Describe) -> str: 1486 style = expression.args.get("style") 1487 style = f" {style}" if style else "" 1488 partition = self.sql(expression, "partition") 1489 partition = f" {partition}" if partition else "" 1490 format = self.sql(expression, "format") 1491 format = f" {format}" if format else "" 1492 as_json = " AS JSON" if expression.args.get("as_json") else "" 1493 1494 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1506 def with_sql(self, expression: exp.With) -> str: 1507 sql = self.expressions(expression, flat=True) 1508 recursive = ( 1509 "RECURSIVE " 1510 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1511 else "" 1512 ) 1513 search = self.sql(expression, "search") 1514 search = f" {search}" if search else "" 1515 1516 return f"WITH {recursive}{sql}{search}"
1518 def cte_sql(self, expression: exp.CTE) -> str: 1519 alias = expression.args.get("alias") 1520 if alias: 1521 alias.add_comments(expression.pop_comments()) 1522 1523 alias_sql = self.sql(expression, "alias") 1524 1525 materialized = expression.args.get("materialized") 1526 if materialized is False: 1527 materialized = "NOT MATERIALIZED " 1528 elif materialized: 1529 materialized = "MATERIALIZED " 1530 1531 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1532 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1533 1534 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1536 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1537 alias = self.sql(expression, "this") 1538 columns = self.expressions(expression, key="columns", flat=True) 1539 columns = f"({columns})" if columns else "" 1540 1541 if ( 1542 columns 1543 and not self.SUPPORTS_TABLE_ALIAS_COLUMNS 1544 and not (self.SUPPORTS_NAMED_CTE_COLUMNS and isinstance(expression.parent, exp.CTE)) 1545 ): 1546 columns = "" 1547 self.unsupported("Named columns are not supported in table alias.") 1548 1549 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1550 alias = self._next_name() 1551 1552 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1560 def hexstring_sql( 1561 self, expression: exp.HexString, binary_function_repr: str | None = None 1562 ) -> str: 1563 this = self.sql(expression, "this") 1564 is_integer_type = expression.args.get("is_integer") 1565 1566 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1567 not self.dialect.HEX_START and not binary_function_repr 1568 ): 1569 # Integer representation will be returned if: 1570 # - The read dialect treats the hex value as integer literal but not the write 1571 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1572 return f"{int(this, 16)}" 1573 1574 if not is_integer_type: 1575 # Read dialect treats the hex value as BINARY/BLOB 1576 if binary_function_repr: 1577 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1578 return self.func(binary_function_repr, exp.Literal.string(this)) 1579 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1580 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1581 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1582 1583 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1585 def bytestring_sql(self, expression: exp.ByteString) -> str: 1586 this = self.sql(expression, "this") 1587 if self.dialect.BYTE_START: 1588 escaped_byte_string = self.escape_str( 1589 this, 1590 escape_backslash=False, 1591 delimiter=self.dialect.BYTE_END, 1592 escaped_delimiter=self._escaped_byte_quote_end, 1593 is_byte_string=True, 1594 ) 1595 is_bytes = expression.args.get("is_bytes", False) 1596 delimited_byte_string = ( 1597 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1598 ) 1599 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1600 return self.sql( 1601 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1602 ) 1603 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1604 return self.sql( 1605 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1606 ) 1607 1608 return delimited_byte_string 1609 return this
1611 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1612 this = self.sql(expression, "this") 1613 escape = expression.args.get("escape") 1614 1615 if self.dialect.UNICODE_START: 1616 escape_substitute = r"\\\1" 1617 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1618 else: 1619 escape_substitute = r"\\u\1" 1620 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1621 1622 if escape: 1623 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1624 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1625 else: 1626 escape_pattern = ESCAPED_UNICODE_RE 1627 escape_sql = "" 1628 1629 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1630 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1631 1632 return f"{left_quote}{this}{right_quote}{escape_sql}"
1634 def rawstring_sql(self, expression: exp.RawString) -> str: 1635 string = expression.this 1636 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1637 string = string.replace("\\", "\\\\") 1638 1639 string = self.escape_str(string, escape_backslash=False) 1640 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:
1648 def datatype_param_bound_limiter( 1649 self, 1650 expression: exp.DataType, 1651 type_value: exp.DType, 1652 defaults: tuple[int, ...], 1653 bounds: tuple[int | None, ...], 1654 ) -> exp.DataType: 1655 params = expression.expressions 1656 1657 if not params: 1658 if defaults: 1659 expression.set( 1660 "expressions", 1661 [exp.DataTypeParam(this=exp.Literal.number(d)) for d in defaults], 1662 ) 1663 return expression 1664 1665 if not bounds: 1666 return expression 1667 1668 for i, param in enumerate(params): 1669 bound = bounds[i] if i < len(bounds) else None 1670 if bound is None: 1671 continue 1672 1673 param_value = param.this if isinstance(param, exp.DataTypeParam) else param 1674 if ( 1675 isinstance(param_value, exp.Literal) 1676 and param_value.is_number 1677 and int(param_value.to_py()) > bound 1678 ): 1679 self.unsupported( 1680 f"{type_value.value} parameter {param_value.name} exceeds " 1681 f"{self.dialect.__class__.__name__}'s maximum of {bound}; capping" 1682 ) 1683 params[i] = exp.DataTypeParam(this=exp.Literal.number(bound)) 1684 1685 return expression
1687 def datatype_sql(self, expression: exp.DataType) -> str: 1688 nested = "" 1689 values = "" 1690 1691 expr_nested = expression.args.get("nested") 1692 type_value = expression.this 1693 1694 if ( 1695 not expr_nested 1696 and isinstance(type_value, exp.DType) 1697 and (settings := self.TYPE_PARAM_SETTINGS.get(type_value)) 1698 ): 1699 expression = self.datatype_param_bound_limiter(expression, type_value, *settings) 1700 1701 interior = ( 1702 self.expressions( 1703 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1704 ) 1705 if expr_nested and self.pretty 1706 else self.expressions(expression, flat=True) 1707 ) 1708 1709 if type_value in self.UNSUPPORTED_TYPES: 1710 self.unsupported( 1711 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1712 ) 1713 1714 type_sql: t.Any = "" 1715 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1716 type_sql = self.sql(expression, "kind") 1717 elif type_value == exp.DType.CHARACTER_SET: 1718 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1719 else: 1720 type_sql = ( 1721 self.TYPE_MAPPING.get(type_value, type_value.value) 1722 if isinstance(type_value, exp.DType) 1723 else type_value 1724 ) 1725 1726 if interior: 1727 if expr_nested: 1728 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1729 if expression.args.get("values") is not None: 1730 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1731 values = self.expressions(expression, key="values", flat=True) 1732 values = f"{delimiters[0]}{values}{delimiters[1]}" 1733 elif type_value == exp.DType.INTERVAL: 1734 nested = f" {interior}" 1735 else: 1736 nested = f"({interior})" 1737 1738 type_sql = f"{type_sql}{nested}{values}" 1739 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1740 exp.DType.TIMETZ, 1741 exp.DType.TIMESTAMPTZ, 1742 ): 1743 type_sql = f"{type_sql} WITH TIME ZONE" 1744 1745 collate = self.sql(expression, "collate") 1746 if collate: 1747 type_sql = f"{type_sql} COLLATE {collate}" 1748 1749 return type_sql
1751 def directory_sql(self, expression: exp.Directory) -> str: 1752 local = "LOCAL " if expression.args.get("local") else "" 1753 row_format = self.sql(expression, "row_format") 1754 row_format = f" {row_format}" if row_format else "" 1755 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1757 def delete_sql(self, expression: exp.Delete) -> str: 1758 hint = self.sql(expression, "hint") 1759 this = self.sql(expression, "this") 1760 this = f" FROM {this}" if this else "" 1761 using = self.expressions(expression, key="using") 1762 using = f" USING {using}" if using else "" 1763 cluster = self.sql(expression, "cluster") 1764 cluster = f" {cluster}" if cluster else "" 1765 where = self.sql(expression, "where") 1766 returning = self.sql(expression, "returning") 1767 order = self.sql(expression, "order") 1768 limit = self.sql(expression, "limit") 1769 tables = self.expressions(expression, key="tables") 1770 tables = f" {tables}" if tables else "" 1771 if self.RETURNING_END: 1772 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1773 else: 1774 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1775 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1777 def drop_sql(self, expression: exp.Drop) -> str: 1778 this = self.sql(expression, "this") 1779 expressions = self.expressions(expression, flat=True) 1780 expressions = f" ({expressions})" if expressions else "" 1781 kind = expression.args["kind"] 1782 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1783 iceberg = ( 1784 " ICEBERG" 1785 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1786 else "" 1787 ) 1788 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1789 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1790 on_cluster = self.sql(expression, "cluster") 1791 on_cluster = f" {on_cluster}" if on_cluster else "" 1792 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1793 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1794 cascade = " CASCADE" if expression.args.get("cascade") else "" 1795 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1796 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1797 purge = " PURGE" if expression.args.get("purge") else "" 1798 sync = " SYNC" if expression.args.get("sync") else "" 1799 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1801 def set_operation(self, expression: exp.SetOperation) -> str: 1802 op_type = type(expression) 1803 op_name = op_type.key.upper() 1804 1805 distinct = expression.args.get("distinct") 1806 if ( 1807 distinct is False 1808 and op_type in (exp.Except, exp.Intersect) 1809 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1810 ): 1811 self.unsupported(f"{op_name} ALL is not supported") 1812 1813 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1814 1815 if distinct is None: 1816 distinct = default_distinct 1817 if distinct is None: 1818 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1819 1820 if distinct is default_distinct: 1821 distinct_or_all = "" 1822 else: 1823 distinct_or_all = " DISTINCT" if distinct else " ALL" 1824 1825 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1826 side_kind = f"{side_kind} " if side_kind else "" 1827 1828 by_name = " BY NAME" if expression.args.get("by_name") else "" 1829 on = self.expressions(expression, key="on", flat=True) 1830 on = f" ON ({on})" if on else "" 1831 1832 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1834 def set_operations(self, expression: exp.SetOperation) -> str: 1835 if not self.SET_OP_MODIFIERS: 1836 limit = expression.args.get("limit") 1837 order = expression.args.get("order") 1838 1839 if limit or order: 1840 select = self._move_ctes_to_top_level( 1841 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1842 ) 1843 1844 if limit: 1845 select = select.limit(limit.pop(), copy=False) 1846 if order: 1847 select = select.order_by(order.pop(), copy=False) 1848 return self.sql(select) 1849 1850 sqls: list[str] = [] 1851 stack: list[str | exp.Expr] = [expression] 1852 1853 while stack: 1854 node = stack.pop() 1855 1856 if isinstance(node, exp.SetOperation): 1857 stack.append(node.expression) 1858 stack.append( 1859 self.maybe_comment( 1860 self.set_operation(node), comments=node.comments, separated=True 1861 ) 1862 ) 1863 stack.append(node.this) 1864 else: 1865 sqls.append(self.sql(node)) 1866 1867 this = self.sep().join(sqls) 1868 this = self.query_modifiers(expression, this) 1869 return self.prepend_ctes(expression, this)
1871 def fetch_sql(self, expression: exp.Fetch) -> str: 1872 direction = expression.args.get("direction") 1873 direction = f" {direction}" if direction else "" 1874 count = self.sql(expression, "count") 1875 count = f" {count}" if count else "" 1876 limit_options = self.sql(expression, "limit_options") 1877 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1878 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1880 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1881 percent = " PERCENT" if expression.args.get("percent") else "" 1882 rows = " ROWS" if expression.args.get("rows") else "" 1883 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1884 if not with_ties and rows: 1885 with_ties = " ONLY" 1886 return f"{percent}{rows}{with_ties}"
1888 def filter_sql(self, expression: exp.Filter) -> str: 1889 if self.AGGREGATE_FILTER_SUPPORTED: 1890 this = self.sql(expression, "this") 1891 where = self.sql(expression, "expression").strip() 1892 return f"{this} FILTER({where})" 1893 1894 agg = expression.this 1895 agg_arg = agg.this 1896 cond = expression.expression.this 1897 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1898 return self.sql(agg)
1907 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1908 using = self.sql(expression, "using") 1909 using = f" USING {using}" if using else "" 1910 columns = self.expressions(expression, key="columns", flat=True) 1911 columns = f"({columns})" if columns else "" 1912 partition_by = self.expressions(expression, key="partition_by", flat=True) 1913 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1914 where = self.sql(expression, "where") 1915 include = self.expressions(expression, key="include", flat=True) 1916 if include: 1917 include = f" INCLUDE ({include})" 1918 with_storage = self.expressions(expression, key="with_storage", flat=True) 1919 with_storage = f" WITH ({with_storage})" if with_storage else "" 1920 tablespace = self.sql(expression, "tablespace") 1921 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1922 on = self.sql(expression, "on") 1923 on = f" ON {on}" if on else "" 1924 1925 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1927 def index_sql(self, expression: exp.Index) -> str: 1928 unique = "UNIQUE " if expression.args.get("unique") else "" 1929 primary = "PRIMARY " if expression.args.get("primary") else "" 1930 amp = "AMP " if expression.args.get("amp") else "" 1931 name = self.sql(expression, "this") 1932 name = f"{name} " if name else "" 1933 table = self.sql(expression, "table") 1934 table = f"{self.INDEX_ON} {table}" if table else "" 1935 1936 index = "INDEX " if not table else "" 1937 1938 params = self.sql(expression, "params") 1939 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1941 def identifier_sql(self, expression: exp.Identifier) -> str: 1942 text = expression.name 1943 lower = text.lower() 1944 quoted = expression.quoted 1945 text = lower if self.normalize and not quoted else text 1946 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1947 if ( 1948 quoted 1949 or self.dialect.can_quote(expression, self.identify) 1950 or lower in self.RESERVED_KEYWORDS 1951 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1952 ): 1953 text = ( 1954 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1955 ) 1956 return text
1971 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1972 input_format = self.sql(expression, "input_format") 1973 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1974 output_format = self.sql(expression, "output_format") 1975 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1976 return self.sep().join((input_format, output_format))
1986 def properties_sql(self, expression: exp.Properties) -> str: 1987 root_properties = [] 1988 with_properties = [] 1989 1990 for p in expression.expressions: 1991 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1992 if p_loc == exp.Properties.Location.POST_WITH: 1993 with_properties.append(p) 1994 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1995 root_properties.append(p) 1996 1997 root_props_ast = exp.Properties(expressions=root_properties) 1998 root_props_ast.parent = expression.parent 1999 2000 with_props_ast = exp.Properties(expressions=with_properties) 2001 with_props_ast.parent = expression.parent 2002 2003 root_props = self.root_properties(root_props_ast) 2004 with_props = self.with_properties(with_props_ast) 2005 2006 if root_props and with_props and not self.pretty: 2007 with_props = " " + with_props 2008 2009 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
2016 def properties( 2017 self, 2018 properties: exp.Properties, 2019 prefix: str = "", 2020 sep: str = ", ", 2021 suffix: str = "", 2022 wrapped: bool = True, 2023 ) -> str: 2024 if properties.expressions: 2025 expressions = self.expressions(properties, sep=sep, indent=False) 2026 if expressions: 2027 expressions = self.wrap(expressions) if wrapped else expressions 2028 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 2029 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
2034 def locate_properties(self, properties: exp.Properties) -> defaultdict: 2035 properties_locs = defaultdict(list) 2036 for p in properties.expressions: 2037 p_loc = self.PROPERTIES_LOCATION[p.__class__] 2038 if p_loc != exp.Properties.Location.UNSUPPORTED: 2039 properties_locs[p_loc].append(p) 2040 else: 2041 self.unsupported(f"Unsupported property {p.key}") 2042 2043 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
2050 def property_sql(self, expression: exp.Property) -> str: 2051 property_cls = expression.__class__ 2052 if property_cls == exp.Property: 2053 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 2054 2055 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 2056 if not property_name: 2057 self.unsupported(f"Unsupported property {expression.key}") 2058 2059 return f"{property_name}={self.sql(expression, 'this')}"
2064 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 2065 if self.SUPPORTS_CREATE_TABLE_LIKE: 2066 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2067 options = f" {options}" if options else "" 2068 2069 like = f"LIKE {self.sql(expression, 'this')}{options}" 2070 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2071 like = f"({like})" 2072 2073 return like 2074 2075 if expression.expressions: 2076 self.unsupported("Transpilation of LIKE property options is unsupported") 2077 2078 select = exp.select("*").from_(expression.this).limit(0) 2079 return f"AS {self.sql(select)}"
2086 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2087 no = "NO " if expression.args.get("no") else "" 2088 local = expression.args.get("local") 2089 local = f"{local} " if local else "" 2090 dual = "DUAL " if expression.args.get("dual") else "" 2091 before = "BEFORE " if expression.args.get("before") else "" 2092 after = "AFTER " if expression.args.get("after") else "" 2093 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:
2109 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2110 if expression.args.get("no"): 2111 return "NO MERGEBLOCKRATIO" 2112 if expression.args.get("default"): 2113 return "DEFAULT MERGEBLOCKRATIO" 2114 2115 percent = " PERCENT" if expression.args.get("percent") else "" 2116 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2123 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2124 default = expression.args.get("default") 2125 minimum = expression.args.get("minimum") 2126 maximum = expression.args.get("maximum") 2127 if default or minimum or maximum: 2128 if default: 2129 prop = "DEFAULT" 2130 elif minimum: 2131 prop = "MINIMUM" 2132 else: 2133 prop = "MAXIMUM" 2134 return f"{prop} DATABLOCKSIZE" 2135 units = expression.args.get("units") 2136 units = f" {units}" if units else "" 2137 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2139 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2140 autotemp = expression.args.get("autotemp") 2141 always = expression.args.get("always") 2142 default = expression.args.get("default") 2143 manual = expression.args.get("manual") 2144 never = expression.args.get("never") 2145 2146 if autotemp is not None: 2147 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2148 elif always: 2149 prop = "ALWAYS" 2150 elif default: 2151 prop = "DEFAULT" 2152 elif manual: 2153 prop = "MANUAL" 2154 elif never: 2155 prop = "NEVER" 2156 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2158 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2159 no = expression.args.get("no") 2160 no = " NO" if no else "" 2161 concurrent = expression.args.get("concurrent") 2162 concurrent = " CONCURRENT" if concurrent else "" 2163 target = self.sql(expression, "target") 2164 target = f" {target}" if target else "" 2165 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2167 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2168 if isinstance(expression.this, list): 2169 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2170 if expression.this: 2171 modulus = self.sql(expression, "this") 2172 remainder = self.sql(expression, "expression") 2173 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2174 2175 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2176 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2177 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2179 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2180 this = self.sql(expression, "this") 2181 2182 for_values_or_default = expression.expression 2183 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2184 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2185 else: 2186 for_values_or_default = " DEFAULT" 2187 2188 return f"PARTITION OF {this}{for_values_or_default}"
2190 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2191 kind = expression.args.get("kind") 2192 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2193 for_or_in = expression.args.get("for_or_in") 2194 for_or_in = f" {for_or_in}" if for_or_in else "" 2195 lock_type = expression.args.get("lock_type") 2196 override = " OVERRIDE" if expression.args.get("override") else "" 2197 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2199 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2200 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2201 statistics = expression.args.get("statistics") 2202 statistics_sql = "" 2203 if statistics is not None: 2204 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2205 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2207 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2208 this = self.sql(expression, "this") 2209 this = f"HISTORY_TABLE={this}" if this else "" 2210 data_consistency: str | None = self.sql(expression, "data_consistency") 2211 data_consistency = ( 2212 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2213 ) 2214 retention_period: str | None = self.sql(expression, "retention_period") 2215 retention_period = ( 2216 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2217 ) 2218 2219 if this: 2220 on_sql = self.func("ON", this, data_consistency, retention_period) 2221 else: 2222 on_sql = "ON" if expression.args.get("on") else "OFF" 2223 2224 sql = f"SYSTEM_VERSIONING={on_sql}" 2225 2226 return f"WITH({sql})" if expression.args.get("with_") else sql
2228 def insert_sql(self, expression: exp.Insert) -> str: 2229 hint = self.sql(expression, "hint") 2230 overwrite = expression.args.get("overwrite") 2231 2232 if isinstance(expression.this, exp.Directory): 2233 this = " OVERWRITE" if overwrite else " INTO" 2234 else: 2235 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2236 2237 stored = self.sql(expression, "stored") 2238 stored = f" {stored}" if stored else "" 2239 alternative = expression.args.get("alternative") 2240 alternative = f" OR {alternative}" if alternative else "" 2241 ignore = " IGNORE" if expression.args.get("ignore") else "" 2242 is_function = expression.args.get("is_function") 2243 if is_function: 2244 this = f"{this} FUNCTION" 2245 this = f"{this} {self.sql(expression, 'this')}" 2246 2247 exists = " IF EXISTS" if expression.args.get("exists") else "" 2248 where = self.sql(expression, "where") 2249 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2250 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2251 on_conflict = self.sql(expression, "conflict") 2252 on_conflict = f" {on_conflict}" if on_conflict else "" 2253 by_name = " BY NAME" if expression.args.get("by_name") else "" 2254 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2255 returning = self.sql(expression, "returning") 2256 2257 if self.RETURNING_END: 2258 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2259 else: 2260 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2261 2262 partition_by = self.sql(expression, "partition") 2263 partition_by = f" {partition_by}" if partition_by else "" 2264 settings = self.sql(expression, "settings") 2265 settings = f" {settings}" if settings else "" 2266 2267 source = self.sql(expression, "source") 2268 source = f"TABLE {source}" if source else "" 2269 2270 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2271 return self.prepend_ctes(expression, sql)
2289 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2290 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2291 2292 constraint = self.sql(expression, "constraint") 2293 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2294 2295 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2296 if conflict_keys: 2297 conflict_keys = f"({conflict_keys})" 2298 2299 index_predicate = self.sql(expression, "index_predicate") 2300 conflict_keys = f"{conflict_keys}{index_predicate} " 2301 2302 action = self.sql(expression, "action") 2303 2304 expressions = self.expressions(expression, flat=True) 2305 if expressions: 2306 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2307 expressions = f" {set_keyword}{expressions}" 2308 2309 where = self.sql(expression, "where") 2310 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2315 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2316 fields = self.sql(expression, "fields") 2317 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2318 escaped = self.sql(expression, "escaped") 2319 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2320 items = self.sql(expression, "collection_items") 2321 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2322 keys = self.sql(expression, "map_keys") 2323 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2324 lines = self.sql(expression, "lines") 2325 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2326 null = self.sql(expression, "null") 2327 null = f" NULL DEFINED AS {null}" if null else "" 2328 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2356 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2357 table = self.table_parts(expression) 2358 only = "ONLY " if expression.args.get("only") else "" 2359 partition = self.sql(expression, "partition") 2360 partition = f" {partition}" if partition else "" 2361 version = self.sql(expression, "version") 2362 version = f" {version}" if version else "" 2363 alias = self.sql(expression, "alias") 2364 alias = f"{sep}{alias}" if alias else "" 2365 2366 sample = self.sql(expression, "sample") 2367 post_alias = "" 2368 pre_alias = "" 2369 2370 if self.dialect.ALIAS_POST_TABLESAMPLE: 2371 pre_alias = sample 2372 else: 2373 post_alias = sample 2374 2375 if self.dialect.ALIAS_POST_VERSION: 2376 pre_alias = f"{pre_alias}{version}" 2377 else: 2378 post_alias = f"{post_alias}{version}" 2379 2380 hints = self.expressions(expression, key="hints", sep=" ") 2381 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2382 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2383 joins = self.indent( 2384 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2385 ) 2386 laterals = self.expressions(expression, key="laterals", sep="") 2387 2388 file_format = self.sql(expression, "format") 2389 if file_format: 2390 pattern = self.sql(expression, "pattern") 2391 pattern = f", PATTERN => {pattern}" if pattern else "" 2392 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2393 2394 ordinality = expression.args.get("ordinality") or "" 2395 if ordinality: 2396 ordinality = f" WITH ORDINALITY{alias}" 2397 alias = "" 2398 2399 when = self.sql(expression, "when") 2400 if when: 2401 table = f"{table} {when}" 2402 2403 changes = self.sql(expression, "changes") 2404 changes = f" {changes}" if changes else "" 2405 2406 rows_from = self.expressions(expression, key="rows_from") 2407 if rows_from: 2408 table = f"ROWS FROM {self.wrap(rows_from)}" 2409 2410 indexed = expression.args.get("indexed") 2411 if indexed is not None: 2412 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2413 else: 2414 indexed = "" 2415 2416 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2418 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2419 table = self.func("TABLE", expression.this) 2420 alias = self.sql(expression, "alias") 2421 alias = f" AS {alias}" if alias else "" 2422 sample = self.sql(expression, "sample") 2423 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2424 joins = self.indent( 2425 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2426 ) 2427 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2429 def tablesample_sql( 2430 self, 2431 expression: exp.TableSample, 2432 tablesample_keyword: str | None = None, 2433 ) -> str: 2434 method = self.sql(expression, "method") 2435 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2436 numerator = self.sql(expression, "bucket_numerator") 2437 denominator = self.sql(expression, "bucket_denominator") 2438 field = self.sql(expression, "bucket_field") 2439 field = f" ON {field}" if field else "" 2440 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2441 seed = self.sql(expression, "seed") 2442 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2443 2444 size = self.sql(expression, "size") 2445 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2446 size = f"{size} ROWS" 2447 2448 percent = self.sql(expression, "percent") 2449 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2450 percent = f"{percent} PERCENT" 2451 2452 expr = f"{bucket}{percent}{size}" 2453 if self.TABLESAMPLE_REQUIRES_PARENS: 2454 expr = f"({expr})" 2455 2456 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2458 def pivot_sql(self, expression: exp.Pivot) -> str: 2459 expressions = self.expressions(expression, flat=True) 2460 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2461 2462 group = self.sql(expression, "group") 2463 2464 if expression.this: 2465 this = self.sql(expression, "this") 2466 if not expressions: 2467 sql = f"UNPIVOT {this}" 2468 else: 2469 on = f"{self.seg('ON')} {expressions}" 2470 into = self.sql(expression, "into") 2471 into = f"{self.seg('INTO')} {into}" if into else "" 2472 using = self.expressions(expression, key="using", flat=True) 2473 using = f"{self.seg('USING')} {using}" if using else "" 2474 sql = f"{direction} {this}{on}{into}{using}{group}" 2475 return self.prepend_ctes(expression, sql) 2476 2477 alias = self.sql(expression, "alias") 2478 alias = f" AS {alias}" if alias else "" 2479 2480 fields = self.expressions( 2481 expression, 2482 "fields", 2483 sep=" ", 2484 dynamic=True, 2485 new_line=True, 2486 skip_first=True, 2487 skip_last=True, 2488 ) 2489 2490 include_nulls = expression.args.get("include_nulls") 2491 if include_nulls is not None: 2492 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2493 else: 2494 nulls = "" 2495 2496 default_on_null = self.sql(expression, "default_on_null") 2497 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2498 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2499 return self.prepend_ctes(expression, sql)
2542 def update_sql(self, expression: exp.Update) -> str: 2543 hint = self.sql(expression, "hint") 2544 this = self.sql(expression, "this") 2545 join_sql, from_sql = self._update_from_joins_sql(expression) 2546 set_sql = self.expressions(expression, flat=True) 2547 where_sql = self.sql(expression, "where") 2548 returning = self.sql(expression, "returning") 2549 order = self.sql(expression, "order") 2550 limit = self.sql(expression, "limit") 2551 if self.RETURNING_END: 2552 expression_sql = f"{from_sql}{where_sql}{returning}" 2553 else: 2554 expression_sql = f"{returning}{from_sql}{where_sql}" 2555 options = self.expressions(expression, key="options") 2556 options = f" OPTION({options})" if options else "" 2557 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2558 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2560 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2561 values_as_table = values_as_table and self.VALUES_AS_TABLE 2562 2563 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2564 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2565 args = self.expressions(expression) 2566 alias = self.sql(expression, "alias") 2567 values = f"VALUES{self.seg('')}{args}" 2568 values = ( 2569 f"({values})" 2570 if self.WRAP_DERIVED_VALUES 2571 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2572 else values 2573 ) 2574 values = self.query_modifiers(expression, values) 2575 return f"{values} AS {alias}" if alias else values 2576 2577 # Converts `VALUES...` expression into a series of select unions. 2578 alias_node = expression.args.get("alias") 2579 column_names = alias_node and alias_node.columns 2580 2581 selects: list[exp.Query] = [] 2582 2583 for i, tup in enumerate(expression.expressions): 2584 row = tup.expressions 2585 2586 if i == 0 and column_names: 2587 row = [ 2588 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2589 ] 2590 2591 selects.append(exp.Select(expressions=row)) 2592 2593 if self.pretty: 2594 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2595 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2596 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2597 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2598 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2599 2600 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2601 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2602 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2607 @unsupported_args("expressions") 2608 def into_sql(self, expression: exp.Into) -> str: 2609 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2610 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2611 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2624 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2625 this = self.sql(expression, "this") 2626 2627 columns = self.expressions(expression, flat=True) 2628 2629 from_sql = self.sql(expression, "from_index") 2630 from_sql = f" FROM {from_sql}" if from_sql else "" 2631 2632 properties = expression.args.get("properties") 2633 properties_sql = ( 2634 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2635 ) 2636 2637 return f"{this}({columns}){from_sql}{properties_sql}"
2646 def group_sql(self, expression: exp.Group) -> str: 2647 group_by_all = expression.args.get("all") 2648 if group_by_all is True: 2649 modifier = " ALL" 2650 elif group_by_all is False: 2651 modifier = " DISTINCT" 2652 else: 2653 modifier = "" 2654 2655 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2656 2657 grouping_sets = self.expressions(expression, key="grouping_sets") 2658 cube = self.expressions(expression, key="cube") 2659 rollup = self.expressions(expression, key="rollup") 2660 2661 groupings = csv( 2662 self.seg(grouping_sets) if grouping_sets else "", 2663 self.seg(cube) if cube else "", 2664 self.seg(rollup) if rollup else "", 2665 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2666 sep=self.GROUPINGS_SEP, 2667 ) 2668 2669 if ( 2670 expression.expressions 2671 and groupings 2672 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2673 ): 2674 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2675 2676 return f"{group_by}{groupings}"
2682 def connect_sql(self, expression: exp.Connect) -> str: 2683 start = self.sql(expression, "start") 2684 start = self.seg(f"START WITH {start}") if start else "" 2685 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2686 connect = self.sql(expression, "connect") 2687 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2688 return start + connect
2693 def join_sql(self, expression: exp.Join) -> str: 2694 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2695 side = None 2696 else: 2697 side = expression.side 2698 2699 op_sql = " ".join( 2700 op 2701 for op in ( 2702 expression.method, 2703 "GLOBAL" if expression.args.get("global_") else None, 2704 side, 2705 expression.kind, 2706 expression.hint if self.JOIN_HINTS else None, 2707 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2708 ) 2709 if op 2710 ) 2711 match_cond = self.sql(expression, "match_condition") 2712 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2713 on_sql = self.sql(expression, "on") 2714 using = expression.args.get("using") 2715 2716 if not on_sql and using: 2717 on_sql = csv(*(self.sql(column) for column in using)) 2718 2719 this = expression.this 2720 this_sql = self.sql(this) 2721 2722 exprs = self.expressions(expression) 2723 if exprs: 2724 this_sql = f"{this_sql},{self.seg(exprs)}" 2725 2726 if on_sql: 2727 on_sql = self.indent(on_sql, skip_first=True) 2728 space = self.seg(" " * self.pad) if self.pretty else " " 2729 if using: 2730 on_sql = f"{space}USING ({on_sql})" 2731 else: 2732 on_sql = f"{space}ON {on_sql}" 2733 elif not op_sql: 2734 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2735 return f" {this_sql}" 2736 2737 return f", {this_sql}" 2738 2739 if op_sql != "STRAIGHT_JOIN": 2740 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2741 2742 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2743 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:
2750 def lateral_op(self, expression: exp.Lateral) -> str: 2751 cross_apply = expression.args.get("cross_apply") 2752 2753 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2754 if cross_apply is True: 2755 op = "INNER JOIN " 2756 elif cross_apply is False: 2757 op = "LEFT JOIN " 2758 else: 2759 op = "" 2760 2761 return f"{op}LATERAL"
2763 def lateral_sql(self, expression: exp.Lateral) -> str: 2764 this = self.sql(expression, "this") 2765 2766 if expression.args.get("view"): 2767 alias = expression.args["alias"] 2768 columns = self.expressions(alias, key="columns", flat=True) 2769 table = f" {alias.name}" if alias.name else "" 2770 columns = f" AS {columns}" if columns else "" 2771 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2772 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2773 2774 alias = self.sql(expression, "alias") 2775 alias = f" AS {alias}" if alias else "" 2776 2777 ordinality = expression.args.get("ordinality") or "" 2778 if ordinality: 2779 ordinality = f" WITH ORDINALITY{alias}" 2780 alias = "" 2781 2782 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2784 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2785 this = self.sql(expression, "this") 2786 2787 args = [ 2788 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2789 for e in (expression.args.get(k) for k in ("offset", "expression")) 2790 if e 2791 ] 2792 2793 args_sql = ", ".join(self.sql(e) for e in args) 2794 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2795 expressions = self.expressions(expression, flat=True) 2796 limit_options = self.sql(expression, "limit_options") 2797 expressions = f" BY {expressions}" if expressions else "" 2798 2799 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2801 def offset_sql(self, expression: exp.Offset) -> str: 2802 this = self.sql(expression, "this") 2803 value = expression.expression 2804 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2805 expressions = self.expressions(expression, flat=True) 2806 expressions = f" BY {expressions}" if expressions else "" 2807 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2809 def setitem_sql(self, expression: exp.SetItem) -> str: 2810 kind = self.sql(expression, "kind") 2811 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2812 kind = "" 2813 else: 2814 kind = f"{kind} " if kind else "" 2815 this = self.sql(expression, "this") 2816 expressions = self.expressions(expression) 2817 collate = self.sql(expression, "collate") 2818 collate = f" COLLATE {collate}" if collate else "" 2819 global_ = "GLOBAL " if expression.args.get("global_") else "" 2820 return f"{global_}{kind}{this}{expressions}{collate}"
2827 def queryband_sql(self, expression: exp.QueryBand) -> str: 2828 this = self.sql(expression, "this") 2829 update = " UPDATE" if expression.args.get("update") else "" 2830 scope = self.sql(expression, "scope") 2831 scope = f" FOR {scope}" if scope else "" 2832 2833 return f"QUERY_BAND = {this}{update}{scope}"
2838 def lock_sql(self, expression: exp.Lock) -> str: 2839 if not self.LOCKING_READS_SUPPORTED: 2840 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2841 return "" 2842 2843 update = expression.args["update"] 2844 key = expression.args.get("key") 2845 if update: 2846 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2847 else: 2848 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2849 expressions = self.expressions(expression, flat=True) 2850 expressions = f" OF {expressions}" if expressions else "" 2851 wait = expression.args.get("wait") 2852 2853 if wait is not None: 2854 if isinstance(wait, exp.Literal): 2855 wait = f" WAIT {self.sql(wait)}" 2856 else: 2857 wait = " NOWAIT" if wait else " SKIP LOCKED" 2858 2859 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:
2867 def escape_str( 2868 self, 2869 text: str, 2870 escape_backslash: bool = True, 2871 delimiter: str | None = None, 2872 escaped_delimiter: str | None = None, 2873 is_byte_string: bool = False, 2874 ) -> str: 2875 if is_byte_string: 2876 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2877 else: 2878 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2879 2880 if supports_escape_sequences: 2881 text = "".join( 2882 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2883 for ch in text 2884 ) 2885 2886 delimiter = delimiter or self.dialect.QUOTE_END 2887 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2888 2889 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2891 def loaddata_sql(self, expression: exp.LoadData) -> str: 2892 is_overwrite = expression.args.get("overwrite") 2893 overwrite = " OVERWRITE" if is_overwrite else "" 2894 this = self.sql(expression, "this") 2895 2896 files = expression.args.get("files") 2897 if files: 2898 files_sql = self.expressions(files, flat=True) 2899 files_sql = f"FILES{self.wrap(files_sql)}" 2900 this = f" {this}" if is_overwrite else f" INTO TABLE {this}" 2901 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2902 2903 local = " LOCAL" if expression.args.get("local") else "" 2904 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2905 this = f" INTO TABLE {this}" 2906 partition = self.sql(expression, "partition") 2907 partition = f" {partition}" if partition else "" 2908 input_format = self.sql(expression, "input_format") 2909 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2910 serde = self.sql(expression, "serde") 2911 serde = f" SERDE {serde}" if serde else "" 2912 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2926 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2927 this = self.sql(expression, "this") 2928 this = f"{this} " if this else this 2929 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2930 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
2932 def withfill_sql(self, expression: exp.WithFill) -> str: 2933 from_sql = self.sql(expression, "from_") 2934 from_sql = f" FROM {from_sql}" if from_sql else "" 2935 to_sql = self.sql(expression, "to") 2936 to_sql = f" TO {to_sql}" if to_sql else "" 2937 step_sql = self.sql(expression, "step") 2938 step_sql = f" STEP {step_sql}" if step_sql else "" 2939 interpolated_values = [ 2940 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2941 if isinstance(e, exp.Alias) 2942 else self.sql(e, "this") 2943 for e in expression.args.get("interpolate") or [] 2944 ] 2945 interpolate = ( 2946 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2947 ) 2948 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2959 def ordered_sql(self, expression: exp.Ordered) -> str: 2960 desc = expression.args.get("desc") 2961 asc = not desc 2962 2963 nulls_first = expression.args.get("nulls_first") 2964 nulls_last = not nulls_first 2965 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2966 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2967 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2968 2969 this = self.sql(expression, "this") 2970 2971 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2972 nulls_sort_change = "" 2973 if nulls_first and ( 2974 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2975 ): 2976 nulls_sort_change = " NULLS FIRST" 2977 elif ( 2978 nulls_last 2979 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2980 and not nulls_are_last 2981 ): 2982 nulls_sort_change = " NULLS LAST" 2983 2984 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2985 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2986 window = expression.find_ancestor(exp.Window, exp.Select) 2987 2988 if isinstance(window, exp.Window): 2989 window_this = window.this 2990 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 2991 window_this = window_this.this 2992 spec = window.args.get("spec") 2993 else: 2994 window_this = None 2995 spec = None 2996 2997 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 2998 # without a spec or with a ROWS spec, but not with RANGE 2999 if not ( 3000 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 3001 and (not spec or spec.text("kind").upper() == "ROWS") 3002 ): 3003 if window_this and spec: 3004 self.unsupported( 3005 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 3006 ) 3007 nulls_sort_change = "" 3008 elif self.NULL_ORDERING_SUPPORTED is False and ( 3009 (asc and nulls_sort_change == " NULLS LAST") 3010 or (desc and nulls_sort_change == " NULLS FIRST") 3011 ): 3012 # BigQuery does not allow these ordering/nulls combinations when used under 3013 # an aggregation func or under a window containing one 3014 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 3015 3016 if isinstance(ancestor, exp.Window): 3017 ancestor = ancestor.this 3018 if isinstance(ancestor, exp.AggFunc): 3019 self.unsupported( 3020 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 3021 ) 3022 nulls_sort_change = "" 3023 elif self.NULL_ORDERING_SUPPORTED is None: 3024 if expression.this.is_int: 3025 self.unsupported( 3026 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 3027 ) 3028 elif not isinstance(expression.this, exp.Rand): 3029 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 3030 this = ( 3031 f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 3032 ) 3033 nulls_sort_change = "" 3034 3035 with_fill = self.sql(expression, "with_fill") 3036 with_fill = f" {with_fill}" if with_fill else "" 3037 3038 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
3048 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 3049 partition = self.partition_by_sql(expression) 3050 order = self.sql(expression, "order") 3051 measures = self.expressions(expression, key="measures") 3052 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 3053 rows = self.sql(expression, "rows") 3054 rows = self.seg(rows) if rows else "" 3055 after = self.sql(expression, "after") 3056 after = self.seg(after) if after else "" 3057 pattern = self.sql(expression, "pattern") 3058 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 3059 definition_sqls = [ 3060 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 3061 for definition in expression.args.get("define", []) 3062 ] 3063 definitions = self.expressions(sqls=definition_sqls) 3064 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 3065 body = "".join( 3066 ( 3067 partition, 3068 order, 3069 measures, 3070 rows, 3071 after, 3072 pattern, 3073 define, 3074 ) 3075 ) 3076 alias = self.sql(expression, "alias") 3077 alias = f" {alias}" if alias else "" 3078 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3080 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3081 limit = expression.args.get("limit") 3082 3083 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3084 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3085 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3086 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3087 3088 return csv( 3089 *sqls, 3090 *[self.sql(join) for join in expression.args.get("joins") or []], 3091 self.sql(expression, "match"), 3092 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3093 self.sql(expression, "prewhere"), 3094 self.sql(expression, "where"), 3095 self.sql(expression, "connect"), 3096 self.sql(expression, "group"), 3097 self.sql(expression, "having"), 3098 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3099 self.sql(expression, "order"), 3100 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3101 *self.after_limit_modifiers(expression), 3102 self.options_modifier(expression), 3103 self.for_modifiers(expression), 3104 sep="", 3105 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.core.Expr, fetch: bool, limit: sqlglot.expressions.query.Fetch | sqlglot.expressions.query.Limit | None) -> list[str]:
3132 def select_sql(self, expression: exp.Select) -> str: 3133 into = expression.args.get("into") 3134 if not self.SUPPORTS_SELECT_INTO and into: 3135 into.pop() 3136 3137 hint = self.sql(expression, "hint") 3138 distinct = self.sql(expression, "distinct") 3139 distinct = f" {distinct}" if distinct else "" 3140 kind = self.sql(expression, "kind") 3141 3142 limit = expression.args.get("limit") 3143 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3144 top = self.limit_sql(limit, top=True) 3145 limit.pop() 3146 else: 3147 top = "" 3148 3149 expressions = self.expressions(expression) 3150 3151 if kind: 3152 if kind in self.SELECT_KINDS: 3153 kind = f" AS {kind}" 3154 else: 3155 if kind == "STRUCT": 3156 expressions = self.expressions( 3157 sqls=[ 3158 self.sql( 3159 exp.Struct( 3160 expressions=[ 3161 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3162 if isinstance(e, exp.Alias) 3163 else e 3164 for e in expression.expressions 3165 ] 3166 ) 3167 ) 3168 ] 3169 ) 3170 kind = "" 3171 3172 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3173 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3174 3175 exclude = expression.args.get("exclude") 3176 3177 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3178 exclude_sql = self.expressions(sqls=exclude, flat=True) 3179 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3180 3181 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3182 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3183 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3184 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3185 sql = self.query_modifiers( 3186 expression, 3187 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3188 self.sql(expression, "into", comment=False), 3189 self.sql(expression, "from_", comment=False), 3190 ) 3191 3192 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3193 if expression.args.get("with_"): 3194 sql = self.maybe_comment(sql, expression) 3195 expression.pop_comments() 3196 3197 sql = self.prepend_ctes(expression, sql) 3198 3199 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3200 expression.set("exclude", None) 3201 subquery = expression.subquery(copy=False) 3202 star = exp.Star(except_=exclude) 3203 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3204 3205 if not self.SUPPORTS_SELECT_INTO and into: 3206 if into.args.get("temporary"): 3207 table_kind = " TEMPORARY" 3208 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3209 table_kind = " UNLOGGED" 3210 else: 3211 table_kind = "" 3212 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3213 3214 return sql
3226 def star_sql(self, expression: exp.Star) -> str: 3227 except_ = self.expressions(expression, key="except_", flat=True) 3228 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3229 replace = self.expressions(expression, key="replace", flat=True) 3230 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3231 rename = self.expressions(expression, key="rename", flat=True) 3232 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3233 return f"*{except_}{replace}{rename}"
3249 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3250 alias = self.sql(expression, "alias") 3251 alias = f"{sep}{alias}" if alias else "" 3252 sample = self.sql(expression, "sample") 3253 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3254 alias = f"{sample}{alias}" 3255 3256 # Set to None so it's not generated again by self.query_modifiers() 3257 expression.set("sample", None) 3258 3259 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3260 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3261 return self.prepend_ctes(expression, sql)
3267 def unnest_sql(self, expression: exp.Unnest) -> str: 3268 args = self.expressions(expression, flat=True) 3269 3270 alias = expression.args.get("alias") 3271 offset = expression.args.get("offset") 3272 3273 if self.UNNEST_WITH_ORDINALITY: 3274 if alias and isinstance(offset, exp.Expr): 3275 alias.append("columns", offset) 3276 expression.set("offset", None) 3277 3278 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3279 columns = alias.columns 3280 alias = self.sql(columns[0]) if columns else "" 3281 else: 3282 alias = self.sql(alias) 3283 3284 alias = f" AS {alias}" if alias else alias 3285 if self.UNNEST_WITH_ORDINALITY: 3286 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3287 else: 3288 if isinstance(offset, exp.Expr): 3289 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3290 elif offset: 3291 suffix = f"{alias} WITH OFFSET" 3292 else: 3293 suffix = alias 3294 3295 return f"UNNEST({args}){suffix}"
3304 def window_sql(self, expression: exp.Window) -> str: 3305 this = self.sql(expression, "this") 3306 partition = self.partition_by_sql(expression) 3307 order = expression.args.get("order") 3308 order = self.order_sql(order, flat=True) if order else "" 3309 spec = self.sql(expression, "spec") 3310 alias = self.sql(expression, "alias") 3311 over = self.sql(expression, "over") or "OVER" 3312 3313 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3314 3315 first = expression.args.get("first") 3316 if first is None: 3317 first = "" 3318 else: 3319 first = "FIRST" if first else "LAST" 3320 3321 if not partition and not order and not spec and alias: 3322 return f"{this} {alias}" 3323 3324 args = self.format_args( 3325 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3326 ) 3327 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3333 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3334 kind = self.sql(expression, "kind") 3335 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3336 end = ( 3337 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3338 or "CURRENT ROW" 3339 ) 3340 3341 window_spec = f"{kind} BETWEEN {start} AND {end}" 3342 3343 exclude = self.sql(expression, "exclude") 3344 if exclude: 3345 if self.SUPPORTS_WINDOW_EXCLUDE: 3346 window_spec += f" EXCLUDE {exclude}" 3347 else: 3348 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3349 3350 return window_spec
3357 def between_sql(self, expression: exp.Between) -> str: 3358 this = self.sql(expression, "this") 3359 low = self.sql(expression, "low") 3360 high = self.sql(expression, "high") 3361 symmetric = expression.args.get("symmetric") 3362 3363 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3364 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3365 3366 flag = ( 3367 " SYMMETRIC" 3368 if symmetric 3369 else " ASYMMETRIC" 3370 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3371 else "" # silently drop ASYMMETRIC – semantics identical 3372 ) 3373 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]:
3375 def bracket_offset_expressions( 3376 self, expression: exp.Bracket, index_offset: int | None = None 3377 ) -> list[exp.Expr]: 3378 if expression.args.get("json_access"): 3379 return expression.expressions 3380 3381 return apply_index_offset( 3382 expression.this, 3383 expression.expressions, 3384 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3385 dialect=self.dialect, 3386 )
3399 def any_sql(self, expression: exp.Any) -> str: 3400 this = self.sql(expression, "this") 3401 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3402 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3403 this = self.wrap(this) 3404 return f"ANY{this}" 3405 return f"ANY {this}"
3410 def case_sql(self, expression: exp.Case) -> str: 3411 this = self.sql(expression, "this") 3412 statements = [f"CASE {this}" if this else "CASE"] 3413 3414 for e in expression.args["ifs"]: 3415 statements.append(f"WHEN {self.sql(e, 'this')}") 3416 statements.append(f"THEN {self.sql(e, 'true')}") 3417 3418 default = self.sql(expression, "default") 3419 3420 if default: 3421 statements.append(f"ELSE {default}") 3422 3423 statements.append("END") 3424 3425 if self.pretty and self.too_wide(statements): 3426 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3427 3428 return " ".join(statements)
3440 def extract_sql(self, expression: exp.Extract) -> str: 3441 import sqlglot.dialects.dialect 3442 3443 this = ( 3444 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3445 if self.NORMALIZE_EXTRACT_DATE_PARTS 3446 else expression.this 3447 ) 3448 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3449 expression_sql = self.sql(expression, "expression") 3450 3451 return f"EXTRACT({this_sql} FROM {expression_sql})"
3453 def trim_sql(self, expression: exp.Trim) -> str: 3454 trim_type = self.sql(expression, "position") 3455 3456 if trim_type == "LEADING": 3457 func_name = "LTRIM" 3458 elif trim_type == "TRAILING": 3459 func_name = "RTRIM" 3460 else: 3461 func_name = "TRIM" 3462 3463 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3465 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3466 args = expression.expressions 3467 if isinstance(expression, exp.ConcatWs): 3468 args = args[1:] # Skip the delimiter 3469 3470 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3471 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3472 3473 concat_coalesce = ( 3474 self.dialect.CONCAT_WS_COALESCE 3475 if isinstance(expression, exp.ConcatWs) 3476 else self.dialect.CONCAT_COALESCE 3477 ) 3478 3479 if not concat_coalesce and expression.args.get("coalesce"): 3480 3481 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3482 if not e.type: 3483 import sqlglot.optimizer.annotate_types 3484 3485 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3486 3487 if e.is_string or e.is_type(exp.DType.ARRAY): 3488 return e 3489 3490 return exp.func("coalesce", e, exp.Literal.string("")) 3491 3492 args = [_wrap_with_coalesce(e) for e in args] 3493 3494 return args
3496 def concat_sql(self, expression: exp.Concat) -> str: 3497 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3498 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3499 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3500 # instead of coalescing them to empty string. 3501 import sqlglot.dialects.dialect 3502 3503 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3504 3505 expressions = self.convert_concat_args(expression) 3506 3507 # Some dialects don't allow a single-argument CONCAT call 3508 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3509 return self.sql(expressions[0]) 3510 3511 return self.func("CONCAT", *expressions)
3513 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3514 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3515 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3516 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3517 all_args = expression.expressions 3518 expression.set("coalesce", True) 3519 return self.sql( 3520 exp.case() 3521 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3522 .else_(expression) 3523 ) 3524 3525 return self.func( 3526 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3527 )
3533 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3534 expressions = self.expressions(expression, flat=True) 3535 expressions = f" ({expressions})" if expressions else "" 3536 reference = self.sql(expression, "reference") 3537 reference = f" {reference}" if reference else "" 3538 delete = self.sql(expression, "delete") 3539 delete = f" ON DELETE {delete}" if delete else "" 3540 update = self.sql(expression, "update") 3541 update = f" ON UPDATE {update}" if update else "" 3542 options = self.expressions(expression, key="options", flat=True, sep=" ") 3543 options = f" {options}" if options else "" 3544 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3546 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3547 this = self.sql(expression, "this") 3548 this = f" {this}" if this else "" 3549 expressions = self.expressions(expression, flat=True) 3550 include = self.sql(expression, "include") 3551 options = self.expressions(expression, key="options", flat=True, sep=" ") 3552 options = f" {options}" if options else "" 3553 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3558 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3559 if self.MATCH_AGAINST_TABLE_PREFIX: 3560 expressions = [] 3561 for expr in expression.expressions: 3562 if isinstance(expr, exp.Table): 3563 expressions.append(f"TABLE {self.sql(expr)}") 3564 else: 3565 expressions.append(expr) 3566 else: 3567 expressions = expression.expressions 3568 3569 modifier = expression.args.get("modifier") 3570 modifier = f" {modifier}" if modifier else "" 3571 return ( 3572 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3573 )
3578 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3579 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3580 3581 if expression.args.get("escape"): 3582 path = self.escape_str(path) 3583 3584 if self.QUOTE_JSON_PATH: 3585 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3586 3587 return path
3589 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3590 if isinstance(expression, exp.JSONPathPart): 3591 transform = self.TRANSFORMS.get(expression.__class__) 3592 if not callable(transform): 3593 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3594 return "" 3595 3596 return transform(self, expression) 3597 3598 if isinstance(expression, int): 3599 return str(expression) 3600 3601 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3602 escaped = expression.replace("'", "\\'") 3603 escaped = f"\\'{expression}\\'" 3604 else: 3605 escaped = expression.replace('"', '\\"') 3606 escaped = f'"{escaped}"' 3607 3608 return escaped
3613 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3614 # Output the Teradata column FORMAT override. 3615 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3616 this = self.sql(expression, "this") 3617 fmt = self.sql(expression, "format") 3618 return f"{this} (FORMAT {fmt})"
3646 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3647 null_handling = expression.args.get("null_handling") 3648 null_handling = f" {null_handling}" if null_handling else "" 3649 return_type = self.sql(expression, "return_type") 3650 return_type = f" RETURNING {return_type}" if return_type else "" 3651 strict = " STRICT" if expression.args.get("strict") else "" 3652 return self.func( 3653 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3654 )
3656 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3657 this = self.sql(expression, "this") 3658 order = self.sql(expression, "order") 3659 null_handling = expression.args.get("null_handling") 3660 null_handling = f" {null_handling}" if null_handling else "" 3661 return_type = self.sql(expression, "return_type") 3662 return_type = f" RETURNING {return_type}" if return_type else "" 3663 strict = " STRICT" if expression.args.get("strict") else "" 3664 return self.func( 3665 "JSON_ARRAYAGG", 3666 this, 3667 suffix=f"{order}{null_handling}{return_type}{strict})", 3668 )
3670 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3671 path = self.sql(expression, "path") 3672 path = f" PATH {path}" if path else "" 3673 nested_schema = self.sql(expression, "nested_schema") 3674 3675 if nested_schema: 3676 return f"NESTED{path} {nested_schema}" 3677 3678 this = self.sql(expression, "this") 3679 kind = self.sql(expression, "kind") 3680 kind = f" {kind}" if kind else "" 3681 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3682 3683 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3684 return f"{this}{kind}{format_json}{path}{ordinality}"
3689 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3690 this = self.sql(expression, "this") 3691 path = self.sql(expression, "path") 3692 path = f", {path}" if path else "" 3693 error_handling = expression.args.get("error_handling") 3694 error_handling = f" {error_handling}" if error_handling else "" 3695 empty_handling = expression.args.get("empty_handling") 3696 empty_handling = f" {empty_handling}" if empty_handling else "" 3697 schema = self.sql(expression, "schema") 3698 return self.func( 3699 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3700 )
3702 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3703 this = self.sql(expression, "this") 3704 kind = self.sql(expression, "kind") 3705 path = self.sql(expression, "path") 3706 path = f" {path}" if path else "" 3707 as_json = " AS JSON" if expression.args.get("as_json") else "" 3708 return f"{this} {kind}{path}{as_json}"
3710 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3711 this = self.sql(expression, "this") 3712 path = self.sql(expression, "path") 3713 path = f", {path}" if path else "" 3714 expressions = self.expressions(expression) 3715 with_ = ( 3716 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3717 if expressions 3718 else "" 3719 ) 3720 return f"OPENJSON({this}{path}){with_}"
3722 def in_sql(self, expression: exp.In) -> str: 3723 query = expression.args.get("query") 3724 unnest = expression.args.get("unnest") 3725 field = expression.args.get("field") 3726 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3727 3728 if query: 3729 in_sql = self.sql(query) 3730 elif unnest: 3731 in_sql = self.in_unnest_op(unnest) 3732 elif field: 3733 in_sql = self.sql(field) 3734 else: 3735 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3736 3737 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3742 def interval_sql(self, expression: exp.Interval) -> str: 3743 unit_expression = expression.args.get("unit") 3744 unit = self.sql(unit_expression) if unit_expression else "" 3745 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3746 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3747 unit = f" {unit}" if unit else "" 3748 3749 if self.SINGLE_STRING_INTERVAL: 3750 this = expression.this.name if expression.this else "" 3751 if this: 3752 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3753 return f"INTERVAL '{this}'{unit}" 3754 return f"INTERVAL '{this}{unit}'" 3755 return f"INTERVAL{unit}" 3756 3757 this = self.sql(expression, "this") 3758 if this: 3759 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3760 this = f" {this}" if unwrapped else f" ({this})" 3761 3762 return f"INTERVAL{this}{unit}"
3767 def reference_sql(self, expression: exp.Reference) -> str: 3768 this = self.sql(expression, "this") 3769 expressions = self.expressions(expression, flat=True) 3770 expressions = f"({expressions})" if expressions else "" 3771 options = self.expressions(expression, key="options", flat=True, sep=" ") 3772 options = f" {options}" if options else "" 3773 return f"REFERENCES {this}{expressions}{options}"
3775 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3776 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3777 parent = expression.parent 3778 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3779 3780 return self.func( 3781 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3782 )
3802 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3803 alias = expression.args["alias"] 3804 3805 parent = expression.parent 3806 pivot = parent and parent.parent 3807 3808 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3809 identifier_alias = isinstance(alias, exp.Identifier) 3810 literal_alias = isinstance(alias, exp.Literal) 3811 3812 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3813 alias.replace(exp.Literal.string(alias.output_name)) 3814 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3815 alias.replace(exp.to_identifier(alias.output_name)) 3816 3817 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:
3849 def connector_sql( 3850 self, 3851 expression: exp.Connector, 3852 op: str, 3853 stack: list[str | exp.Expr] | None = None, 3854 ) -> str: 3855 if stack is not None: 3856 if expression.expressions: 3857 stack.append(self.expressions(expression, sep=f" {op} ")) 3858 else: 3859 stack.append(expression.right) 3860 if expression.comments and self.comments: 3861 for comment in expression.comments: 3862 if comment: 3863 op += f" /*{self.sanitize_comment(comment)}*/" 3864 stack.extend((op, expression.left)) 3865 return op 3866 3867 stack = [expression] 3868 sqls: list[str] = [] 3869 ops = set() 3870 3871 while stack: 3872 node = stack.pop() 3873 if isinstance(node, exp.Connector): 3874 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3875 else: 3876 sql = self.sql(node) 3877 if sqls and sqls[-1] in ops: 3878 sqls[-1] += f" {sql}" 3879 else: 3880 sqls.append(sql) 3881 3882 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3883 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
3903 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 3904 format_sql = self.sql(expression, "format") 3905 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3906 to_sql = self.sql(expression, "to") 3907 to_sql = f" {to_sql}" if to_sql else "" 3908 action = self.sql(expression, "action") 3909 action = f" {action}" if action else "" 3910 default = self.sql(expression, "default") 3911 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3912 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3930 def comment_sql(self, expression: exp.Comment) -> str: 3931 this = self.sql(expression, "this") 3932 kind = expression.args["kind"] 3933 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3934 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3935 expression_sql = self.sql(expression, "expression") 3936 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3938 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3939 this = self.sql(expression, "this") 3940 delete = " DELETE" if expression.args.get("delete") else "" 3941 recompress = self.sql(expression, "recompress") 3942 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3943 to_disk = self.sql(expression, "to_disk") 3944 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3945 to_volume = self.sql(expression, "to_volume") 3946 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3947 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3949 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3950 where = self.sql(expression, "where") 3951 group = self.sql(expression, "group") 3952 aggregates = self.expressions(expression, key="aggregates") 3953 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3954 3955 if not (where or group or aggregates) and len(expression.expressions) == 1: 3956 return f"TTL {self.expressions(expression, flat=True)}" 3957 3958 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3977 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3978 this = self.sql(expression, "this") 3979 3980 dtype = self.sql(expression, "dtype") 3981 if dtype: 3982 collate = self.sql(expression, "collate") 3983 collate = f" COLLATE {collate}" if collate else "" 3984 using = self.sql(expression, "using") 3985 using = f" USING {using}" if using else "" 3986 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3987 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3988 3989 default = self.sql(expression, "default") 3990 if default: 3991 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3992 3993 comment = self.sql(expression, "comment") 3994 if comment: 3995 return f"ALTER COLUMN {this} COMMENT {comment}" 3996 3997 visible = expression.args.get("visible") 3998 if visible: 3999 return f"ALTER COLUMN {this} SET {visible}" 4000 4001 allow_null = expression.args.get("allow_null") 4002 drop = expression.args.get("drop") 4003 4004 if not drop and not allow_null: 4005 self.unsupported("Unsupported ALTER COLUMN syntax") 4006 4007 if allow_null is not None: 4008 keyword = "DROP" if drop else "SET" 4009 return f"ALTER COLUMN {this} {keyword} NOT NULL" 4010 4011 return f"ALTER COLUMN {this} DROP DEFAULT"
4013 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 4014 this = self.sql(expression, "this") 4015 rename_from = self.sql(expression, "rename_from") 4016 if rename_from: 4017 if not self.SUPPORTS_CHANGE_COLUMN: 4018 self.unsupported("CHANGE COLUMN is not supported in this dialect") 4019 return f"CHANGE COLUMN {rename_from} {this}" 4020 if not self.SUPPORTS_MODIFY_COLUMN: 4021 self.unsupported("MODIFY COLUMN is not supported in this dialect") 4022 return f"MODIFY COLUMN {this}"
4038 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 4039 compound = " COMPOUND" if expression.args.get("compound") else "" 4040 this = self.sql(expression, "this") 4041 expressions = self.expressions(expression, flat=True) 4042 expressions = f"({expressions})" if expressions else "" 4043 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
4045 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 4046 if not self.RENAME_TABLE_WITH_DB: 4047 # Remove db from tables 4048 expression = expression.transform( 4049 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 4050 ).assert_is(exp.AlterRename) 4051 this = self.sql(expression, "this") 4052 to_kw = " TO" if include_to else "" 4053 return f"RENAME{to_kw} {this}"
4068 def alter_sql(self, expression: exp.Alter) -> str: 4069 actions = expression.args["actions"] 4070 4071 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4072 actions[0], exp.ColumnDef 4073 ): 4074 actions_sql = self.expressions(expression, key="actions", flat=True) 4075 actions_sql = f"ADD {actions_sql}" 4076 else: 4077 actions_list = [] 4078 for action in actions: 4079 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4080 action_sql = self.add_column_sql(action) 4081 else: 4082 action_sql = self.sql(action) 4083 if isinstance(action, exp.Query): 4084 action_sql = f"AS {action_sql}" 4085 4086 actions_list.append(action_sql) 4087 4088 actions_sql = self.format_args(*actions_list).lstrip("\n") 4089 4090 iceberg = ( 4091 "ICEBERG " 4092 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4093 else "" 4094 ) 4095 exists = " IF EXISTS" if expression.args.get("exists") else "" 4096 on_cluster = self.sql(expression, "cluster") 4097 on_cluster = f" {on_cluster}" if on_cluster else "" 4098 only = " ONLY" if expression.args.get("only") else "" 4099 options = self.expressions(expression, key="options") 4100 options = f", {options}" if options else "" 4101 kind = self.sql(expression, "kind") 4102 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4103 check = " WITH CHECK" if expression.args.get("check") else "" 4104 cascade = ( 4105 " CASCADE" 4106 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4107 else "" 4108 ) 4109 this = self.sql(expression, "this") 4110 this = f" {this}" if this else "" 4111 4112 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4119 def add_column_sql(self, expression: exp.Expr) -> str: 4120 sql = self.sql(expression) 4121 if isinstance(expression, exp.Schema): 4122 column_text = " COLUMNS" 4123 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4124 column_text = " COLUMN" 4125 else: 4126 column_text = "" 4127 4128 return f"ADD{column_text} {sql}"
4141 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4142 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4143 location = self.sql(expression, "location") 4144 location = f" {location}" if location else "" 4145 return f"ADD {exists}{self.sql(expression.this)}{location}"
4147 def distinct_sql(self, expression: exp.Distinct) -> str: 4148 this = self.expressions(expression, flat=True) 4149 4150 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4151 case = exp.case() 4152 for arg in expression.expressions: 4153 case = case.when(arg.is_(exp.null()), exp.null()) 4154 this = self.sql(case.else_(f"({this})")) 4155 4156 this = f" {this}" if this else "" 4157 4158 on = self.sql(expression, "on") 4159 on = f" ON {on}" if on else "" 4160 return f"DISTINCT{this}{on}"
4187 def div_sql(self, expression: exp.Div) -> str: 4188 l, r = expression.left, expression.right 4189 4190 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4191 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4192 4193 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4194 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4195 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4196 4197 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4198 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4199 return self.sql( 4200 exp.cast( 4201 l / r, 4202 to=exp.DType.BIGINT, 4203 ) 4204 ) 4205 4206 return self.binary(expression, "/")
4228 def escape_sql(self, expression: exp.Escape) -> str: 4229 this = expression.this 4230 if ( 4231 isinstance(this, (exp.Like, exp.ILike)) 4232 and isinstance(this.expression, (exp.All, exp.Any)) 4233 and not self.SUPPORTS_LIKE_QUANTIFIERS 4234 ): 4235 return self._like_sql(this, escape=expression) 4236 return self.binary(expression, "ESCAPE")
4353 def log_sql(self, expression: exp.Log) -> str: 4354 this = expression.this 4355 expr = expression.expression 4356 4357 if self.dialect.LOG_BASE_FIRST is False: 4358 this, expr = expr, this 4359 elif self.dialect.LOG_BASE_FIRST is None and expr: 4360 if this.name in ("2", "10"): 4361 return self.func(f"LOG{this.name}", expr) 4362 4363 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4364 4365 return self.func("LOG", this, expr)
4374 def binary(self, expression: exp.Binary, op: str) -> str: 4375 sqls: list[str] = [] 4376 stack: list[None | str | exp.Expr] = [expression] 4377 binary_type = type(expression) 4378 4379 while stack: 4380 node = stack.pop() 4381 4382 if type(node) is binary_type: 4383 op_func = node.args.get("operator") 4384 if op_func: 4385 op = f"OPERATOR({self.sql(op_func)})" 4386 4387 stack.append(node.args.get("expression")) 4388 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4389 stack.append(node.args.get("this")) 4390 else: 4391 sqls.append(self.sql(node)) 4392 4393 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4402 def function_fallback_sql(self, expression: exp.Func) -> str: 4403 args = [] 4404 4405 for key in expression.arg_types: 4406 arg_value = expression.args.get(key) 4407 4408 if isinstance(arg_value, list): 4409 for value in arg_value: 4410 args.append(value) 4411 elif arg_value is not None: 4412 args.append(arg_value) 4413 4414 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4415 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4416 else: 4417 name = expression.sql_name() 4418 4419 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:
4432 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4433 arg_sqls = tuple( 4434 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4435 ) 4436 if self.pretty and self.too_wide(arg_sqls): 4437 return self.indent( 4438 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4439 ) 4440 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:
4445 def format_time( 4446 self, 4447 expression: exp.Expr, 4448 inverse_time_mapping: dict[str, str] | None = None, 4449 inverse_time_trie: dict | None = None, 4450 ) -> str | None: 4451 return format_time( 4452 self.sql(expression, "format"), 4453 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4454 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4455 )
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:
4457 def expressions( 4458 self, 4459 expression: exp.Expr | None = None, 4460 key: str | None = None, 4461 sqls: t.Collection[str | exp.Expr] | None = None, 4462 flat: bool = False, 4463 indent: bool = True, 4464 skip_first: bool = False, 4465 skip_last: bool = False, 4466 sep: str = ", ", 4467 prefix: str = "", 4468 dynamic: bool = False, 4469 new_line: bool = False, 4470 ) -> str: 4471 expressions = expression.args.get(key or "expressions") if expression else sqls 4472 4473 if not expressions: 4474 return "" 4475 4476 if flat: 4477 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4478 4479 num_sqls = len(expressions) 4480 result_sqls = [] 4481 4482 for i, e in enumerate(expressions): 4483 sql = self.sql(e, comment=False) 4484 if not sql: 4485 continue 4486 4487 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4488 4489 if self.pretty: 4490 if self.leading_comma: 4491 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4492 else: 4493 result_sqls.append( 4494 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4495 ) 4496 else: 4497 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4498 4499 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4500 if new_line: 4501 result_sqls.insert(0, "") 4502 result_sqls.append("") 4503 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4504 else: 4505 result_sql = "".join(result_sqls) 4506 4507 return ( 4508 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4509 if indent 4510 else result_sql 4511 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4513 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4514 flat = flat or isinstance(expression.parent, exp.Properties) 4515 expressions_sql = self.expressions(expression, flat=flat) 4516 if flat: 4517 return f"{op} {expressions_sql}" 4518 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4520 def naked_property(self, expression: exp.Property) -> str: 4521 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4522 if not property_name: 4523 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4524 return f"{property_name} {self.sql(expression, 'this')}"
4532 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4533 this = self.sql(expression, "this") 4534 expressions = self.no_identify(self.expressions, expression) 4535 expressions = ( 4536 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4537 ) 4538 return f"{this}{expressions}" if expressions.strip() != "" else this
4548 def when_sql(self, expression: exp.When) -> str: 4549 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4550 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4551 condition = self.sql(expression, "condition") 4552 condition = f" AND {condition}" if condition else "" 4553 4554 then_expression = expression.args.get("then") 4555 if isinstance(then_expression, exp.Insert): 4556 this = self.sql(then_expression, "this") 4557 this = f"INSERT {this}" if this else "INSERT" 4558 then = self.sql(then_expression, "expression") 4559 then = f"{this} VALUES {then}" if then else this 4560 elif isinstance(then_expression, exp.Update): 4561 if isinstance(then_expression.args.get("expressions"), exp.Star): 4562 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4563 else: 4564 expressions_sql = self.expressions(then_expression) 4565 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4566 else: 4567 then = self.sql(then_expression) 4568 4569 if isinstance(then_expression, (exp.Insert, exp.Update)): 4570 where = self.sql(then_expression, "where") 4571 if where and not self.SUPPORTS_MERGE_WHERE: 4572 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4573 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4574 where = "" 4575 then = f"{then}{where}" 4576 return f"WHEN {matched}{source}{condition} THEN {then}"
4581 def merge_sql(self, expression: exp.Merge) -> str: 4582 table = expression.this 4583 table_alias = "" 4584 4585 hints = table.args.get("hints") 4586 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4587 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4588 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4589 4590 this = self.sql(table) 4591 using = f"USING {self.sql(expression, 'using')}" 4592 whens = self.sql(expression, "whens") 4593 4594 on = self.sql(expression, "on") 4595 on = f"ON {on}" if on else "" 4596 4597 if not on: 4598 on = self.expressions(expression, key="using_cond") 4599 on = f"USING ({on})" if on else "" 4600 4601 returning = self.sql(expression, "returning") 4602 if returning: 4603 whens = f"{whens}{returning}" 4604 4605 sep = self.sep() 4606 4607 return self.prepend_ctes( 4608 expression, 4609 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4610 )
@unsupported_args('format')
def
tochar_sql(self, expression: sqlglot.expressions.string.ToChar) -> str:
4616 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4617 if not self.SUPPORTS_TO_NUMBER: 4618 self.unsupported("Unsupported TO_NUMBER function") 4619 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4620 4621 fmt = expression.args.get("format") 4622 if not fmt: 4623 self.unsupported("Conversion format is required for TO_NUMBER") 4624 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4625 4626 return self.func("TO_NUMBER", expression.this, fmt)
4628 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4629 this = self.sql(expression, "this") 4630 kind = self.sql(expression, "kind") 4631 settings_sql = self.expressions(expression, key="settings", sep=" ") 4632 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4633 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:
4654 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4655 expressions = self.expressions(expression, flat=True) 4656 expressions = f" {self.wrap(expressions)}" if expressions else "" 4657 buckets = self.sql(expression, "buckets") 4658 kind = self.sql(expression, "kind") 4659 buckets = f" BUCKETS {buckets}" if buckets else "" 4660 order = self.sql(expression, "order") 4661 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
4666 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4667 expressions = self.expressions(expression, key="expressions", flat=True) 4668 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4669 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4670 buckets = self.sql(expression, "buckets") 4671 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4673 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4674 this = self.sql(expression, "this") 4675 having = self.sql(expression, "having") 4676 4677 if having: 4678 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4679 4680 return self.func("ANY_VALUE", this)
4682 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4683 transform = self.func("TRANSFORM", *expression.expressions) 4684 row_format_before = self.sql(expression, "row_format_before") 4685 row_format_before = f" {row_format_before}" if row_format_before else "" 4686 record_writer = self.sql(expression, "record_writer") 4687 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4688 using = f" USING {self.sql(expression, 'command_script')}" 4689 schema = self.sql(expression, "schema") 4690 schema = f" AS {schema}" if schema else "" 4691 row_format_after = self.sql(expression, "row_format_after") 4692 row_format_after = f" {row_format_after}" if row_format_after else "" 4693 record_reader = self.sql(expression, "record_reader") 4694 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4695 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:
4697 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4698 key_block_size = self.sql(expression, "key_block_size") 4699 if key_block_size: 4700 return f"KEY_BLOCK_SIZE = {key_block_size}" 4701 4702 using = self.sql(expression, "using") 4703 if using: 4704 return f"USING {using}" 4705 4706 parser = self.sql(expression, "parser") 4707 if parser: 4708 return f"WITH PARSER {parser}" 4709 4710 comment = self.sql(expression, "comment") 4711 if comment: 4712 return f"COMMENT {comment}" 4713 4714 visible = expression.args.get("visible") 4715 if visible is not None: 4716 return "VISIBLE" if visible else "INVISIBLE" 4717 4718 engine_attr = self.sql(expression, "engine_attr") 4719 if engine_attr: 4720 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4721 4722 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4723 if secondary_engine_attr: 4724 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4725 4726 self.unsupported("Unsupported index constraint option.") 4727 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
4733 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4734 kind = self.sql(expression, "kind") 4735 kind = f"{kind} INDEX" if kind else "INDEX" 4736 this = self.sql(expression, "this") 4737 this = f" {this}" if this else "" 4738 index_type = self.sql(expression, "index_type") 4739 index_type = f" USING {index_type}" if index_type else "" 4740 expressions = self.expressions(expression, flat=True) 4741 expressions = f" ({expressions})" if expressions else "" 4742 options = self.expressions(expression, key="options", sep=" ") 4743 options = f" {options}" if options else "" 4744 return f"{kind}{this}{index_type}{expressions}{options}"
4746 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4747 if self.NVL2_SUPPORTED: 4748 return self.function_fallback_sql(expression) 4749 4750 case = exp.Case().when( 4751 expression.this.is_(exp.null()).not_(copy=False), 4752 expression.args["true"], 4753 copy=False, 4754 ) 4755 else_cond = expression.args.get("false") 4756 if else_cond: 4757 case.else_(else_cond, copy=False) 4758 4759 return self.sql(case)
4761 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4762 this = self.sql(expression, "this") 4763 expr = self.sql(expression, "expression") 4764 position = self.sql(expression, "position") 4765 position = f", {position}" if position else "" 4766 iterator = self.sql(expression, "iterator") 4767 condition = self.sql(expression, "condition") 4768 condition = f" IF {condition}" if condition else "" 4769 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
4819 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4820 this_sql = self.sql(expression, "this") 4821 if isinstance(expression.this, exp.Table): 4822 this_sql = f"TABLE {this_sql}" 4823 4824 return self.func( 4825 "FORECAST", 4826 this_sql, 4827 expression.args.get("data_col"), 4828 expression.args.get("timestamp_col"), 4829 expression.args.get("model"), 4830 expression.args.get("id_cols"), 4831 expression.args.get("horizon"), 4832 expression.args.get("forecast_end_timestamp"), 4833 expression.args.get("confidence_level"), 4834 expression.args.get("output_historical_time_series"), 4835 expression.args.get("context_window"), 4836 )
4838 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4839 this_sql = self.sql(expression, "this") 4840 if isinstance(expression.this, exp.Table): 4841 this_sql = f"TABLE {this_sql}" 4842 4843 return self.func( 4844 "FEATURES_AT_TIME", 4845 this_sql, 4846 expression.args.get("time"), 4847 expression.args.get("num_rows"), 4848 expression.args.get("ignore_feature_nulls"), 4849 )
4851 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4852 this_sql = self.sql(expression, "this") 4853 if isinstance(expression.this, exp.Table): 4854 this_sql = f"TABLE {this_sql}" 4855 4856 query_table = self.sql(expression, "query_table") 4857 if isinstance(expression.args["query_table"], exp.Table): 4858 query_table = f"TABLE {query_table}" 4859 4860 return self.func( 4861 "VECTOR_SEARCH", 4862 this_sql, 4863 expression.args.get("column_to_search"), 4864 query_table, 4865 expression.args.get("query_column_to_search"), 4866 expression.args.get("top_k"), 4867 expression.args.get("distance_type"), 4868 expression.args.get("options"), 4869 )
4881 def toarray_sql(self, expression: exp.ToArray) -> str: 4882 arg = expression.this 4883 if not arg.type: 4884 import sqlglot.optimizer.annotate_types 4885 4886 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 4887 4888 if arg.is_type(exp.DType.ARRAY): 4889 return self.sql(arg) 4890 4891 cond_for_null = arg.is_(exp.null()) 4892 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4894 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4895 this = expression.this 4896 time_format = self.format_time(expression) 4897 4898 if time_format: 4899 return self.sql( 4900 exp.cast( 4901 exp.StrToTime(this=this, format=expression.args["format"]), 4902 exp.DType.TIME, 4903 ) 4904 ) 4905 4906 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 4907 return self.sql(this) 4908 4909 return self.sql(exp.cast(this, exp.DType.TIME))
4911 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4912 this = expression.this 4913 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 4914 return self.sql(this) 4915 4916 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
4918 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4919 this = expression.this 4920 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 4921 return self.sql(this) 4922 4923 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
4925 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4926 this = expression.this 4927 time_format = self.format_time(expression) 4928 safe = expression.args.get("safe") 4929 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4930 return self.sql( 4931 exp.cast( 4932 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4933 exp.DType.DATE, 4934 ) 4935 ) 4936 4937 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 4938 return self.sql(this) 4939 4940 if safe: 4941 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 4942 4943 return self.sql(exp.cast(this, exp.DType.DATE))
4955 def lastday_sql(self, expression: exp.LastDay) -> str: 4956 if self.LAST_DAY_SUPPORTS_DATE_PART: 4957 return self.function_fallback_sql(expression) 4958 4959 unit = expression.text("unit") 4960 if unit and unit != "MONTH": 4961 self.unsupported("Date parts are not supported in LAST_DAY.") 4962 4963 return self.func("LAST_DAY", expression.this)
4975 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4976 if self.CAN_IMPLEMENT_ARRAY_ANY: 4977 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4978 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4979 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4980 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4981 4982 import sqlglot.dialects.dialect 4983 4984 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4985 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 4986 self.unsupported("ARRAY_ANY is unsupported") 4987 4988 return self.function_fallback_sql(expression)
4990 def struct_sql(self, expression: exp.Struct) -> str: 4991 expression.set( 4992 "expressions", 4993 [ 4994 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4995 if isinstance(e, exp.PropertyEQ) 4996 else e 4997 for e in expression.expressions 4998 ], 4999 ) 5000 5001 return self.function_fallback_sql(expression)
5009 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 5010 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 5011 tables = f" {self.expressions(expression)}" 5012 5013 exists = " IF EXISTS" if expression.args.get("exists") else "" 5014 5015 on_cluster = self.sql(expression, "cluster") 5016 on_cluster = f" {on_cluster}" if on_cluster else "" 5017 5018 identity = self.sql(expression, "identity") 5019 identity = f" {identity} IDENTITY" if identity else "" 5020 5021 option = self.sql(expression, "option") 5022 option = f" {option}" if option else "" 5023 5024 partition = self.sql(expression, "partition") 5025 partition = f" {partition}" if partition else "" 5026 5027 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
5031 def convert_sql(self, expression: exp.Convert) -> str: 5032 to = expression.this 5033 value = expression.expression 5034 style = expression.args.get("style") 5035 safe = expression.args.get("safe") 5036 strict = expression.args.get("strict") 5037 5038 if not to or not value: 5039 return "" 5040 5041 # Retrieve length of datatype and override to default if not specified 5042 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5043 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 5044 5045 transformed: exp.Expr | None = None 5046 cast = exp.Cast if strict else exp.TryCast 5047 5048 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 5049 if isinstance(style, exp.Literal) and style.is_int: 5050 import sqlglot.dialects.tsql 5051 5052 style_value = style.name 5053 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 5054 if not converted_style: 5055 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 5056 5057 fmt = exp.Literal.string(converted_style) 5058 5059 if to.this == exp.DType.DATE: 5060 transformed = exp.StrToDate(this=value, format=fmt) 5061 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 5062 transformed = exp.StrToTime(this=value, format=fmt) 5063 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 5064 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 5065 elif to.this == exp.DType.TEXT: 5066 transformed = exp.TimeToStr(this=value, format=fmt) 5067 5068 if not transformed: 5069 transformed = cast(this=value, to=to, safe=safe) 5070 5071 return self.sql(transformed)
5142 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5143 option = self.sql(expression, "this") 5144 5145 if expression.expressions: 5146 upper = option.upper() 5147 5148 # Snowflake FILE_FORMAT options are separated by whitespace 5149 sep = " " if upper == "FILE_FORMAT" else ", " 5150 5151 # Databricks copy/format options do not set their list of values with EQ 5152 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5153 values = self.expressions(expression, flat=True, sep=sep) 5154 return f"{option}{op}({values})" 5155 5156 value = self.sql(expression, "expression") 5157 5158 if not value: 5159 return option 5160 5161 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5162 5163 return f"{option}{op}{value}"
5165 def credentials_sql(self, expression: exp.Credentials) -> str: 5166 cred_expr = expression.args.get("credentials") 5167 if isinstance(cred_expr, exp.Literal): 5168 # Redshift case: CREDENTIALS <string> 5169 credentials = self.sql(expression, "credentials") 5170 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5171 else: 5172 # Snowflake case: CREDENTIALS = (...) 5173 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5174 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5175 5176 storage = self.sql(expression, "storage") 5177 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5178 5179 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5180 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5181 5182 iam_role = self.sql(expression, "iam_role") 5183 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5184 5185 region = self.sql(expression, "region") 5186 region = f" REGION {region}" if region else "" 5187 5188 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5190 def copy_sql(self, expression: exp.Copy) -> str: 5191 this = self.sql(expression, "this") 5192 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5193 5194 credentials = self.sql(expression, "credentials") 5195 credentials = self.seg(credentials) if credentials else "" 5196 files = self.expressions(expression, key="files", flat=True) 5197 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5198 5199 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5200 params = self.expressions( 5201 expression, 5202 key="params", 5203 sep=sep, 5204 new_line=True, 5205 skip_last=True, 5206 skip_first=True, 5207 indent=self.COPY_PARAMS_ARE_WRAPPED, 5208 ) 5209 5210 if params: 5211 if self.COPY_PARAMS_ARE_WRAPPED: 5212 params = f" WITH ({params})" 5213 elif not self.pretty and (files or credentials): 5214 params = f" {params}" 5215 5216 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5221 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5222 on_sql = "ON" if expression.args.get("on") else "OFF" 5223 filter_col: str | None = self.sql(expression, "filter_column") 5224 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5225 retention_period: str | None = self.sql(expression, "retention_period") 5226 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5227 5228 if filter_col or retention_period: 5229 on_sql = self.func("ON", filter_col, retention_period) 5230 5231 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5233 def maskingpolicycolumnconstraint_sql( 5234 self, expression: exp.MaskingPolicyColumnConstraint 5235 ) -> str: 5236 this = self.sql(expression, "this") 5237 expressions = self.expressions(expression, flat=True) 5238 expressions = f" USING ({expressions})" if expressions else "" 5239 return f"MASKING POLICY {this}{expressions}"
5249 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5250 this = self.sql(expression, "this") 5251 expr = expression.expression 5252 5253 if isinstance(expr, exp.Func): 5254 # T-SQL's CLR functions are case sensitive 5255 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5256 else: 5257 expr = self.sql(expression, "expression") 5258 5259 return self.scope_resolution(expr, this)
5267 def rand_sql(self, expression: exp.Rand) -> str: 5268 lower = self.sql(expression, "lower") 5269 upper = self.sql(expression, "upper") 5270 5271 if lower and upper: 5272 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5273 return self.func("RAND", expression.this)
5275 def changes_sql(self, expression: exp.Changes) -> str: 5276 information = self.sql(expression, "information") 5277 information = f"INFORMATION => {information}" 5278 at_before = self.sql(expression, "at_before") 5279 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5280 end = self.sql(expression, "end") 5281 end = f"{self.seg('')}{end}" if end else "" 5282 5283 return f"CHANGES ({information}){at_before}{end}"
5285 def pad_sql(self, expression: exp.Pad) -> str: 5286 prefix = "L" if expression.args.get("is_left") else "R" 5287 5288 fill_pattern = self.sql(expression, "fill_pattern") or None 5289 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5290 fill_pattern = "' '" 5291 5292 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5298 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5299 generate_series = exp.GenerateSeries(**expression.args) 5300 5301 parent = expression.parent 5302 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5303 parent = parent.parent 5304 5305 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5306 return self.sql(exp.Unnest(expressions=[generate_series])) 5307 5308 if isinstance(parent, exp.Select): 5309 self.unsupported("GenerateSeries projection unnesting is not supported.") 5310 5311 return self.sql(generate_series)
5313 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5314 if self.SUPPORTS_CONVERT_TIMEZONE: 5315 return self.function_fallback_sql(expression) 5316 5317 source_tz = expression.args.get("source_tz") 5318 target_tz = expression.args.get("target_tz") 5319 timestamp = expression.args.get("timestamp") 5320 5321 if source_tz and timestamp: 5322 timestamp = exp.AtTimeZone( 5323 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5324 ) 5325 5326 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5327 5328 return self.sql(expr)
5330 def json_sql(self, expression: exp.JSON) -> str: 5331 this = self.sql(expression, "this") 5332 this = f" {this}" if this else "" 5333 5334 _with = expression.args.get("with_") 5335 5336 if _with is None: 5337 with_sql = "" 5338 elif not _with: 5339 with_sql = " WITHOUT" 5340 else: 5341 with_sql = " WITH" 5342 5343 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5344 5345 return f"JSON{this}{with_sql}{unique_sql}"
5347 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5348 path = self.sql(expression, "path") 5349 returning = self.sql(expression, "returning") 5350 returning = f" RETURNING {returning}" if returning else "" 5351 5352 on_condition = self.sql(expression, "on_condition") 5353 on_condition = f" {on_condition}" if on_condition else "" 5354 5355 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5361 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5362 else_ = "ELSE " if expression.args.get("else_") else "" 5363 condition = self.sql(expression, "expression") 5364 condition = f"WHEN {condition} THEN " if condition else else_ 5365 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5366 return f"{condition}{insert}"
5374 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5375 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5376 empty = expression.args.get("empty") 5377 empty = ( 5378 f"DEFAULT {empty} ON EMPTY" 5379 if isinstance(empty, exp.Expr) 5380 else self.sql(expression, "empty") 5381 ) 5382 5383 error = expression.args.get("error") 5384 error = ( 5385 f"DEFAULT {error} ON ERROR" 5386 if isinstance(error, exp.Expr) 5387 else self.sql(expression, "error") 5388 ) 5389 5390 if error and empty: 5391 error = ( 5392 f"{empty} {error}" 5393 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5394 else f"{error} {empty}" 5395 ) 5396 empty = "" 5397 5398 null = self.sql(expression, "null") 5399 5400 return f"{empty}{error}{null}"
5406 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5407 this = self.sql(expression, "this") 5408 path = self.sql(expression, "path") 5409 5410 passing = self.expressions(expression, "passing") 5411 passing = f" PASSING {passing}" if passing else "" 5412 5413 on_condition = self.sql(expression, "on_condition") 5414 on_condition = f" {on_condition}" if on_condition else "" 5415 5416 path = f"{path}{passing}{on_condition}" 5417 5418 return self.func("JSON_EXISTS", this, path)
5543 def overlay_sql(self, expression: exp.Overlay) -> str: 5544 this = self.sql(expression, "this") 5545 expr = self.sql(expression, "expression") 5546 from_sql = self.sql(expression, "from_") 5547 for_sql = self.sql(expression, "for_") 5548 for_sql = f" FOR {for_sql}" if for_sql else "" 5549 5550 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
5557 def string_sql(self, expression: exp.String) -> str: 5558 this = expression.this 5559 zone = expression.args.get("zone") 5560 5561 if zone: 5562 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5563 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5564 # set for source_tz to transpile the time conversion before the STRING cast 5565 this = exp.ConvertTimezone( 5566 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5567 ) 5568 5569 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
5579 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5580 filler = self.sql(expression, "this") 5581 filler = f" {filler}" if filler else "" 5582 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5583 return f"TRUNCATE{filler} {with_count}"
5585 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5586 if self.SUPPORTS_UNIX_SECONDS: 5587 return self.function_fallback_sql(expression) 5588 5589 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5590 5591 return self.sql( 5592 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5593 )
5595 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5596 dim = expression.expression 5597 5598 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5599 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5600 if not (dim.is_int and dim.name == "1"): 5601 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5602 dim = None 5603 5604 # If dimension is required but not specified, default initialize it 5605 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5606 dim = exp.Literal.number(1) 5607 5608 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5610 def attach_sql(self, expression: exp.Attach) -> str: 5611 this = self.sql(expression, "this") 5612 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5613 expressions = self.expressions(expression) 5614 expressions = f" ({expressions})" if expressions else "" 5615 5616 return f"ATTACH{exists_sql} {this}{expressions}"
5618 def detach_sql(self, expression: exp.Detach) -> str: 5619 kind = self.sql(expression, "kind") 5620 kind = f" {kind}" if kind else "" 5621 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5622 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5623 exists = " IF EXISTS" if expression.args.get("exists") else "" 5624 if exists: 5625 kind = kind or " DATABASE" 5626 5627 this = self.sql(expression, "this") 5628 this = f" {this}" if this else "" 5629 cluster = self.sql(expression, "cluster") 5630 cluster = f" {cluster}" if cluster else "" 5631 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5632 sync = " SYNC" if expression.args.get("sync") else "" 5633 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
5646 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5647 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5648 encode = f"{encode} {self.sql(expression, 'this')}" 5649 5650 properties = expression.args.get("properties") 5651 if properties: 5652 encode = f"{encode} {self.properties(properties)}" 5653 5654 return encode
5656 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5657 this = self.sql(expression, "this") 5658 include = f"INCLUDE {this}" 5659 5660 column_def = self.sql(expression, "column_def") 5661 if column_def: 5662 include = f"{include} {column_def}" 5663 5664 alias = self.sql(expression, "alias") 5665 if alias: 5666 include = f"{include} AS {alias}" 5667 5668 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
5681 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5682 partitions = self.expressions(expression, "partition_expressions") 5683 create = self.expressions(expression, "create_expressions") 5684 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
5686 def partitionbyrangepropertydynamic_sql( 5687 self, expression: exp.PartitionByRangePropertyDynamic 5688 ) -> str: 5689 start = self.sql(expression, "start") 5690 end = self.sql(expression, "end") 5691 5692 every = expression.args["every"] 5693 if isinstance(every, exp.Interval) and every.this.is_string: 5694 every.this.replace(exp.Literal.number(every.name)) 5695 5696 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5709 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5710 kind = self.sql(expression, "kind") 5711 option = self.sql(expression, "option") 5712 option = f" {option}" if option else "" 5713 this = self.sql(expression, "this") 5714 this = f" {this}" if this else "" 5715 columns = self.expressions(expression) 5716 columns = f" {columns}" if columns else "" 5717 return f"{kind}{option} STATISTICS{this}{columns}"
5719 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5720 this = self.sql(expression, "this") 5721 columns = self.expressions(expression) 5722 inner_expression = self.sql(expression, "expression") 5723 inner_expression = f" {inner_expression}" if inner_expression else "" 5724 update_options = self.sql(expression, "update_options") 5725 update_options = f" {update_options} UPDATE" if update_options else "" 5726 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
5737 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5738 kind = self.sql(expression, "kind") 5739 this = self.sql(expression, "this") 5740 this = f" {this}" if this else "" 5741 inner_expression = self.sql(expression, "expression") 5742 return f"VALIDATE {kind}{this}{inner_expression}"
5744 def analyze_sql(self, expression: exp.Analyze) -> str: 5745 options = self.expressions(expression, key="options", sep=" ") 5746 options = f" {options}" if options else "" 5747 kind = self.sql(expression, "kind") 5748 kind = f" {kind}" if kind else "" 5749 this = self.sql(expression, "this") 5750 this = f" {this}" if this else "" 5751 mode = self.sql(expression, "mode") 5752 mode = f" {mode}" if mode else "" 5753 properties = self.sql(expression, "properties") 5754 properties = f" {properties}" if properties else "" 5755 partition = self.sql(expression, "partition") 5756 partition = f" {partition}" if partition else "" 5757 inner_expression = self.sql(expression, "expression") 5758 inner_expression = f" {inner_expression}" if inner_expression else "" 5759 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5761 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5762 this = self.sql(expression, "this") 5763 namespaces = self.expressions(expression, key="namespaces") 5764 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5765 passing = self.expressions(expression, key="passing") 5766 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5767 columns = self.expressions(expression, key="columns") 5768 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5769 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5770 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5776 def export_sql(self, expression: exp.Export) -> str: 5777 this = self.sql(expression, "this") 5778 connection = self.sql(expression, "connection") 5779 connection = f"WITH CONNECTION {connection} " if connection else "" 5780 options = self.sql(expression, "options") 5781 return f"EXPORT DATA {connection}{options} AS {this}"
5787 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5788 variables = self.expressions(expression, "this") 5789 default = self.sql(expression, "default") 5790 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5791 5792 kind = self.sql(expression, "kind") 5793 if isinstance(expression.args.get("kind"), exp.Schema): 5794 kind = f"TABLE {kind}" 5795 5796 kind = f" {kind}" if kind else "" 5797 5798 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
5800 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5801 kind = self.sql(expression, "kind") 5802 this = self.sql(expression, "this") 5803 set = self.sql(expression, "expression") 5804 using = self.sql(expression, "using") 5805 using = f" USING {using}" if using else "" 5806 5807 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5808 5809 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:
5832 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5833 # Snowflake GET/PUT statements: 5834 # PUT <file> <internalStage> <properties> 5835 # GET <internalStage> <file> <properties> 5836 props = expression.args.get("properties") 5837 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5838 this = self.sql(expression, "this") 5839 target = self.sql(expression, "target") 5840 5841 if isinstance(expression, exp.Put): 5842 return f"PUT {this} {target}{props_sql}" 5843 else: 5844 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
5846 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5847 this = self.sql(expression, "this") 5848 expr = self.sql(expression, "expression") 5849 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5850 return f"TRANSLATE({this} USING {expr}{with_error})"
5852 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5853 if self.SUPPORTS_DECODE_CASE: 5854 return self.func("DECODE", *expression.expressions) 5855 5856 decode_expr, *expressions = expression.expressions 5857 5858 ifs = [] 5859 for search, result in zip(expressions[::2], expressions[1::2]): 5860 if isinstance(search, exp.Literal): 5861 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 5862 elif isinstance(search, exp.Null): 5863 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 5864 else: 5865 if isinstance(search, exp.Binary): 5866 search = exp.paren(search) 5867 5868 cond = exp.or_( 5869 decode_expr.eq(search), 5870 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5871 copy=False, 5872 ) 5873 ifs.append(exp.If(this=cond, true=result)) 5874 5875 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5876 return self.sql(case)
5878 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5879 this = self.sql(expression, "this") 5880 this = self.seg(this, sep="") 5881 dimensions = self.expressions( 5882 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5883 ) 5884 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5885 metrics = self.expressions( 5886 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5887 ) 5888 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5889 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5890 facts = self.seg(f"FACTS {facts}") if facts else "" 5891 where = self.sql(expression, "where") 5892 where = self.seg(f"WHERE {where}") if where else "" 5893 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5894 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5896 def getextract_sql(self, expression: exp.GetExtract) -> str: 5897 this = expression.this 5898 expr = expression.expression 5899 5900 if not this.type or not expression.type: 5901 import sqlglot.optimizer.annotate_types 5902 5903 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 5904 5905 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 5906 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5907 5908 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
5925 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5926 method = self.sql(expression, "method") 5927 kind = expression.args.get("kind") 5928 if not kind: 5929 return f"REFRESH {method}" 5930 5931 every = self.sql(expression, "every") 5932 unit = self.sql(expression, "unit") 5933 every = f" EVERY {every} {unit}" if every else "" 5934 starts = self.sql(expression, "starts") 5935 starts = f" STARTS {starts}" if starts else "" 5936 5937 return f"REFRESH {method} ON {kind}{every}{starts}"
5946 def uuid_sql(self, expression: exp.Uuid) -> str: 5947 is_string = expression.args.get("is_string", False) 5948 uuid_func_sql = self.func("UUID") 5949 5950 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5951 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 5952 5953 return uuid_func_sql
5955 def initcap_sql(self, expression: exp.Initcap) -> str: 5956 delimiters = expression.expression 5957 5958 if delimiters: 5959 # do not generate delimiters arg if we are round-tripping from default delimiters 5960 if ( 5961 delimiters.is_string 5962 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5963 ): 5964 delimiters = None 5965 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5966 self.unsupported("INITCAP does not support custom delimiters") 5967 delimiters = None 5968 5969 return self.func("INITCAP", expression.this, delimiters)
5979 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5980 this = expression.this.name.upper() 5981 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5982 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5983 return "WEEK" 5984 5985 return self.func("WEEK", expression.this)
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: