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 UNPIVOT aliases are Identifiers (False means they're Literals) 450 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 451 452 # What delimiter to use for separating JSON key/value pairs 453 JSON_KEY_VALUE_PAIR_SEP = ":" 454 455 # INSERT OVERWRITE TABLE x override 456 INSERT_OVERWRITE = " OVERWRITE TABLE" 457 458 # Whether the SELECT .. INTO syntax is used instead of CTAS 459 SUPPORTS_SELECT_INTO = False 460 461 # Whether UNLOGGED tables can be created 462 SUPPORTS_UNLOGGED_TABLES = False 463 464 # Whether the CREATE TABLE LIKE statement is supported 465 SUPPORTS_CREATE_TABLE_LIKE = True 466 467 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 468 SUPPORTS_MODIFY_COLUMN = False 469 470 # Whether the LikeProperty needs to be specified inside of the schema clause 471 LIKE_PROPERTY_INSIDE_SCHEMA = False 472 473 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 474 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 475 MULTI_ARG_DISTINCT = True 476 477 # Whether the JSON extraction operators expect a value of type JSON 478 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 479 480 # Whether bracketed keys like ["foo"] are supported in JSON paths 481 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 482 483 # Whether to escape keys using single quotes in JSON paths 484 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 485 486 # The JSONPathPart expressions supported by this dialect 487 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 488 489 # Whether any(f(x) for x in array) can be implemented by this dialect 490 CAN_IMPLEMENT_ARRAY_ANY = False 491 492 # Whether the function TO_NUMBER is supported 493 SUPPORTS_TO_NUMBER = True 494 495 # Whether EXCLUDE in window specification is supported 496 SUPPORTS_WINDOW_EXCLUDE = False 497 498 # Whether or not set op modifiers apply to the outer set op or select. 499 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 500 # True means limit 1 happens after the set op, False means it it happens on y. 501 SET_OP_MODIFIERS = True 502 503 # Whether parameters from COPY statement are wrapped in parentheses 504 COPY_PARAMS_ARE_WRAPPED = True 505 506 # Whether values of params are set with "=" token or empty space 507 COPY_PARAMS_EQ_REQUIRED = False 508 509 # Whether COPY statement has INTO keyword 510 COPY_HAS_INTO_KEYWORD = True 511 512 # Whether the conditional TRY(expression) function is supported 513 TRY_SUPPORTED = True 514 515 # Whether the UESCAPE syntax in unicode strings is supported 516 SUPPORTS_UESCAPE = True 517 518 # Function used to replace escaped unicode codes in unicode strings 519 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 520 521 # The keyword to use when generating a star projection with excluded columns 522 STAR_EXCEPT = "EXCEPT" 523 524 # The HEX function name 525 HEX_FUNC = "HEX" 526 527 # The keywords to use when prefixing & separating WITH based properties 528 WITH_PROPERTIES_PREFIX = "WITH" 529 530 # Whether to quote the generated expression of exp.JsonPath 531 QUOTE_JSON_PATH = True 532 533 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 534 PAD_FILL_PATTERN_IS_REQUIRED = False 535 536 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 537 SUPPORTS_EXPLODING_PROJECTIONS = True 538 539 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 540 ARRAY_CONCAT_IS_VAR_LEN = True 541 542 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 543 SUPPORTS_CONVERT_TIMEZONE = False 544 545 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 546 SUPPORTS_MEDIAN = True 547 548 # Whether UNIX_SECONDS(timestamp) is supported 549 SUPPORTS_UNIX_SECONDS = False 550 551 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 552 ALTER_SET_WRAPPED = False 553 554 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 555 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 556 # TODO: The normalization should be done by default once we've tested it across all dialects. 557 NORMALIZE_EXTRACT_DATE_PARTS = False 558 559 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 560 PARSE_JSON_NAME: str | None = "PARSE_JSON" 561 562 # The function name of the exp.ArraySize expression 563 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 564 565 # The syntax to use when altering the type of a column 566 ALTER_SET_TYPE = "SET DATA TYPE" 567 568 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 569 # None -> Doesn't support it at all 570 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 571 # True (Postgres) -> Explicitly requires it 572 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 573 574 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 575 SUPPORTS_DECODE_CASE = True 576 577 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 578 SUPPORTS_BETWEEN_FLAGS = False 579 580 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 581 SUPPORTS_LIKE_QUANTIFIERS = True 582 583 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 584 MATCH_AGAINST_TABLE_PREFIX: str | None = None 585 586 # Whether to include the VARIABLE keyword for SET assignments 587 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 588 589 # The keyword to use for default value assignment in DECLARE statements 590 DECLARE_DEFAULT_ASSIGNMENT = "=" 591 592 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 593 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 594 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 595 UPDATE_STATEMENT_SUPPORTS_FROM = True 596 597 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 598 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 599 600 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 601 # - Snowflake: DROP ICEBERG TABLE a.b; 602 # - DuckDB: DROP TABLE a.b; 603 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 604 605 TYPE_MAPPING: t.ClassVar = { 606 exp.DType.DATETIME2: "TIMESTAMP", 607 exp.DType.NCHAR: "CHAR", 608 exp.DType.NVARCHAR: "VARCHAR", 609 exp.DType.MEDIUMTEXT: "TEXT", 610 exp.DType.LONGTEXT: "TEXT", 611 exp.DType.TINYTEXT: "TEXT", 612 exp.DType.BLOB: "VARBINARY", 613 exp.DType.MEDIUMBLOB: "BLOB", 614 exp.DType.LONGBLOB: "BLOB", 615 exp.DType.TINYBLOB: "BLOB", 616 exp.DType.INET: "INET", 617 exp.DType.ROWVERSION: "VARBINARY", 618 exp.DType.SMALLDATETIME: "TIMESTAMP", 619 } 620 621 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 622 623 TIME_PART_SINGULARS: t.ClassVar = { 624 "MICROSECONDS": "MICROSECOND", 625 "SECONDS": "SECOND", 626 "MINUTES": "MINUTE", 627 "HOURS": "HOUR", 628 "DAYS": "DAY", 629 "WEEKS": "WEEK", 630 "MONTHS": "MONTH", 631 "QUARTERS": "QUARTER", 632 "YEARS": "YEAR", 633 } 634 635 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 636 "cluster": lambda self, e: self.sql(e, "cluster"), 637 "distribute": lambda self, e: self.sql(e, "distribute"), 638 "sort": lambda self, e: self.sql(e, "sort"), 639 **AFTER_HAVING_MODIFIER_TRANSFORMS, 640 } 641 642 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 643 644 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 645 646 PARAMETER_TOKEN = "@" 647 NAMED_PLACEHOLDER_TOKEN = ":" 648 649 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 650 651 PROPERTIES_LOCATION: t.ClassVar = { 652 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 653 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 654 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 655 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 656 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 657 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 658 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 659 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 660 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 661 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 662 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 663 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 664 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 665 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 666 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 667 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 668 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 669 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 671 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 672 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 673 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 674 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 675 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 676 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 677 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 678 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 679 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 681 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 682 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 683 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 684 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 685 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 686 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 687 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 688 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 689 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 690 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 691 exp.HeapProperty: exp.Properties.Location.POST_WITH, 692 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 693 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 694 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 695 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 696 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 697 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 698 exp.JournalProperty: exp.Properties.Location.POST_NAME, 699 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 700 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 701 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 702 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 703 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 704 exp.LogProperty: exp.Properties.Location.POST_NAME, 705 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 706 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 707 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 708 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 709 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 710 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 711 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 712 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 713 exp.Order: exp.Properties.Location.POST_SCHEMA, 714 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 715 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 716 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 717 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 718 exp.Property: exp.Properties.Location.POST_WITH, 719 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 720 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 721 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 723 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 724 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 725 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 729 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 730 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 731 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 732 exp.Set: exp.Properties.Location.POST_SCHEMA, 733 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.SetProperty: exp.Properties.Location.POST_CREATE, 735 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 736 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 737 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 738 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 739 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 745 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 746 exp.Tags: exp.Properties.Location.POST_WITH, 747 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 748 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 749 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 750 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 751 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 752 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 753 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 754 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 755 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 757 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 758 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 759 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 760 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 761 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 762 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 763 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 764 } 765 766 # Keywords that can't be used as unquoted identifier names 767 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 768 769 # Exprs whose comments are separated from them for better formatting 770 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 771 exp.Command, 772 exp.Create, 773 exp.Describe, 774 exp.Delete, 775 exp.Drop, 776 exp.From, 777 exp.Insert, 778 exp.Join, 779 exp.MultitableInserts, 780 exp.Order, 781 exp.Group, 782 exp.Having, 783 exp.Select, 784 exp.SetOperation, 785 exp.Update, 786 exp.Where, 787 exp.With, 788 ) 789 790 # Exprs that should not have their comments generated in maybe_comment 791 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 792 exp.Binary, 793 exp.SetOperation, 794 ) 795 796 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 797 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 798 exp.Column, 799 exp.Literal, 800 exp.Neg, 801 exp.Paren, 802 ) 803 804 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 805 exp.DType.NVARCHAR, 806 exp.DType.VARCHAR, 807 exp.DType.CHAR, 808 exp.DType.NCHAR, 809 } 810 811 # Exprs that need to have all CTEs under them bubbled up to them 812 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 813 814 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 815 816 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 817 818 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 819 820 __slots__ = ( 821 "pretty", 822 "identify", 823 "normalize", 824 "pad", 825 "_indent", 826 "normalize_functions", 827 "unsupported_level", 828 "max_unsupported", 829 "leading_comma", 830 "max_text_width", 831 "comments", 832 "dialect", 833 "unsupported_messages", 834 "_escaped_quote_end", 835 "_escaped_byte_quote_end", 836 "_escaped_identifier_end", 837 "_next_name", 838 "_identifier_start", 839 "_identifier_end", 840 "_quote_json_path_key_using_brackets", 841 "_dispatch", 842 ) 843 844 def __init__( 845 self, 846 pretty: bool | int | None = None, 847 identify: str | bool = False, 848 normalize: bool = False, 849 pad: int = 2, 850 indent: int = 2, 851 normalize_functions: str | bool | None = None, 852 unsupported_level: ErrorLevel = ErrorLevel.WARN, 853 max_unsupported: int = 3, 854 leading_comma: bool = False, 855 max_text_width: int = 80, 856 comments: bool = True, 857 dialect: DialectType = None, 858 ): 859 import sqlglot 860 import sqlglot.dialects.dialect 861 862 self.pretty = pretty if pretty is not None else sqlglot.pretty 863 self.identify = identify 864 self.normalize = normalize 865 self.pad = pad 866 self._indent = indent 867 self.unsupported_level = unsupported_level 868 self.max_unsupported = max_unsupported 869 self.leading_comma = leading_comma 870 self.max_text_width = max_text_width 871 self.comments = comments 872 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 873 874 # This is both a Dialect property and a Generator argument, so we prioritize the latter 875 self.normalize_functions = ( 876 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 877 ) 878 879 self.unsupported_messages: list[str] = [] 880 self._escaped_quote_end: str = ( 881 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 882 ) 883 self._escaped_byte_quote_end: str = ( 884 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 885 if self.dialect.BYTE_END 886 else "" 887 ) 888 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 889 890 self._next_name = name_sequence("_t") 891 892 self._identifier_start = self.dialect.IDENTIFIER_START 893 self._identifier_end = self.dialect.IDENTIFIER_END 894 895 self._quote_json_path_key_using_brackets = True 896 897 cls = type(self) 898 dispatch = _DISPATCH_CACHE.get(cls) 899 if dispatch is None: 900 dispatch = _build_dispatch(cls) 901 _DISPATCH_CACHE[cls] = dispatch 902 self._dispatch = dispatch 903 904 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 905 """ 906 Generates the SQL string corresponding to the given syntax tree. 907 908 Args: 909 expression: The syntax tree. 910 copy: Whether to copy the expression. The generator performs mutations so 911 it is safer to copy. 912 913 Returns: 914 The SQL string corresponding to `expression`. 915 """ 916 if copy: 917 expression = expression.copy() 918 919 expression = self.preprocess(expression) 920 921 self.unsupported_messages = [] 922 sql = self.sql(expression).strip() 923 924 if self.pretty: 925 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 926 927 if self.unsupported_level == ErrorLevel.IGNORE: 928 return sql 929 930 if self.unsupported_level == ErrorLevel.WARN: 931 for msg in self.unsupported_messages: 932 logger.warning(msg) 933 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 934 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 935 936 return sql 937 938 def preprocess(self, expression: exp.Expr) -> exp.Expr: 939 """Apply generic preprocessing transformations to a given expression.""" 940 expression = self._move_ctes_to_top_level(expression) 941 942 if self.ENSURE_BOOLS: 943 import sqlglot.transforms 944 945 expression = sqlglot.transforms.ensure_bools(expression) 946 947 return expression 948 949 def _move_ctes_to_top_level(self, expression: E) -> E: 950 if ( 951 not expression.parent 952 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 953 and any(node.parent is not expression for node in expression.find_all(exp.With)) 954 ): 955 import sqlglot.transforms 956 957 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 958 return expression 959 960 def unsupported(self, message: str) -> None: 961 if self.unsupported_level == ErrorLevel.IMMEDIATE: 962 raise UnsupportedError(message) 963 self.unsupported_messages.append(message) 964 965 def sep(self, sep: str = " ") -> str: 966 return f"{sep.strip()}\n" if self.pretty else sep 967 968 def seg(self, sql: str, sep: str = " ") -> str: 969 return f"{self.sep(sep)}{sql}" 970 971 def sanitize_comment(self, comment: str) -> str: 972 comment = " " + comment if comment[0].strip() else comment 973 comment = comment + " " if comment[-1].strip() else comment 974 975 # Escape block comment markers to prevent premature closure or unintended nesting. 976 # This is necessary because single-line comments (--) are converted to block comments 977 # (/* */) on output, and any */ in the original text would close the comment early. 978 comment = comment.replace("*/", "* /").replace("/*", "/ *") 979 980 return comment 981 982 def maybe_comment( 983 self, 984 sql: str, 985 expression: exp.Expr | None = None, 986 comments: list[str] | None = None, 987 separated: bool = False, 988 ) -> str: 989 comments = ( 990 ((expression and expression.comments) if comments is None else comments) # type: ignore 991 if self.comments 992 else None 993 ) 994 995 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 996 return sql 997 998 comments_sql = " ".join( 999 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 1000 ) 1001 1002 if not comments_sql: 1003 return sql 1004 1005 comments_sql = self._replace_line_breaks(comments_sql) 1006 1007 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1008 return ( 1009 f"{self.sep()}{comments_sql}{sql}" 1010 if not sql or sql[0].isspace() 1011 else f"{comments_sql}{self.sep()}{sql}" 1012 ) 1013 1014 return f"{sql} {comments_sql}" 1015 1016 def wrap(self, expression: exp.Expr | str) -> str: 1017 this_sql = ( 1018 self.sql(expression) 1019 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1020 else self.sql(expression, "this") 1021 ) 1022 if not this_sql: 1023 return "()" 1024 1025 this_sql = self.indent(this_sql, level=1, pad=0) 1026 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1027 1028 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1029 original = self.identify 1030 self.identify = False 1031 result = func(*args, **kwargs) 1032 self.identify = original 1033 return result 1034 1035 def normalize_func(self, name: str) -> str: 1036 if self.normalize_functions == "upper" or self.normalize_functions is True: 1037 return name.upper() 1038 if self.normalize_functions == "lower": 1039 return name.lower() 1040 return name 1041 1042 def indent( 1043 self, 1044 sql: str, 1045 level: int = 0, 1046 pad: int | None = None, 1047 skip_first: bool = False, 1048 skip_last: bool = False, 1049 ) -> str: 1050 if not self.pretty or not sql: 1051 return sql 1052 1053 pad = self.pad if pad is None else pad 1054 lines = sql.split("\n") 1055 1056 return "\n".join( 1057 ( 1058 line 1059 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1060 else f"{' ' * (level * self._indent + pad)}{line}" 1061 ) 1062 for i, line in enumerate(lines) 1063 ) 1064 1065 def sql( 1066 self, 1067 expression: str | exp.Expr | None, 1068 key: str | None = None, 1069 comment: bool = True, 1070 ) -> str: 1071 if not expression: 1072 return "" 1073 1074 if isinstance(expression, str): 1075 return expression 1076 1077 if key: 1078 value = expression.args.get(key) 1079 if value: 1080 return self.sql(value) 1081 return "" 1082 1083 handler = self._dispatch.get(expression.__class__) 1084 1085 if handler: 1086 sql = handler(self, expression) 1087 elif isinstance(expression, exp.Func): 1088 sql = self.function_fallback_sql(expression) 1089 elif isinstance(expression, exp.Property): 1090 sql = self.property_sql(expression) 1091 else: 1092 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1093 1094 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1095 1096 def uncache_sql(self, expression: exp.Uncache) -> str: 1097 table = self.sql(expression, "this") 1098 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1099 return f"UNCACHE TABLE{exists_sql} {table}" 1100 1101 def cache_sql(self, expression: exp.Cache) -> str: 1102 lazy = " LAZY" if expression.args.get("lazy") else "" 1103 table = self.sql(expression, "this") 1104 options = expression.args.get("options") 1105 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1106 sql = self.sql(expression, "expression") 1107 sql = f" AS{self.sep()}{sql}" if sql else "" 1108 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1109 return self.prepend_ctes(expression, sql) 1110 1111 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1112 default = "DEFAULT " if expression.args.get("default") else "" 1113 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1114 1115 def column_parts(self, expression: exp.Column) -> str: 1116 return ".".join( 1117 self.sql(part) 1118 for part in ( 1119 expression.args.get("catalog"), 1120 expression.args.get("db"), 1121 expression.args.get("table"), 1122 expression.args.get("this"), 1123 ) 1124 if part 1125 ) 1126 1127 def column_sql(self, expression: exp.Column) -> str: 1128 join_mark = " (+)" if expression.args.get("join_mark") else "" 1129 1130 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1131 join_mark = "" 1132 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1133 1134 return f"{self.column_parts(expression)}{join_mark}" 1135 1136 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1137 return self.column_sql(expression) 1138 1139 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1140 this = self.sql(expression, "this") 1141 this = f" {this}" if this else "" 1142 position = self.sql(expression, "position") 1143 return f"{position}{this}" 1144 1145 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1146 column = self.sql(expression, "this") 1147 kind = self.sql(expression, "kind") 1148 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1149 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1150 kind = f"{sep}{kind}" if kind else "" 1151 constraints = f" {constraints}" if constraints else "" 1152 position = self.sql(expression, "position") 1153 position = f" {position}" if position else "" 1154 1155 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1156 kind = "" 1157 1158 return f"{exists}{column}{kind}{constraints}{position}" 1159 1160 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1161 this = self.sql(expression, "this") 1162 kind_sql = self.sql(expression, "kind").strip() 1163 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1164 1165 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1166 this = self.sql(expression, "this") 1167 if expression.args.get("not_null"): 1168 persisted = " PERSISTED NOT NULL" 1169 elif expression.args.get("persisted"): 1170 persisted = " PERSISTED" 1171 else: 1172 persisted = "" 1173 1174 return f"AS {this}{persisted}" 1175 1176 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1177 return self.token_sql(TokenType.AUTO_INCREMENT) 1178 1179 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1180 if isinstance(expression.this, list): 1181 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1182 else: 1183 this = self.sql(expression, "this") 1184 1185 return f"COMPRESS {this}" 1186 1187 def generatedasidentitycolumnconstraint_sql( 1188 self, expression: exp.GeneratedAsIdentityColumnConstraint 1189 ) -> str: 1190 this = "" 1191 if expression.this is not None: 1192 on_null = " ON NULL" if expression.args.get("on_null") else "" 1193 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1194 1195 start = expression.args.get("start") 1196 start = f"START WITH {start}" if start else "" 1197 increment = expression.args.get("increment") 1198 increment = f" INCREMENT BY {increment}" if increment else "" 1199 minvalue = expression.args.get("minvalue") 1200 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1201 maxvalue = expression.args.get("maxvalue") 1202 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1203 cycle = expression.args.get("cycle") 1204 cycle_sql = "" 1205 1206 if cycle is not None: 1207 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1208 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1209 1210 sequence_opts = "" 1211 if start or increment or cycle_sql: 1212 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1213 sequence_opts = f" ({sequence_opts.strip()})" 1214 1215 expr = self.sql(expression, "expression") 1216 expr = f"({expr})" if expr else "IDENTITY" 1217 1218 return f"GENERATED{this} AS {expr}{sequence_opts}" 1219 1220 def generatedasrowcolumnconstraint_sql( 1221 self, expression: exp.GeneratedAsRowColumnConstraint 1222 ) -> str: 1223 start = "START" if expression.args.get("start") else "END" 1224 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1225 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1226 1227 def periodforsystemtimeconstraint_sql( 1228 self, expression: exp.PeriodForSystemTimeConstraint 1229 ) -> str: 1230 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1231 1232 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1233 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1234 1235 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1236 desc = expression.args.get("desc") 1237 if desc is not None: 1238 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1239 options = self.expressions(expression, key="options", flat=True, sep=" ") 1240 options = f" {options}" if options else "" 1241 return f"PRIMARY KEY{options}" 1242 1243 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1244 this = self.sql(expression, "this") 1245 this = f" {this}" if this else "" 1246 index_type = expression.args.get("index_type") 1247 index_type = f" USING {index_type}" if index_type else "" 1248 on_conflict = self.sql(expression, "on_conflict") 1249 on_conflict = f" {on_conflict}" if on_conflict else "" 1250 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1251 options = self.expressions(expression, key="options", flat=True, sep=" ") 1252 options = f" {options}" if options else "" 1253 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1254 1255 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1256 input_ = expression.args.get("input_") 1257 output = expression.args.get("output") 1258 variadic = expression.args.get("variadic") 1259 1260 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1261 if variadic: 1262 return "VARIADIC" 1263 1264 if input_ and output: 1265 return f"IN{self.INOUT_SEPARATOR}OUT" 1266 if input_: 1267 return "IN" 1268 if output: 1269 return "OUT" 1270 1271 return "" 1272 1273 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1274 return self.sql(expression, "this") 1275 1276 def create_sql(self, expression: exp.Create) -> str: 1277 kind = self.sql(expression, "kind") 1278 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1279 1280 properties = expression.args.get("properties") 1281 1282 if ( 1283 kind == "TRIGGER" 1284 and properties 1285 and properties.expressions 1286 and isinstance(properties.expressions[0], exp.TriggerProperties) 1287 and properties.expressions[0].args.get("constraint") 1288 ): 1289 kind = f"CONSTRAINT {kind}" 1290 1291 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1292 1293 this = self.createable_sql(expression, properties_locs) 1294 1295 properties_sql = "" 1296 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1297 exp.Properties.Location.POST_WITH 1298 ): 1299 props_ast = exp.Properties( 1300 expressions=[ 1301 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1302 *properties_locs[exp.Properties.Location.POST_WITH], 1303 ] 1304 ) 1305 props_ast.parent = expression 1306 properties_sql = self.sql(props_ast) 1307 1308 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1309 properties_sql = self.sep() + properties_sql 1310 elif not self.pretty: 1311 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1312 properties_sql = f" {properties_sql}" 1313 1314 begin = " BEGIN" if expression.args.get("begin") else "" 1315 1316 expression_sql = self.sql(expression, "expression") 1317 if expression_sql: 1318 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1319 1320 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1321 postalias_props_sql = "" 1322 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1323 postalias_props_sql = self.properties( 1324 exp.Properties( 1325 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1326 ), 1327 wrapped=False, 1328 ) 1329 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1330 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1331 1332 postindex_props_sql = "" 1333 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1334 postindex_props_sql = self.properties( 1335 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1336 wrapped=False, 1337 prefix=" ", 1338 ) 1339 1340 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1341 indexes = f" {indexes}" if indexes else "" 1342 index_sql = indexes + postindex_props_sql 1343 1344 replace = " OR REPLACE" if expression.args.get("replace") else "" 1345 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1346 unique = " UNIQUE" if expression.args.get("unique") else "" 1347 1348 clustered = expression.args.get("clustered") 1349 if clustered is None: 1350 clustered_sql = "" 1351 elif clustered: 1352 clustered_sql = " CLUSTERED COLUMNSTORE" 1353 else: 1354 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1355 1356 postcreate_props_sql = "" 1357 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1358 postcreate_props_sql = self.properties( 1359 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1360 sep=" ", 1361 prefix=" ", 1362 wrapped=False, 1363 ) 1364 1365 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1366 1367 postexpression_props_sql = "" 1368 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1369 postexpression_props_sql = self.properties( 1370 exp.Properties( 1371 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1372 ), 1373 sep=" ", 1374 prefix=" ", 1375 wrapped=False, 1376 ) 1377 1378 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1379 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1380 no_schema_binding = ( 1381 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1382 ) 1383 1384 clone = self.sql(expression, "clone") 1385 clone = f" {clone}" if clone else "" 1386 1387 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1388 properties_expression = f"{expression_sql}{properties_sql}" 1389 else: 1390 properties_expression = f"{properties_sql}{expression_sql}" 1391 1392 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1393 return self.prepend_ctes(expression, expression_sql) 1394 1395 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1396 start = self.sql(expression, "start") 1397 start = f"START WITH {start}" if start else "" 1398 increment = self.sql(expression, "increment") 1399 increment = f" INCREMENT BY {increment}" if increment else "" 1400 minvalue = self.sql(expression, "minvalue") 1401 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1402 maxvalue = self.sql(expression, "maxvalue") 1403 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1404 owned = self.sql(expression, "owned") 1405 owned = f" OWNED BY {owned}" if owned else "" 1406 1407 cache = expression.args.get("cache") 1408 if cache is None: 1409 cache_str = "" 1410 elif cache is True: 1411 cache_str = " CACHE" 1412 else: 1413 cache_str = f" CACHE {cache}" 1414 1415 options = self.expressions(expression, key="options", flat=True, sep=" ") 1416 options = f" {options}" if options else "" 1417 1418 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1419 1420 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1421 timing = expression.args.get("timing", "") 1422 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1423 timing_events = f"{timing} {events}".strip() if timing or events else "" 1424 1425 parts = [timing_events, "ON", self.sql(expression, "table")] 1426 1427 if referenced_table := expression.args.get("referenced_table"): 1428 parts.extend(["FROM", self.sql(referenced_table)]) 1429 1430 if deferrable := expression.args.get("deferrable"): 1431 parts.append(deferrable) 1432 1433 if initially := expression.args.get("initially"): 1434 parts.append(f"INITIALLY {initially}") 1435 1436 if referencing := expression.args.get("referencing"): 1437 parts.append(self.sql(referencing)) 1438 1439 if for_each := expression.args.get("for_each"): 1440 parts.append(f"FOR EACH {for_each}") 1441 1442 if when := expression.args.get("when"): 1443 parts.append(f"WHEN ({self.sql(when)})") 1444 1445 parts.append(self.sql(expression, "execute")) 1446 1447 return self.sep().join(parts) 1448 1449 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1450 parts = [] 1451 1452 if old_alias := expression.args.get("old"): 1453 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1454 1455 if new_alias := expression.args.get("new"): 1456 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1457 1458 return f"REFERENCING {' '.join(parts)}" 1459 1460 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1461 columns = expression.args.get("columns") 1462 if columns: 1463 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1464 1465 return self.sql(expression, "this") 1466 1467 def clone_sql(self, expression: exp.Clone) -> str: 1468 this = self.sql(expression, "this") 1469 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1470 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1471 return f"{shallow}{keyword} {this}" 1472 1473 def describe_sql(self, expression: exp.Describe) -> str: 1474 style = expression.args.get("style") 1475 style = f" {style}" if style else "" 1476 partition = self.sql(expression, "partition") 1477 partition = f" {partition}" if partition else "" 1478 format = self.sql(expression, "format") 1479 format = f" {format}" if format else "" 1480 as_json = " AS JSON" if expression.args.get("as_json") else "" 1481 1482 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1483 1484 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1485 tag = self.sql(expression, "tag") 1486 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1487 1488 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1489 with_ = self.sql(expression, "with_") 1490 if with_: 1491 sql = f"{with_}{self.sep()}{sql}" 1492 return sql 1493 1494 def with_sql(self, expression: exp.With) -> str: 1495 sql = self.expressions(expression, flat=True) 1496 recursive = ( 1497 "RECURSIVE " 1498 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1499 else "" 1500 ) 1501 search = self.sql(expression, "search") 1502 search = f" {search}" if search else "" 1503 1504 return f"WITH {recursive}{sql}{search}" 1505 1506 def cte_sql(self, expression: exp.CTE) -> str: 1507 alias = expression.args.get("alias") 1508 if alias: 1509 alias.add_comments(expression.pop_comments()) 1510 1511 alias_sql = self.sql(expression, "alias") 1512 1513 materialized = expression.args.get("materialized") 1514 if materialized is False: 1515 materialized = "NOT MATERIALIZED " 1516 elif materialized: 1517 materialized = "MATERIALIZED " 1518 1519 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1520 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1521 1522 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1523 1524 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1525 alias = self.sql(expression, "this") 1526 columns = self.expressions(expression, key="columns", flat=True) 1527 columns = f"({columns})" if columns else "" 1528 1529 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1530 columns = "" 1531 self.unsupported("Named columns are not supported in table alias.") 1532 1533 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1534 alias = self._next_name() 1535 1536 return f"{alias}{columns}" 1537 1538 def bitstring_sql(self, expression: exp.BitString) -> str: 1539 this = self.sql(expression, "this") 1540 if self.dialect.BIT_START: 1541 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1542 return f"{int(this, 2)}" 1543 1544 def hexstring_sql( 1545 self, expression: exp.HexString, binary_function_repr: str | None = None 1546 ) -> str: 1547 this = self.sql(expression, "this") 1548 is_integer_type = expression.args.get("is_integer") 1549 1550 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1551 not self.dialect.HEX_START and not binary_function_repr 1552 ): 1553 # Integer representation will be returned if: 1554 # - The read dialect treats the hex value as integer literal but not the write 1555 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1556 return f"{int(this, 16)}" 1557 1558 if not is_integer_type: 1559 # Read dialect treats the hex value as BINARY/BLOB 1560 if binary_function_repr: 1561 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1562 return self.func(binary_function_repr, exp.Literal.string(this)) 1563 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1564 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1565 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1566 1567 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1568 1569 def bytestring_sql(self, expression: exp.ByteString) -> str: 1570 this = self.sql(expression, "this") 1571 if self.dialect.BYTE_START: 1572 escaped_byte_string = self.escape_str( 1573 this, 1574 escape_backslash=False, 1575 delimiter=self.dialect.BYTE_END, 1576 escaped_delimiter=self._escaped_byte_quote_end, 1577 is_byte_string=True, 1578 ) 1579 is_bytes = expression.args.get("is_bytes", False) 1580 delimited_byte_string = ( 1581 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1582 ) 1583 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1584 return self.sql( 1585 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1586 ) 1587 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1588 return self.sql( 1589 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1590 ) 1591 1592 return delimited_byte_string 1593 return this 1594 1595 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1596 this = self.sql(expression, "this") 1597 escape = expression.args.get("escape") 1598 1599 if self.dialect.UNICODE_START: 1600 escape_substitute = r"\\\1" 1601 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1602 else: 1603 escape_substitute = r"\\u\1" 1604 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1605 1606 if escape: 1607 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1608 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1609 else: 1610 escape_pattern = ESCAPED_UNICODE_RE 1611 escape_sql = "" 1612 1613 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1614 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1615 1616 return f"{left_quote}{this}{right_quote}{escape_sql}" 1617 1618 def rawstring_sql(self, expression: exp.RawString) -> str: 1619 string = expression.this 1620 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1621 string = string.replace("\\", "\\\\") 1622 1623 string = self.escape_str(string, escape_backslash=False) 1624 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1625 1626 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1627 this = self.sql(expression, "this") 1628 specifier = self.sql(expression, "expression") 1629 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1630 return f"{this}{specifier}" 1631 1632 def datatype_sql(self, expression: exp.DataType) -> str: 1633 nested = "" 1634 values = "" 1635 1636 expr_nested = expression.args.get("nested") 1637 interior = ( 1638 self.expressions( 1639 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1640 ) 1641 if expr_nested and self.pretty 1642 else self.expressions(expression, flat=True) 1643 ) 1644 1645 type_value = expression.this 1646 if type_value in self.UNSUPPORTED_TYPES: 1647 self.unsupported( 1648 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1649 ) 1650 1651 type_sql: t.Any = "" 1652 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1653 type_sql = self.sql(expression, "kind") 1654 elif type_value == exp.DType.CHARACTER_SET: 1655 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1656 else: 1657 type_sql = ( 1658 self.TYPE_MAPPING.get(type_value, type_value.value) 1659 if isinstance(type_value, exp.DType) 1660 else type_value 1661 ) 1662 1663 if interior: 1664 if expr_nested: 1665 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1666 if expression.args.get("values") is not None: 1667 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1668 values = self.expressions(expression, key="values", flat=True) 1669 values = f"{delimiters[0]}{values}{delimiters[1]}" 1670 elif type_value == exp.DType.INTERVAL: 1671 nested = f" {interior}" 1672 else: 1673 nested = f"({interior})" 1674 1675 type_sql = f"{type_sql}{nested}{values}" 1676 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1677 exp.DType.TIMETZ, 1678 exp.DType.TIMESTAMPTZ, 1679 ): 1680 type_sql = f"{type_sql} WITH TIME ZONE" 1681 1682 return type_sql 1683 1684 def directory_sql(self, expression: exp.Directory) -> str: 1685 local = "LOCAL " if expression.args.get("local") else "" 1686 row_format = self.sql(expression, "row_format") 1687 row_format = f" {row_format}" if row_format else "" 1688 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1689 1690 def delete_sql(self, expression: exp.Delete) -> str: 1691 hint = self.sql(expression, "hint") 1692 this = self.sql(expression, "this") 1693 this = f" FROM {this}" if this else "" 1694 using = self.expressions(expression, key="using") 1695 using = f" USING {using}" if using else "" 1696 cluster = self.sql(expression, "cluster") 1697 cluster = f" {cluster}" if cluster else "" 1698 where = self.sql(expression, "where") 1699 returning = self.sql(expression, "returning") 1700 order = self.sql(expression, "order") 1701 limit = self.sql(expression, "limit") 1702 tables = self.expressions(expression, key="tables") 1703 tables = f" {tables}" if tables else "" 1704 if self.RETURNING_END: 1705 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1706 else: 1707 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1708 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1709 1710 def drop_sql(self, expression: exp.Drop) -> str: 1711 this = self.sql(expression, "this") 1712 expressions = self.expressions(expression, flat=True) 1713 expressions = f" ({expressions})" if expressions else "" 1714 kind = expression.args["kind"] 1715 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1716 iceberg = ( 1717 " ICEBERG" 1718 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1719 else "" 1720 ) 1721 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1722 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1723 on_cluster = self.sql(expression, "cluster") 1724 on_cluster = f" {on_cluster}" if on_cluster else "" 1725 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1726 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1727 cascade = " CASCADE" if expression.args.get("cascade") else "" 1728 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1729 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1730 purge = " PURGE" if expression.args.get("purge") else "" 1731 sync = " SYNC" if expression.args.get("sync") else "" 1732 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}" 1733 1734 def set_operation(self, expression: exp.SetOperation) -> str: 1735 op_type = type(expression) 1736 op_name = op_type.key.upper() 1737 1738 distinct = expression.args.get("distinct") 1739 if ( 1740 distinct is False 1741 and op_type in (exp.Except, exp.Intersect) 1742 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1743 ): 1744 self.unsupported(f"{op_name} ALL is not supported") 1745 1746 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1747 1748 if distinct is None: 1749 distinct = default_distinct 1750 if distinct is None: 1751 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1752 1753 if distinct is default_distinct: 1754 distinct_or_all = "" 1755 else: 1756 distinct_or_all = " DISTINCT" if distinct else " ALL" 1757 1758 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1759 side_kind = f"{side_kind} " if side_kind else "" 1760 1761 by_name = " BY NAME" if expression.args.get("by_name") else "" 1762 on = self.expressions(expression, key="on", flat=True) 1763 on = f" ON ({on})" if on else "" 1764 1765 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1766 1767 def set_operations(self, expression: exp.SetOperation) -> str: 1768 if not self.SET_OP_MODIFIERS: 1769 limit = expression.args.get("limit") 1770 order = expression.args.get("order") 1771 1772 if limit or order: 1773 select = self._move_ctes_to_top_level( 1774 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1775 ) 1776 1777 if limit: 1778 select = select.limit(limit.pop(), copy=False) 1779 if order: 1780 select = select.order_by(order.pop(), copy=False) 1781 return self.sql(select) 1782 1783 sqls: list[str] = [] 1784 stack: list[str | exp.Expr] = [expression] 1785 1786 while stack: 1787 node = stack.pop() 1788 1789 if isinstance(node, exp.SetOperation): 1790 stack.append(node.expression) 1791 stack.append( 1792 self.maybe_comment( 1793 self.set_operation(node), comments=node.comments, separated=True 1794 ) 1795 ) 1796 stack.append(node.this) 1797 else: 1798 sqls.append(self.sql(node)) 1799 1800 this = self.sep().join(sqls) 1801 this = self.query_modifiers(expression, this) 1802 return self.prepend_ctes(expression, this) 1803 1804 def fetch_sql(self, expression: exp.Fetch) -> str: 1805 direction = expression.args.get("direction") 1806 direction = f" {direction}" if direction else "" 1807 count = self.sql(expression, "count") 1808 count = f" {count}" if count else "" 1809 limit_options = self.sql(expression, "limit_options") 1810 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1811 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1812 1813 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1814 percent = " PERCENT" if expression.args.get("percent") else "" 1815 rows = " ROWS" if expression.args.get("rows") else "" 1816 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1817 if not with_ties and rows: 1818 with_ties = " ONLY" 1819 return f"{percent}{rows}{with_ties}" 1820 1821 def filter_sql(self, expression: exp.Filter) -> str: 1822 if self.AGGREGATE_FILTER_SUPPORTED: 1823 this = self.sql(expression, "this") 1824 where = self.sql(expression, "expression").strip() 1825 return f"{this} FILTER({where})" 1826 1827 agg = expression.this 1828 agg_arg = agg.this 1829 cond = expression.expression.this 1830 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1831 return self.sql(agg) 1832 1833 def hint_sql(self, expression: exp.Hint) -> str: 1834 if not self.QUERY_HINTS: 1835 self.unsupported("Hints are not supported") 1836 return "" 1837 1838 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1839 1840 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1841 using = self.sql(expression, "using") 1842 using = f" USING {using}" if using else "" 1843 columns = self.expressions(expression, key="columns", flat=True) 1844 columns = f"({columns})" if columns else "" 1845 partition_by = self.expressions(expression, key="partition_by", flat=True) 1846 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1847 where = self.sql(expression, "where") 1848 include = self.expressions(expression, key="include", flat=True) 1849 if include: 1850 include = f" INCLUDE ({include})" 1851 with_storage = self.expressions(expression, key="with_storage", flat=True) 1852 with_storage = f" WITH ({with_storage})" if with_storage else "" 1853 tablespace = self.sql(expression, "tablespace") 1854 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1855 on = self.sql(expression, "on") 1856 on = f" ON {on}" if on else "" 1857 1858 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1859 1860 def index_sql(self, expression: exp.Index) -> str: 1861 unique = "UNIQUE " if expression.args.get("unique") else "" 1862 primary = "PRIMARY " if expression.args.get("primary") else "" 1863 amp = "AMP " if expression.args.get("amp") else "" 1864 name = self.sql(expression, "this") 1865 name = f"{name} " if name else "" 1866 table = self.sql(expression, "table") 1867 table = f"{self.INDEX_ON} {table}" if table else "" 1868 1869 index = "INDEX " if not table else "" 1870 1871 params = self.sql(expression, "params") 1872 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1873 1874 def identifier_sql(self, expression: exp.Identifier) -> str: 1875 text = expression.name 1876 lower = text.lower() 1877 quoted = expression.quoted 1878 text = lower if self.normalize and not quoted else text 1879 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1880 if ( 1881 quoted 1882 or self.dialect.can_quote(expression, self.identify) 1883 or lower in self.RESERVED_KEYWORDS 1884 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1885 ): 1886 text = ( 1887 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1888 ) 1889 return text 1890 1891 def hex_sql(self, expression: exp.Hex) -> str: 1892 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1893 if self.dialect.HEX_LOWERCASE: 1894 text = self.func("LOWER", text) 1895 1896 return text 1897 1898 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1899 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1900 if not self.dialect.HEX_LOWERCASE: 1901 text = self.func("LOWER", text) 1902 return text 1903 1904 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1905 input_format = self.sql(expression, "input_format") 1906 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1907 output_format = self.sql(expression, "output_format") 1908 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1909 return self.sep().join((input_format, output_format)) 1910 1911 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1912 string = self.sql(exp.Literal.string(expression.name)) 1913 return f"{prefix}{string}" 1914 1915 def partition_sql(self, expression: exp.Partition) -> str: 1916 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1917 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1918 1919 def properties_sql(self, expression: exp.Properties) -> str: 1920 root_properties = [] 1921 with_properties = [] 1922 1923 for p in expression.expressions: 1924 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1925 if p_loc == exp.Properties.Location.POST_WITH: 1926 with_properties.append(p) 1927 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1928 root_properties.append(p) 1929 1930 root_props_ast = exp.Properties(expressions=root_properties) 1931 root_props_ast.parent = expression.parent 1932 1933 with_props_ast = exp.Properties(expressions=with_properties) 1934 with_props_ast.parent = expression.parent 1935 1936 root_props = self.root_properties(root_props_ast) 1937 with_props = self.with_properties(with_props_ast) 1938 1939 if root_props and with_props and not self.pretty: 1940 with_props = " " + with_props 1941 1942 return root_props + with_props 1943 1944 def root_properties(self, properties: exp.Properties) -> str: 1945 if properties.expressions: 1946 return self.expressions(properties, indent=False, sep=" ") 1947 return "" 1948 1949 def properties( 1950 self, 1951 properties: exp.Properties, 1952 prefix: str = "", 1953 sep: str = ", ", 1954 suffix: str = "", 1955 wrapped: bool = True, 1956 ) -> str: 1957 if properties.expressions: 1958 expressions = self.expressions(properties, sep=sep, indent=False) 1959 if expressions: 1960 expressions = self.wrap(expressions) if wrapped else expressions 1961 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1962 return "" 1963 1964 def with_properties(self, properties: exp.Properties) -> str: 1965 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1966 1967 def locate_properties(self, properties: exp.Properties) -> defaultdict: 1968 properties_locs = defaultdict(list) 1969 for p in properties.expressions: 1970 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1971 if p_loc != exp.Properties.Location.UNSUPPORTED: 1972 properties_locs[p_loc].append(p) 1973 else: 1974 self.unsupported(f"Unsupported property {p.key}") 1975 1976 return properties_locs 1977 1978 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1979 if isinstance(expression.this, exp.Dot): 1980 return self.sql(expression, "this") 1981 return f"'{expression.name}'" if string_key else expression.name 1982 1983 def property_sql(self, expression: exp.Property) -> str: 1984 property_cls = expression.__class__ 1985 if property_cls == exp.Property: 1986 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1987 1988 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1989 if not property_name: 1990 self.unsupported(f"Unsupported property {expression.key}") 1991 1992 return f"{property_name}={self.sql(expression, 'this')}" 1993 1994 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 1995 return f"UUID {self.sql(expression, 'this')}" 1996 1997 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1998 if self.SUPPORTS_CREATE_TABLE_LIKE: 1999 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2000 options = f" {options}" if options else "" 2001 2002 like = f"LIKE {self.sql(expression, 'this')}{options}" 2003 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2004 like = f"({like})" 2005 2006 return like 2007 2008 if expression.expressions: 2009 self.unsupported("Transpilation of LIKE property options is unsupported") 2010 2011 select = exp.select("*").from_(expression.this).limit(0) 2012 return f"AS {self.sql(select)}" 2013 2014 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2015 no = "NO " if expression.args.get("no") else "" 2016 protection = " PROTECTION" if expression.args.get("protection") else "" 2017 return f"{no}FALLBACK{protection}" 2018 2019 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2020 no = "NO " if expression.args.get("no") else "" 2021 local = expression.args.get("local") 2022 local = f"{local} " if local else "" 2023 dual = "DUAL " if expression.args.get("dual") else "" 2024 before = "BEFORE " if expression.args.get("before") else "" 2025 after = "AFTER " if expression.args.get("after") else "" 2026 return f"{no}{local}{dual}{before}{after}JOURNAL" 2027 2028 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2029 freespace = self.sql(expression, "this") 2030 percent = " PERCENT" if expression.args.get("percent") else "" 2031 return f"FREESPACE={freespace}{percent}" 2032 2033 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2034 if expression.args.get("default"): 2035 property = "DEFAULT" 2036 elif expression.args.get("on"): 2037 property = "ON" 2038 else: 2039 property = "OFF" 2040 return f"CHECKSUM={property}" 2041 2042 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2043 if expression.args.get("no"): 2044 return "NO MERGEBLOCKRATIO" 2045 if expression.args.get("default"): 2046 return "DEFAULT MERGEBLOCKRATIO" 2047 2048 percent = " PERCENT" if expression.args.get("percent") else "" 2049 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2050 2051 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2052 expressions = self.expressions(expression, flat=True) 2053 expressions = f"({expressions})" if expressions else "" 2054 return f"USING {self.sql(expression, 'this')}{expressions}" 2055 2056 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2057 default = expression.args.get("default") 2058 minimum = expression.args.get("minimum") 2059 maximum = expression.args.get("maximum") 2060 if default or minimum or maximum: 2061 if default: 2062 prop = "DEFAULT" 2063 elif minimum: 2064 prop = "MINIMUM" 2065 else: 2066 prop = "MAXIMUM" 2067 return f"{prop} DATABLOCKSIZE" 2068 units = expression.args.get("units") 2069 units = f" {units}" if units else "" 2070 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2071 2072 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2073 autotemp = expression.args.get("autotemp") 2074 always = expression.args.get("always") 2075 default = expression.args.get("default") 2076 manual = expression.args.get("manual") 2077 never = expression.args.get("never") 2078 2079 if autotemp is not None: 2080 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2081 elif always: 2082 prop = "ALWAYS" 2083 elif default: 2084 prop = "DEFAULT" 2085 elif manual: 2086 prop = "MANUAL" 2087 elif never: 2088 prop = "NEVER" 2089 return f"BLOCKCOMPRESSION={prop}" 2090 2091 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2092 no = expression.args.get("no") 2093 no = " NO" if no else "" 2094 concurrent = expression.args.get("concurrent") 2095 concurrent = " CONCURRENT" if concurrent else "" 2096 target = self.sql(expression, "target") 2097 target = f" {target}" if target else "" 2098 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2099 2100 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2101 if isinstance(expression.this, list): 2102 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2103 if expression.this: 2104 modulus = self.sql(expression, "this") 2105 remainder = self.sql(expression, "expression") 2106 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2107 2108 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2109 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2110 return f"FROM ({from_expressions}) TO ({to_expressions})" 2111 2112 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2113 this = self.sql(expression, "this") 2114 2115 for_values_or_default = expression.expression 2116 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2117 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2118 else: 2119 for_values_or_default = " DEFAULT" 2120 2121 return f"PARTITION OF {this}{for_values_or_default}" 2122 2123 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2124 kind = expression.args.get("kind") 2125 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2126 for_or_in = expression.args.get("for_or_in") 2127 for_or_in = f" {for_or_in}" if for_or_in else "" 2128 lock_type = expression.args.get("lock_type") 2129 override = " OVERRIDE" if expression.args.get("override") else "" 2130 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2131 2132 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2133 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2134 statistics = expression.args.get("statistics") 2135 statistics_sql = "" 2136 if statistics is not None: 2137 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2138 return f"{data_sql}{statistics_sql}" 2139 2140 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2141 this = self.sql(expression, "this") 2142 this = f"HISTORY_TABLE={this}" if this else "" 2143 data_consistency: str | None = self.sql(expression, "data_consistency") 2144 data_consistency = ( 2145 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2146 ) 2147 retention_period: str | None = self.sql(expression, "retention_period") 2148 retention_period = ( 2149 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2150 ) 2151 2152 if this: 2153 on_sql = self.func("ON", this, data_consistency, retention_period) 2154 else: 2155 on_sql = "ON" if expression.args.get("on") else "OFF" 2156 2157 sql = f"SYSTEM_VERSIONING={on_sql}" 2158 2159 return f"WITH({sql})" if expression.args.get("with_") else sql 2160 2161 def insert_sql(self, expression: exp.Insert) -> str: 2162 hint = self.sql(expression, "hint") 2163 overwrite = expression.args.get("overwrite") 2164 2165 if isinstance(expression.this, exp.Directory): 2166 this = " OVERWRITE" if overwrite else " INTO" 2167 else: 2168 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2169 2170 stored = self.sql(expression, "stored") 2171 stored = f" {stored}" if stored else "" 2172 alternative = expression.args.get("alternative") 2173 alternative = f" OR {alternative}" if alternative else "" 2174 ignore = " IGNORE" if expression.args.get("ignore") else "" 2175 is_function = expression.args.get("is_function") 2176 if is_function: 2177 this = f"{this} FUNCTION" 2178 this = f"{this} {self.sql(expression, 'this')}" 2179 2180 exists = " IF EXISTS" if expression.args.get("exists") else "" 2181 where = self.sql(expression, "where") 2182 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2183 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2184 on_conflict = self.sql(expression, "conflict") 2185 on_conflict = f" {on_conflict}" if on_conflict else "" 2186 by_name = " BY NAME" if expression.args.get("by_name") else "" 2187 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2188 returning = self.sql(expression, "returning") 2189 2190 if self.RETURNING_END: 2191 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2192 else: 2193 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2194 2195 partition_by = self.sql(expression, "partition") 2196 partition_by = f" {partition_by}" if partition_by else "" 2197 settings = self.sql(expression, "settings") 2198 settings = f" {settings}" if settings else "" 2199 2200 source = self.sql(expression, "source") 2201 source = f"TABLE {source}" if source else "" 2202 2203 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2204 return self.prepend_ctes(expression, sql) 2205 2206 def introducer_sql(self, expression: exp.Introducer) -> str: 2207 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2208 2209 def kill_sql(self, expression: exp.Kill) -> str: 2210 kind = self.sql(expression, "kind") 2211 kind = f" {kind}" if kind else "" 2212 this = self.sql(expression, "this") 2213 this = f" {this}" if this else "" 2214 return f"KILL{kind}{this}" 2215 2216 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2217 return expression.name 2218 2219 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2220 return expression.name 2221 2222 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2223 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2224 2225 constraint = self.sql(expression, "constraint") 2226 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2227 2228 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2229 if conflict_keys: 2230 conflict_keys = f"({conflict_keys})" 2231 2232 index_predicate = self.sql(expression, "index_predicate") 2233 conflict_keys = f"{conflict_keys}{index_predicate} " 2234 2235 action = self.sql(expression, "action") 2236 2237 expressions = self.expressions(expression, flat=True) 2238 if expressions: 2239 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2240 expressions = f" {set_keyword}{expressions}" 2241 2242 where = self.sql(expression, "where") 2243 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2244 2245 def returning_sql(self, expression: exp.Returning) -> str: 2246 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2247 2248 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2249 fields = self.sql(expression, "fields") 2250 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2251 escaped = self.sql(expression, "escaped") 2252 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2253 items = self.sql(expression, "collection_items") 2254 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2255 keys = self.sql(expression, "map_keys") 2256 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2257 lines = self.sql(expression, "lines") 2258 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2259 null = self.sql(expression, "null") 2260 null = f" NULL DEFINED AS {null}" if null else "" 2261 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2262 2263 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2264 return f"WITH ({self.expressions(expression, flat=True)})" 2265 2266 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2267 this = f"{self.sql(expression, 'this')} INDEX" 2268 target = self.sql(expression, "target") 2269 target = f" FOR {target}" if target else "" 2270 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2271 2272 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2273 this = self.sql(expression, "this") 2274 kind = self.sql(expression, "kind") 2275 expr = self.sql(expression, "expression") 2276 return f"{this} ({kind} => {expr})" 2277 2278 def table_parts(self, expression: exp.Table) -> str: 2279 return ".".join( 2280 self.sql(part) 2281 for part in ( 2282 expression.args.get("catalog"), 2283 expression.args.get("db"), 2284 expression.args.get("this"), 2285 ) 2286 if part is not None 2287 ) 2288 2289 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2290 table = self.table_parts(expression) 2291 only = "ONLY " if expression.args.get("only") else "" 2292 partition = self.sql(expression, "partition") 2293 partition = f" {partition}" if partition else "" 2294 version = self.sql(expression, "version") 2295 version = f" {version}" if version else "" 2296 alias = self.sql(expression, "alias") 2297 alias = f"{sep}{alias}" if alias else "" 2298 2299 sample = self.sql(expression, "sample") 2300 post_alias = "" 2301 pre_alias = "" 2302 2303 if self.dialect.ALIAS_POST_TABLESAMPLE: 2304 pre_alias = sample 2305 else: 2306 post_alias = sample 2307 2308 if self.dialect.ALIAS_POST_VERSION: 2309 pre_alias = f"{pre_alias}{version}" 2310 else: 2311 post_alias = f"{post_alias}{version}" 2312 2313 hints = self.expressions(expression, key="hints", sep=" ") 2314 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2315 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2316 joins = self.indent( 2317 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2318 ) 2319 laterals = self.expressions(expression, key="laterals", sep="") 2320 2321 file_format = self.sql(expression, "format") 2322 if file_format: 2323 pattern = self.sql(expression, "pattern") 2324 pattern = f", PATTERN => {pattern}" if pattern else "" 2325 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2326 2327 ordinality = expression.args.get("ordinality") or "" 2328 if ordinality: 2329 ordinality = f" WITH ORDINALITY{alias}" 2330 alias = "" 2331 2332 when = self.sql(expression, "when") 2333 if when: 2334 table = f"{table} {when}" 2335 2336 changes = self.sql(expression, "changes") 2337 changes = f" {changes}" if changes else "" 2338 2339 rows_from = self.expressions(expression, key="rows_from") 2340 if rows_from: 2341 table = f"ROWS FROM {self.wrap(rows_from)}" 2342 2343 indexed = expression.args.get("indexed") 2344 if indexed is not None: 2345 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2346 else: 2347 indexed = "" 2348 2349 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2350 2351 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2352 table = self.func("TABLE", expression.this) 2353 alias = self.sql(expression, "alias") 2354 alias = f" AS {alias}" if alias else "" 2355 sample = self.sql(expression, "sample") 2356 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2357 joins = self.indent( 2358 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2359 ) 2360 return f"{table}{alias}{pivots}{sample}{joins}" 2361 2362 def tablesample_sql( 2363 self, 2364 expression: exp.TableSample, 2365 tablesample_keyword: str | None = None, 2366 ) -> str: 2367 method = self.sql(expression, "method") 2368 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2369 numerator = self.sql(expression, "bucket_numerator") 2370 denominator = self.sql(expression, "bucket_denominator") 2371 field = self.sql(expression, "bucket_field") 2372 field = f" ON {field}" if field else "" 2373 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2374 seed = self.sql(expression, "seed") 2375 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2376 2377 size = self.sql(expression, "size") 2378 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2379 size = f"{size} ROWS" 2380 2381 percent = self.sql(expression, "percent") 2382 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2383 percent = f"{percent} PERCENT" 2384 2385 expr = f"{bucket}{percent}{size}" 2386 if self.TABLESAMPLE_REQUIRES_PARENS: 2387 expr = f"({expr})" 2388 2389 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2390 2391 def pivot_sql(self, expression: exp.Pivot) -> str: 2392 expressions = self.expressions(expression, flat=True) 2393 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2394 2395 group = self.sql(expression, "group") 2396 2397 if expression.this: 2398 this = self.sql(expression, "this") 2399 if not expressions: 2400 sql = f"UNPIVOT {this}" 2401 else: 2402 on = f"{self.seg('ON')} {expressions}" 2403 into = self.sql(expression, "into") 2404 into = f"{self.seg('INTO')} {into}" if into else "" 2405 using = self.expressions(expression, key="using", flat=True) 2406 using = f"{self.seg('USING')} {using}" if using else "" 2407 sql = f"{direction} {this}{on}{into}{using}{group}" 2408 return self.prepend_ctes(expression, sql) 2409 2410 alias = self.sql(expression, "alias") 2411 alias = f" AS {alias}" if alias else "" 2412 2413 fields = self.expressions( 2414 expression, 2415 "fields", 2416 sep=" ", 2417 dynamic=True, 2418 new_line=True, 2419 skip_first=True, 2420 skip_last=True, 2421 ) 2422 2423 include_nulls = expression.args.get("include_nulls") 2424 if include_nulls is not None: 2425 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2426 else: 2427 nulls = "" 2428 2429 default_on_null = self.sql(expression, "default_on_null") 2430 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2431 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2432 return self.prepend_ctes(expression, sql) 2433 2434 def version_sql(self, expression: exp.Version) -> str: 2435 this = f"FOR {expression.name}" 2436 kind = expression.text("kind") 2437 expr = self.sql(expression, "expression") 2438 return f"{this} {kind} {expr}" 2439 2440 def tuple_sql(self, expression: exp.Tuple) -> str: 2441 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2442 2443 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2444 """ 2445 Returns (join_sql, from_sql) for UPDATE statements. 2446 - join_sql: placed after UPDATE table, before SET 2447 - from_sql: placed after SET clause (standard position) 2448 Dialects like MySQL need to convert FROM to JOIN syntax. 2449 """ 2450 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2451 return ("", self.sql(expression, "from_")) 2452 2453 # Qualify unqualified columns in SET clause with the target table 2454 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2455 target_table = expression.this 2456 if isinstance(target_table, exp.Table): 2457 target_name = exp.to_identifier(target_table.alias_or_name) 2458 for eq in expression.expressions: 2459 col = eq.this 2460 if isinstance(col, exp.Column) and not col.table: 2461 col.set("table", target_name) 2462 2463 table = from_expr.this 2464 if nested_joins := table.args.get("joins", []): 2465 table.set("joins", None) 2466 2467 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2468 for nested in nested_joins: 2469 if not nested.args.get("on") and not nested.args.get("using"): 2470 nested.set("on", exp.true()) 2471 join_sql += self.sql(nested) 2472 2473 return (join_sql, "") 2474 2475 def update_sql(self, expression: exp.Update) -> str: 2476 hint = self.sql(expression, "hint") 2477 this = self.sql(expression, "this") 2478 join_sql, from_sql = self._update_from_joins_sql(expression) 2479 set_sql = self.expressions(expression, flat=True) 2480 where_sql = self.sql(expression, "where") 2481 returning = self.sql(expression, "returning") 2482 order = self.sql(expression, "order") 2483 limit = self.sql(expression, "limit") 2484 if self.RETURNING_END: 2485 expression_sql = f"{from_sql}{where_sql}{returning}" 2486 else: 2487 expression_sql = f"{returning}{from_sql}{where_sql}" 2488 options = self.expressions(expression, key="options") 2489 options = f" OPTION({options})" if options else "" 2490 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2491 return self.prepend_ctes(expression, sql) 2492 2493 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2494 values_as_table = values_as_table and self.VALUES_AS_TABLE 2495 2496 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2497 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2498 args = self.expressions(expression) 2499 alias = self.sql(expression, "alias") 2500 values = f"VALUES{self.seg('')}{args}" 2501 values = ( 2502 f"({values})" 2503 if self.WRAP_DERIVED_VALUES 2504 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2505 else values 2506 ) 2507 values = self.query_modifiers(expression, values) 2508 return f"{values} AS {alias}" if alias else values 2509 2510 # Converts `VALUES...` expression into a series of select unions. 2511 alias_node = expression.args.get("alias") 2512 column_names = alias_node and alias_node.columns 2513 2514 selects: list[exp.Query] = [] 2515 2516 for i, tup in enumerate(expression.expressions): 2517 row = tup.expressions 2518 2519 if i == 0 and column_names: 2520 row = [ 2521 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2522 ] 2523 2524 selects.append(exp.Select(expressions=row)) 2525 2526 if self.pretty: 2527 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2528 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2529 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2530 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2531 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2532 2533 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2534 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2535 return f"({unions}){alias}" 2536 2537 def var_sql(self, expression: exp.Var) -> str: 2538 return self.sql(expression, "this") 2539 2540 @unsupported_args("expressions") 2541 def into_sql(self, expression: exp.Into) -> str: 2542 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2543 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2544 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2545 2546 def from_sql(self, expression: exp.From) -> str: 2547 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2548 2549 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2550 grouping_sets = self.expressions(expression, indent=False) 2551 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2552 2553 def rollup_sql(self, expression: exp.Rollup) -> str: 2554 expressions = self.expressions(expression, indent=False) 2555 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2556 2557 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2558 this = self.sql(expression, "this") 2559 2560 columns = self.expressions(expression, flat=True) 2561 2562 from_sql = self.sql(expression, "from_index") 2563 from_sql = f" FROM {from_sql}" if from_sql else "" 2564 2565 properties = expression.args.get("properties") 2566 properties_sql = ( 2567 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2568 ) 2569 2570 return f"{this}({columns}){from_sql}{properties_sql}" 2571 2572 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2573 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2574 2575 def cube_sql(self, expression: exp.Cube) -> str: 2576 expressions = self.expressions(expression, indent=False) 2577 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2578 2579 def group_sql(self, expression: exp.Group) -> str: 2580 group_by_all = expression.args.get("all") 2581 if group_by_all is True: 2582 modifier = " ALL" 2583 elif group_by_all is False: 2584 modifier = " DISTINCT" 2585 else: 2586 modifier = "" 2587 2588 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2589 2590 grouping_sets = self.expressions(expression, key="grouping_sets") 2591 cube = self.expressions(expression, key="cube") 2592 rollup = self.expressions(expression, key="rollup") 2593 2594 groupings = csv( 2595 self.seg(grouping_sets) if grouping_sets else "", 2596 self.seg(cube) if cube else "", 2597 self.seg(rollup) if rollup else "", 2598 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2599 sep=self.GROUPINGS_SEP, 2600 ) 2601 2602 if ( 2603 expression.expressions 2604 and groupings 2605 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2606 ): 2607 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2608 2609 return f"{group_by}{groupings}" 2610 2611 def having_sql(self, expression: exp.Having) -> str: 2612 this = self.indent(self.sql(expression, "this")) 2613 return f"{self.seg('HAVING')}{self.sep()}{this}" 2614 2615 def connect_sql(self, expression: exp.Connect) -> str: 2616 start = self.sql(expression, "start") 2617 start = self.seg(f"START WITH {start}") if start else "" 2618 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2619 connect = self.sql(expression, "connect") 2620 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2621 return start + connect 2622 2623 def prior_sql(self, expression: exp.Prior) -> str: 2624 return f"PRIOR {self.sql(expression, 'this')}" 2625 2626 def join_sql(self, expression: exp.Join) -> str: 2627 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2628 side = None 2629 else: 2630 side = expression.side 2631 2632 op_sql = " ".join( 2633 op 2634 for op in ( 2635 expression.method, 2636 "GLOBAL" if expression.args.get("global_") else None, 2637 side, 2638 expression.kind, 2639 expression.hint if self.JOIN_HINTS else None, 2640 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2641 ) 2642 if op 2643 ) 2644 match_cond = self.sql(expression, "match_condition") 2645 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2646 on_sql = self.sql(expression, "on") 2647 using = expression.args.get("using") 2648 2649 if not on_sql and using: 2650 on_sql = csv(*(self.sql(column) for column in using)) 2651 2652 this = expression.this 2653 this_sql = self.sql(this) 2654 2655 exprs = self.expressions(expression) 2656 if exprs: 2657 this_sql = f"{this_sql},{self.seg(exprs)}" 2658 2659 if on_sql: 2660 on_sql = self.indent(on_sql, skip_first=True) 2661 space = self.seg(" " * self.pad) if self.pretty else " " 2662 if using: 2663 on_sql = f"{space}USING ({on_sql})" 2664 else: 2665 on_sql = f"{space}ON {on_sql}" 2666 elif not op_sql: 2667 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2668 return f" {this_sql}" 2669 2670 return f", {this_sql}" 2671 2672 if op_sql != "STRAIGHT_JOIN": 2673 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2674 2675 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2676 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2677 2678 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2679 args = self.expressions(expression, flat=True) 2680 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2681 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2682 2683 def lateral_op(self, expression: exp.Lateral) -> str: 2684 cross_apply = expression.args.get("cross_apply") 2685 2686 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2687 if cross_apply is True: 2688 op = "INNER JOIN " 2689 elif cross_apply is False: 2690 op = "LEFT JOIN " 2691 else: 2692 op = "" 2693 2694 return f"{op}LATERAL" 2695 2696 def lateral_sql(self, expression: exp.Lateral) -> str: 2697 this = self.sql(expression, "this") 2698 2699 if expression.args.get("view"): 2700 alias = expression.args["alias"] 2701 columns = self.expressions(alias, key="columns", flat=True) 2702 table = f" {alias.name}" if alias.name else "" 2703 columns = f" AS {columns}" if columns else "" 2704 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2705 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2706 2707 alias = self.sql(expression, "alias") 2708 alias = f" AS {alias}" if alias else "" 2709 2710 ordinality = expression.args.get("ordinality") or "" 2711 if ordinality: 2712 ordinality = f" WITH ORDINALITY{alias}" 2713 alias = "" 2714 2715 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2716 2717 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2718 this = self.sql(expression, "this") 2719 2720 args = [ 2721 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2722 for e in (expression.args.get(k) for k in ("offset", "expression")) 2723 if e 2724 ] 2725 2726 args_sql = ", ".join(self.sql(e) for e in args) 2727 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2728 expressions = self.expressions(expression, flat=True) 2729 limit_options = self.sql(expression, "limit_options") 2730 expressions = f" BY {expressions}" if expressions else "" 2731 2732 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2733 2734 def offset_sql(self, expression: exp.Offset) -> str: 2735 this = self.sql(expression, "this") 2736 value = expression.expression 2737 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2738 expressions = self.expressions(expression, flat=True) 2739 expressions = f" BY {expressions}" if expressions else "" 2740 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2741 2742 def setitem_sql(self, expression: exp.SetItem) -> str: 2743 kind = self.sql(expression, "kind") 2744 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2745 kind = "" 2746 else: 2747 kind = f"{kind} " if kind else "" 2748 this = self.sql(expression, "this") 2749 expressions = self.expressions(expression) 2750 collate = self.sql(expression, "collate") 2751 collate = f" COLLATE {collate}" if collate else "" 2752 global_ = "GLOBAL " if expression.args.get("global_") else "" 2753 return f"{global_}{kind}{this}{expressions}{collate}" 2754 2755 def set_sql(self, expression: exp.Set) -> str: 2756 expressions = f" {self.expressions(expression, flat=True)}" 2757 tag = " TAG" if expression.args.get("tag") else "" 2758 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2759 2760 def queryband_sql(self, expression: exp.QueryBand) -> str: 2761 this = self.sql(expression, "this") 2762 update = " UPDATE" if expression.args.get("update") else "" 2763 scope = self.sql(expression, "scope") 2764 scope = f" FOR {scope}" if scope else "" 2765 2766 return f"QUERY_BAND = {this}{update}{scope}" 2767 2768 def pragma_sql(self, expression: exp.Pragma) -> str: 2769 return f"PRAGMA {self.sql(expression, 'this')}" 2770 2771 def lock_sql(self, expression: exp.Lock) -> str: 2772 if not self.LOCKING_READS_SUPPORTED: 2773 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2774 return "" 2775 2776 update = expression.args["update"] 2777 key = expression.args.get("key") 2778 if update: 2779 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2780 else: 2781 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2782 expressions = self.expressions(expression, flat=True) 2783 expressions = f" OF {expressions}" if expressions else "" 2784 wait = expression.args.get("wait") 2785 2786 if wait is not None: 2787 if isinstance(wait, exp.Literal): 2788 wait = f" WAIT {self.sql(wait)}" 2789 else: 2790 wait = " NOWAIT" if wait else " SKIP LOCKED" 2791 2792 return f"{lock_type}{expressions}{wait or ''}" 2793 2794 def literal_sql(self, expression: exp.Literal) -> str: 2795 text = expression.this or "" 2796 if expression.is_string: 2797 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2798 return text 2799 2800 def escape_str( 2801 self, 2802 text: str, 2803 escape_backslash: bool = True, 2804 delimiter: str | None = None, 2805 escaped_delimiter: str | None = None, 2806 is_byte_string: bool = False, 2807 ) -> str: 2808 if is_byte_string: 2809 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2810 else: 2811 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2812 2813 if supports_escape_sequences: 2814 text = "".join( 2815 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2816 for ch in text 2817 ) 2818 2819 delimiter = delimiter or self.dialect.QUOTE_END 2820 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2821 2822 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2823 2824 def loaddata_sql(self, expression: exp.LoadData) -> str: 2825 is_overwrite = expression.args.get("overwrite") 2826 overwrite = " OVERWRITE" if is_overwrite else "" 2827 this = self.sql(expression, "this") 2828 2829 files = expression.args.get("files") 2830 if files: 2831 files_sql = self.expressions(files, flat=True) 2832 files_sql = f"FILES{self.wrap(files_sql)}" 2833 this = f" {this}" if is_overwrite else f" INTO TABLE {this}" 2834 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2835 2836 local = " LOCAL" if expression.args.get("local") else "" 2837 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2838 this = f" INTO TABLE {this}" 2839 partition = self.sql(expression, "partition") 2840 partition = f" {partition}" if partition else "" 2841 input_format = self.sql(expression, "input_format") 2842 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2843 serde = self.sql(expression, "serde") 2844 serde = f" SERDE {serde}" if serde else "" 2845 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2846 2847 def null_sql(self, *_) -> str: 2848 return "NULL" 2849 2850 def boolean_sql(self, expression: exp.Boolean) -> str: 2851 return "TRUE" if expression.this else "FALSE" 2852 2853 def booland_sql(self, expression: exp.Booland) -> str: 2854 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2855 2856 def boolor_sql(self, expression: exp.Boolor) -> str: 2857 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2858 2859 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2860 this = self.sql(expression, "this") 2861 this = f"{this} " if this else this 2862 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2863 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 2864 2865 def withfill_sql(self, expression: exp.WithFill) -> str: 2866 from_sql = self.sql(expression, "from_") 2867 from_sql = f" FROM {from_sql}" if from_sql else "" 2868 to_sql = self.sql(expression, "to") 2869 to_sql = f" TO {to_sql}" if to_sql else "" 2870 step_sql = self.sql(expression, "step") 2871 step_sql = f" STEP {step_sql}" if step_sql else "" 2872 interpolated_values = [ 2873 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2874 if isinstance(e, exp.Alias) 2875 else self.sql(e, "this") 2876 for e in expression.args.get("interpolate") or [] 2877 ] 2878 interpolate = ( 2879 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2880 ) 2881 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2882 2883 def cluster_sql(self, expression: exp.Cluster) -> str: 2884 return self.op_expressions("CLUSTER BY", expression) 2885 2886 def distribute_sql(self, expression: exp.Distribute) -> str: 2887 return self.op_expressions("DISTRIBUTE BY", expression) 2888 2889 def sort_sql(self, expression: exp.Sort) -> str: 2890 return self.op_expressions("SORT BY", expression) 2891 2892 def ordered_sql(self, expression: exp.Ordered) -> str: 2893 desc = expression.args.get("desc") 2894 asc = not desc 2895 2896 nulls_first = expression.args.get("nulls_first") 2897 nulls_last = not nulls_first 2898 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2899 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2900 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2901 2902 this = self.sql(expression, "this") 2903 2904 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2905 nulls_sort_change = "" 2906 if nulls_first and ( 2907 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2908 ): 2909 nulls_sort_change = " NULLS FIRST" 2910 elif ( 2911 nulls_last 2912 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2913 and not nulls_are_last 2914 ): 2915 nulls_sort_change = " NULLS LAST" 2916 2917 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2918 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2919 window = expression.find_ancestor(exp.Window, exp.Select) 2920 2921 if isinstance(window, exp.Window): 2922 window_this = window.this 2923 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 2924 window_this = window_this.this 2925 spec = window.args.get("spec") 2926 else: 2927 window_this = None 2928 spec = None 2929 2930 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 2931 # without a spec or with a ROWS spec, but not with RANGE 2932 if not ( 2933 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 2934 and (not spec or spec.text("kind").upper() == "ROWS") 2935 ): 2936 if window_this and spec: 2937 self.unsupported( 2938 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 2939 ) 2940 nulls_sort_change = "" 2941 elif self.NULL_ORDERING_SUPPORTED is False and ( 2942 (asc and nulls_sort_change == " NULLS LAST") 2943 or (desc and nulls_sort_change == " NULLS FIRST") 2944 ): 2945 # BigQuery does not allow these ordering/nulls combinations when used under 2946 # an aggregation func or under a window containing one 2947 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2948 2949 if isinstance(ancestor, exp.Window): 2950 ancestor = ancestor.this 2951 if isinstance(ancestor, exp.AggFunc): 2952 self.unsupported( 2953 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 2954 ) 2955 nulls_sort_change = "" 2956 elif self.NULL_ORDERING_SUPPORTED is None: 2957 if expression.this.is_int: 2958 self.unsupported( 2959 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2960 ) 2961 elif not isinstance(expression.this, exp.Rand): 2962 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2963 this = ( 2964 f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2965 ) 2966 nulls_sort_change = "" 2967 2968 with_fill = self.sql(expression, "with_fill") 2969 with_fill = f" {with_fill}" if with_fill else "" 2970 2971 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2972 2973 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2974 window_frame = self.sql(expression, "window_frame") 2975 window_frame = f"{window_frame} " if window_frame else "" 2976 2977 this = self.sql(expression, "this") 2978 2979 return f"{window_frame}{this}" 2980 2981 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2982 partition = self.partition_by_sql(expression) 2983 order = self.sql(expression, "order") 2984 measures = self.expressions(expression, key="measures") 2985 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2986 rows = self.sql(expression, "rows") 2987 rows = self.seg(rows) if rows else "" 2988 after = self.sql(expression, "after") 2989 after = self.seg(after) if after else "" 2990 pattern = self.sql(expression, "pattern") 2991 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2992 definition_sqls = [ 2993 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2994 for definition in expression.args.get("define", []) 2995 ] 2996 definitions = self.expressions(sqls=definition_sqls) 2997 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2998 body = "".join( 2999 ( 3000 partition, 3001 order, 3002 measures, 3003 rows, 3004 after, 3005 pattern, 3006 define, 3007 ) 3008 ) 3009 alias = self.sql(expression, "alias") 3010 alias = f" {alias}" if alias else "" 3011 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3012 3013 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3014 limit = expression.args.get("limit") 3015 3016 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3017 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3018 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3019 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3020 3021 return csv( 3022 *sqls, 3023 *[self.sql(join) for join in expression.args.get("joins") or []], 3024 self.sql(expression, "match"), 3025 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3026 self.sql(expression, "prewhere"), 3027 self.sql(expression, "where"), 3028 self.sql(expression, "connect"), 3029 self.sql(expression, "group"), 3030 self.sql(expression, "having"), 3031 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3032 self.sql(expression, "order"), 3033 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3034 *self.after_limit_modifiers(expression), 3035 self.options_modifier(expression), 3036 self.for_modifiers(expression), 3037 sep="", 3038 ) 3039 3040 def options_modifier(self, expression: exp.Expr) -> str: 3041 options = self.expressions(expression, key="options") 3042 return f" {options}" if options else "" 3043 3044 def for_modifiers(self, expression: exp.Expr) -> str: 3045 for_modifiers = self.expressions(expression, key="for_") 3046 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 3047 3048 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3049 self.unsupported("Unsupported query option.") 3050 return "" 3051 3052 def offset_limit_modifiers( 3053 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3054 ) -> list[str]: 3055 return [ 3056 self.sql(expression, "offset") if fetch else self.sql(limit), 3057 self.sql(limit) if fetch else self.sql(expression, "offset"), 3058 ] 3059 3060 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3061 locks = self.expressions(expression, key="locks", sep=" ") 3062 locks = f" {locks}" if locks else "" 3063 return [locks, self.sql(expression, "sample")] 3064 3065 def select_sql(self, expression: exp.Select) -> str: 3066 into = expression.args.get("into") 3067 if not self.SUPPORTS_SELECT_INTO and into: 3068 into.pop() 3069 3070 hint = self.sql(expression, "hint") 3071 distinct = self.sql(expression, "distinct") 3072 distinct = f" {distinct}" if distinct else "" 3073 kind = self.sql(expression, "kind") 3074 3075 limit = expression.args.get("limit") 3076 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3077 top = self.limit_sql(limit, top=True) 3078 limit.pop() 3079 else: 3080 top = "" 3081 3082 expressions = self.expressions(expression) 3083 3084 if kind: 3085 if kind in self.SELECT_KINDS: 3086 kind = f" AS {kind}" 3087 else: 3088 if kind == "STRUCT": 3089 expressions = self.expressions( 3090 sqls=[ 3091 self.sql( 3092 exp.Struct( 3093 expressions=[ 3094 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3095 if isinstance(e, exp.Alias) 3096 else e 3097 for e in expression.expressions 3098 ] 3099 ) 3100 ) 3101 ] 3102 ) 3103 kind = "" 3104 3105 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3106 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3107 3108 exclude = expression.args.get("exclude") 3109 3110 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3111 exclude_sql = self.expressions(sqls=exclude, flat=True) 3112 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3113 3114 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3115 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3116 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3117 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3118 sql = self.query_modifiers( 3119 expression, 3120 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3121 self.sql(expression, "into", comment=False), 3122 self.sql(expression, "from_", comment=False), 3123 ) 3124 3125 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3126 if expression.args.get("with_"): 3127 sql = self.maybe_comment(sql, expression) 3128 expression.pop_comments() 3129 3130 sql = self.prepend_ctes(expression, sql) 3131 3132 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3133 expression.set("exclude", None) 3134 subquery = expression.subquery(copy=False) 3135 star = exp.Star(except_=exclude) 3136 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3137 3138 if not self.SUPPORTS_SELECT_INTO and into: 3139 if into.args.get("temporary"): 3140 table_kind = " TEMPORARY" 3141 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3142 table_kind = " UNLOGGED" 3143 else: 3144 table_kind = "" 3145 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3146 3147 return sql 3148 3149 def schema_sql(self, expression: exp.Schema) -> str: 3150 this = self.sql(expression, "this") 3151 sql = self.schema_columns_sql(expression) 3152 return f"{this} {sql}" if this and sql else this or sql 3153 3154 def schema_columns_sql(self, expression: exp.Expr) -> str: 3155 if expression.expressions: 3156 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3157 return "" 3158 3159 def star_sql(self, expression: exp.Star) -> str: 3160 except_ = self.expressions(expression, key="except_", flat=True) 3161 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3162 replace = self.expressions(expression, key="replace", flat=True) 3163 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3164 rename = self.expressions(expression, key="rename", flat=True) 3165 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3166 return f"*{except_}{replace}{rename}" 3167 3168 def parameter_sql(self, expression: exp.Parameter) -> str: 3169 this = self.sql(expression, "this") 3170 return f"{self.PARAMETER_TOKEN}{this}" 3171 3172 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3173 this = self.sql(expression, "this") 3174 kind = expression.text("kind") 3175 if kind: 3176 kind = f"{kind}." 3177 return f"@@{kind}{this}" 3178 3179 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3180 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3181 3182 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3183 alias = self.sql(expression, "alias") 3184 alias = f"{sep}{alias}" if alias else "" 3185 sample = self.sql(expression, "sample") 3186 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3187 alias = f"{sample}{alias}" 3188 3189 # Set to None so it's not generated again by self.query_modifiers() 3190 expression.set("sample", None) 3191 3192 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3193 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3194 return self.prepend_ctes(expression, sql) 3195 3196 def qualify_sql(self, expression: exp.Qualify) -> str: 3197 this = self.indent(self.sql(expression, "this")) 3198 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3199 3200 def unnest_sql(self, expression: exp.Unnest) -> str: 3201 args = self.expressions(expression, flat=True) 3202 3203 alias = expression.args.get("alias") 3204 offset = expression.args.get("offset") 3205 3206 if self.UNNEST_WITH_ORDINALITY: 3207 if alias and isinstance(offset, exp.Expr): 3208 alias.append("columns", offset) 3209 expression.set("offset", None) 3210 3211 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3212 columns = alias.columns 3213 alias = self.sql(columns[0]) if columns else "" 3214 else: 3215 alias = self.sql(alias) 3216 3217 alias = f" AS {alias}" if alias else alias 3218 if self.UNNEST_WITH_ORDINALITY: 3219 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3220 else: 3221 if isinstance(offset, exp.Expr): 3222 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3223 elif offset: 3224 suffix = f"{alias} WITH OFFSET" 3225 else: 3226 suffix = alias 3227 3228 return f"UNNEST({args}){suffix}" 3229 3230 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3231 return "" 3232 3233 def where_sql(self, expression: exp.Where) -> str: 3234 this = self.indent(self.sql(expression, "this")) 3235 return f"{self.seg('WHERE')}{self.sep()}{this}" 3236 3237 def window_sql(self, expression: exp.Window) -> str: 3238 this = self.sql(expression, "this") 3239 partition = self.partition_by_sql(expression) 3240 order = expression.args.get("order") 3241 order = self.order_sql(order, flat=True) if order else "" 3242 spec = self.sql(expression, "spec") 3243 alias = self.sql(expression, "alias") 3244 over = self.sql(expression, "over") or "OVER" 3245 3246 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3247 3248 first = expression.args.get("first") 3249 if first is None: 3250 first = "" 3251 else: 3252 first = "FIRST" if first else "LAST" 3253 3254 if not partition and not order and not spec and alias: 3255 return f"{this} {alias}" 3256 3257 args = self.format_args( 3258 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3259 ) 3260 return f"{this} ({args})" 3261 3262 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3263 partition = self.expressions(expression, key="partition_by", flat=True) 3264 return f"PARTITION BY {partition}" if partition else "" 3265 3266 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3267 kind = self.sql(expression, "kind") 3268 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3269 end = ( 3270 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3271 or "CURRENT ROW" 3272 ) 3273 3274 window_spec = f"{kind} BETWEEN {start} AND {end}" 3275 3276 exclude = self.sql(expression, "exclude") 3277 if exclude: 3278 if self.SUPPORTS_WINDOW_EXCLUDE: 3279 window_spec += f" EXCLUDE {exclude}" 3280 else: 3281 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3282 3283 return window_spec 3284 3285 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3286 this = self.sql(expression, "this") 3287 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3288 return f"{this} WITHIN GROUP ({expression_sql})" 3289 3290 def between_sql(self, expression: exp.Between) -> str: 3291 this = self.sql(expression, "this") 3292 low = self.sql(expression, "low") 3293 high = self.sql(expression, "high") 3294 symmetric = expression.args.get("symmetric") 3295 3296 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3297 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3298 3299 flag = ( 3300 " SYMMETRIC" 3301 if symmetric 3302 else " ASYMMETRIC" 3303 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3304 else "" # silently drop ASYMMETRIC – semantics identical 3305 ) 3306 return f"{this} BETWEEN{flag} {low} AND {high}" 3307 3308 def bracket_offset_expressions( 3309 self, expression: exp.Bracket, index_offset: int | None = None 3310 ) -> list[exp.Expr]: 3311 if expression.args.get("json_access"): 3312 return expression.expressions 3313 3314 return apply_index_offset( 3315 expression.this, 3316 expression.expressions, 3317 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3318 dialect=self.dialect, 3319 ) 3320 3321 def bracket_sql(self, expression: exp.Bracket) -> str: 3322 expressions = self.bracket_offset_expressions(expression) 3323 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3324 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3325 3326 def all_sql(self, expression: exp.All) -> str: 3327 this = self.sql(expression, "this") 3328 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3329 this = self.wrap(this) 3330 return f"ALL {this}" 3331 3332 def any_sql(self, expression: exp.Any) -> str: 3333 this = self.sql(expression, "this") 3334 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3335 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3336 this = self.wrap(this) 3337 return f"ANY{this}" 3338 return f"ANY {this}" 3339 3340 def exists_sql(self, expression: exp.Exists) -> str: 3341 return f"EXISTS{self.wrap(expression)}" 3342 3343 def case_sql(self, expression: exp.Case) -> str: 3344 this = self.sql(expression, "this") 3345 statements = [f"CASE {this}" if this else "CASE"] 3346 3347 for e in expression.args["ifs"]: 3348 statements.append(f"WHEN {self.sql(e, 'this')}") 3349 statements.append(f"THEN {self.sql(e, 'true')}") 3350 3351 default = self.sql(expression, "default") 3352 3353 if default: 3354 statements.append(f"ELSE {default}") 3355 3356 statements.append("END") 3357 3358 if self.pretty and self.too_wide(statements): 3359 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3360 3361 return " ".join(statements) 3362 3363 def constraint_sql(self, expression: exp.Constraint) -> str: 3364 this = self.sql(expression, "this") 3365 expressions = self.expressions(expression, flat=True) 3366 return f"CONSTRAINT {this} {expressions}" 3367 3368 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3369 order = expression.args.get("order") 3370 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3371 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3372 3373 def extract_sql(self, expression: exp.Extract) -> str: 3374 import sqlglot.dialects.dialect 3375 3376 this = ( 3377 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3378 if self.NORMALIZE_EXTRACT_DATE_PARTS 3379 else expression.this 3380 ) 3381 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3382 expression_sql = self.sql(expression, "expression") 3383 3384 return f"EXTRACT({this_sql} FROM {expression_sql})" 3385 3386 def trim_sql(self, expression: exp.Trim) -> str: 3387 trim_type = self.sql(expression, "position") 3388 3389 if trim_type == "LEADING": 3390 func_name = "LTRIM" 3391 elif trim_type == "TRAILING": 3392 func_name = "RTRIM" 3393 else: 3394 func_name = "TRIM" 3395 3396 return self.func(func_name, expression.this, expression.expression) 3397 3398 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3399 args = expression.expressions 3400 if isinstance(expression, exp.ConcatWs): 3401 args = args[1:] # Skip the delimiter 3402 3403 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3404 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3405 3406 concat_coalesce = ( 3407 self.dialect.CONCAT_WS_COALESCE 3408 if isinstance(expression, exp.ConcatWs) 3409 else self.dialect.CONCAT_COALESCE 3410 ) 3411 3412 if not concat_coalesce and expression.args.get("coalesce"): 3413 3414 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3415 if not e.type: 3416 import sqlglot.optimizer.annotate_types 3417 3418 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3419 3420 if e.is_string or e.is_type(exp.DType.ARRAY): 3421 return e 3422 3423 return exp.func("coalesce", e, exp.Literal.string("")) 3424 3425 args = [_wrap_with_coalesce(e) for e in args] 3426 3427 return args 3428 3429 def concat_sql(self, expression: exp.Concat) -> str: 3430 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3431 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3432 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3433 # instead of coalescing them to empty string. 3434 import sqlglot.dialects.dialect 3435 3436 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3437 3438 expressions = self.convert_concat_args(expression) 3439 3440 # Some dialects don't allow a single-argument CONCAT call 3441 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3442 return self.sql(expressions[0]) 3443 3444 return self.func("CONCAT", *expressions) 3445 3446 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3447 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3448 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3449 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3450 all_args = expression.expressions 3451 expression.set("coalesce", True) 3452 return self.sql( 3453 exp.case() 3454 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3455 .else_(expression) 3456 ) 3457 3458 return self.func( 3459 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3460 ) 3461 3462 def check_sql(self, expression: exp.Check) -> str: 3463 this = self.sql(expression, key="this") 3464 return f"CHECK ({this})" 3465 3466 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3467 expressions = self.expressions(expression, flat=True) 3468 expressions = f" ({expressions})" if expressions else "" 3469 reference = self.sql(expression, "reference") 3470 reference = f" {reference}" if reference else "" 3471 delete = self.sql(expression, "delete") 3472 delete = f" ON DELETE {delete}" if delete else "" 3473 update = self.sql(expression, "update") 3474 update = f" ON UPDATE {update}" if update else "" 3475 options = self.expressions(expression, key="options", flat=True, sep=" ") 3476 options = f" {options}" if options else "" 3477 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3478 3479 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3480 this = self.sql(expression, "this") 3481 this = f" {this}" if this else "" 3482 expressions = self.expressions(expression, flat=True) 3483 include = self.sql(expression, "include") 3484 options = self.expressions(expression, key="options", flat=True, sep=" ") 3485 options = f" {options}" if options else "" 3486 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3487 3488 def if_sql(self, expression: exp.If) -> str: 3489 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3490 3491 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3492 if self.MATCH_AGAINST_TABLE_PREFIX: 3493 expressions = [] 3494 for expr in expression.expressions: 3495 if isinstance(expr, exp.Table): 3496 expressions.append(f"TABLE {self.sql(expr)}") 3497 else: 3498 expressions.append(expr) 3499 else: 3500 expressions = expression.expressions 3501 3502 modifier = expression.args.get("modifier") 3503 modifier = f" {modifier}" if modifier else "" 3504 return ( 3505 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3506 ) 3507 3508 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3509 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3510 3511 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3512 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3513 3514 if expression.args.get("escape"): 3515 path = self.escape_str(path) 3516 3517 if self.QUOTE_JSON_PATH: 3518 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3519 3520 return path 3521 3522 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3523 if isinstance(expression, exp.JSONPathPart): 3524 transform = self.TRANSFORMS.get(expression.__class__) 3525 if not callable(transform): 3526 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3527 return "" 3528 3529 return transform(self, expression) 3530 3531 if isinstance(expression, int): 3532 return str(expression) 3533 3534 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3535 escaped = expression.replace("'", "\\'") 3536 escaped = f"\\'{expression}\\'" 3537 else: 3538 escaped = expression.replace('"', '\\"') 3539 escaped = f'"{escaped}"' 3540 3541 return escaped 3542 3543 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3544 return f"{self.sql(expression, 'this')} FORMAT JSON" 3545 3546 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3547 # Output the Teradata column FORMAT override. 3548 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3549 this = self.sql(expression, "this") 3550 fmt = self.sql(expression, "format") 3551 return f"{this} (FORMAT {fmt})" 3552 3553 def _jsonobject_sql( 3554 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3555 ) -> str: 3556 null_handling = expression.args.get("null_handling") 3557 null_handling = f" {null_handling}" if null_handling else "" 3558 3559 unique_keys = expression.args.get("unique_keys") 3560 if unique_keys is not None: 3561 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3562 else: 3563 unique_keys = "" 3564 3565 return_type = self.sql(expression, "return_type") 3566 return_type = f" RETURNING {return_type}" if return_type else "" 3567 encoding = self.sql(expression, "encoding") 3568 encoding = f" ENCODING {encoding}" if encoding else "" 3569 3570 if not name: 3571 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3572 3573 return self.func( 3574 name, 3575 *expression.expressions, 3576 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3577 ) 3578 3579 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3580 null_handling = expression.args.get("null_handling") 3581 null_handling = f" {null_handling}" if null_handling else "" 3582 return_type = self.sql(expression, "return_type") 3583 return_type = f" RETURNING {return_type}" if return_type else "" 3584 strict = " STRICT" if expression.args.get("strict") else "" 3585 return self.func( 3586 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3587 ) 3588 3589 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3590 this = self.sql(expression, "this") 3591 order = self.sql(expression, "order") 3592 null_handling = expression.args.get("null_handling") 3593 null_handling = f" {null_handling}" if null_handling else "" 3594 return_type = self.sql(expression, "return_type") 3595 return_type = f" RETURNING {return_type}" if return_type else "" 3596 strict = " STRICT" if expression.args.get("strict") else "" 3597 return self.func( 3598 "JSON_ARRAYAGG", 3599 this, 3600 suffix=f"{order}{null_handling}{return_type}{strict})", 3601 ) 3602 3603 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3604 path = self.sql(expression, "path") 3605 path = f" PATH {path}" if path else "" 3606 nested_schema = self.sql(expression, "nested_schema") 3607 3608 if nested_schema: 3609 return f"NESTED{path} {nested_schema}" 3610 3611 this = self.sql(expression, "this") 3612 kind = self.sql(expression, "kind") 3613 kind = f" {kind}" if kind else "" 3614 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3615 3616 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3617 return f"{this}{kind}{format_json}{path}{ordinality}" 3618 3619 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3620 return self.func("COLUMNS", *expression.expressions) 3621 3622 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3623 this = self.sql(expression, "this") 3624 path = self.sql(expression, "path") 3625 path = f", {path}" if path else "" 3626 error_handling = expression.args.get("error_handling") 3627 error_handling = f" {error_handling}" if error_handling else "" 3628 empty_handling = expression.args.get("empty_handling") 3629 empty_handling = f" {empty_handling}" if empty_handling else "" 3630 schema = self.sql(expression, "schema") 3631 return self.func( 3632 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3633 ) 3634 3635 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3636 this = self.sql(expression, "this") 3637 kind = self.sql(expression, "kind") 3638 path = self.sql(expression, "path") 3639 path = f" {path}" if path else "" 3640 as_json = " AS JSON" if expression.args.get("as_json") else "" 3641 return f"{this} {kind}{path}{as_json}" 3642 3643 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3644 this = self.sql(expression, "this") 3645 path = self.sql(expression, "path") 3646 path = f", {path}" if path else "" 3647 expressions = self.expressions(expression) 3648 with_ = ( 3649 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3650 if expressions 3651 else "" 3652 ) 3653 return f"OPENJSON({this}{path}){with_}" 3654 3655 def in_sql(self, expression: exp.In) -> str: 3656 query = expression.args.get("query") 3657 unnest = expression.args.get("unnest") 3658 field = expression.args.get("field") 3659 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3660 3661 if query: 3662 in_sql = self.sql(query) 3663 elif unnest: 3664 in_sql = self.in_unnest_op(unnest) 3665 elif field: 3666 in_sql = self.sql(field) 3667 else: 3668 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3669 3670 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3671 3672 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3673 return f"(SELECT {self.sql(unnest)})" 3674 3675 def interval_sql(self, expression: exp.Interval) -> str: 3676 unit_expression = expression.args.get("unit") 3677 unit = self.sql(unit_expression) if unit_expression else "" 3678 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3679 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3680 unit = f" {unit}" if unit else "" 3681 3682 if self.SINGLE_STRING_INTERVAL: 3683 this = expression.this.name if expression.this else "" 3684 if this: 3685 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3686 return f"INTERVAL '{this}'{unit}" 3687 return f"INTERVAL '{this}{unit}'" 3688 return f"INTERVAL{unit}" 3689 3690 this = self.sql(expression, "this") 3691 if this: 3692 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3693 this = f" {this}" if unwrapped else f" ({this})" 3694 3695 return f"INTERVAL{this}{unit}" 3696 3697 def return_sql(self, expression: exp.Return) -> str: 3698 return f"RETURN {self.sql(expression, 'this')}" 3699 3700 def reference_sql(self, expression: exp.Reference) -> str: 3701 this = self.sql(expression, "this") 3702 expressions = self.expressions(expression, flat=True) 3703 expressions = f"({expressions})" if expressions else "" 3704 options = self.expressions(expression, key="options", flat=True, sep=" ") 3705 options = f" {options}" if options else "" 3706 return f"REFERENCES {this}{expressions}{options}" 3707 3708 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3709 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3710 parent = expression.parent 3711 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3712 3713 return self.func( 3714 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3715 ) 3716 3717 def paren_sql(self, expression: exp.Paren) -> str: 3718 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3719 return f"({sql}{self.seg(')', sep='')}" 3720 3721 def neg_sql(self, expression: exp.Neg) -> str: 3722 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3723 this_sql = self.sql(expression, "this") 3724 sep = " " if this_sql[0] == "-" else "" 3725 return f"-{sep}{this_sql}" 3726 3727 def not_sql(self, expression: exp.Not) -> str: 3728 return f"NOT {self.sql(expression, 'this')}" 3729 3730 def alias_sql(self, expression: exp.Alias) -> str: 3731 alias = self.sql(expression, "alias") 3732 alias = f" AS {alias}" if alias else "" 3733 return f"{self.sql(expression, 'this')}{alias}" 3734 3735 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3736 alias = expression.args["alias"] 3737 3738 parent = expression.parent 3739 pivot = parent and parent.parent 3740 3741 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3742 identifier_alias = isinstance(alias, exp.Identifier) 3743 literal_alias = isinstance(alias, exp.Literal) 3744 3745 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3746 alias.replace(exp.Literal.string(alias.output_name)) 3747 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3748 alias.replace(exp.to_identifier(alias.output_name)) 3749 3750 return self.alias_sql(expression) 3751 3752 def aliases_sql(self, expression: exp.Aliases) -> str: 3753 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3754 3755 def atindex_sql(self, expression: exp.AtIndex) -> str: 3756 this = self.sql(expression, "this") 3757 index = self.sql(expression, "expression") 3758 return f"{this} AT {index}" 3759 3760 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3761 this = self.sql(expression, "this") 3762 zone = self.sql(expression, "zone") 3763 return f"{this} AT TIME ZONE {zone}" 3764 3765 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3766 this = self.sql(expression, "this") 3767 zone = self.sql(expression, "zone") 3768 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3769 3770 def add_sql(self, expression: exp.Add) -> str: 3771 return self.binary(expression, "+") 3772 3773 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 3774 return self.connector_sql(expression, "AND", stack) 3775 3776 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 3777 return self.connector_sql(expression, "OR", stack) 3778 3779 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 3780 return self.connector_sql(expression, "XOR", stack) 3781 3782 def connector_sql( 3783 self, 3784 expression: exp.Connector, 3785 op: str, 3786 stack: list[str | exp.Expr] | None = None, 3787 ) -> str: 3788 if stack is not None: 3789 if expression.expressions: 3790 stack.append(self.expressions(expression, sep=f" {op} ")) 3791 else: 3792 stack.append(expression.right) 3793 if expression.comments and self.comments: 3794 for comment in expression.comments: 3795 if comment: 3796 op += f" /*{self.sanitize_comment(comment)}*/" 3797 stack.extend((op, expression.left)) 3798 return op 3799 3800 stack = [expression] 3801 sqls: list[str] = [] 3802 ops = set() 3803 3804 while stack: 3805 node = stack.pop() 3806 if isinstance(node, exp.Connector): 3807 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3808 else: 3809 sql = self.sql(node) 3810 if sqls and sqls[-1] in ops: 3811 sqls[-1] += f" {sql}" 3812 else: 3813 sqls.append(sql) 3814 3815 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3816 return sep.join(sqls) 3817 3818 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3819 return self.binary(expression, "&") 3820 3821 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3822 return self.binary(expression, "<<") 3823 3824 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3825 return f"~{self.sql(expression, 'this')}" 3826 3827 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3828 return self.binary(expression, "|") 3829 3830 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3831 return self.binary(expression, ">>") 3832 3833 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3834 return self.binary(expression, "^") 3835 3836 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 3837 format_sql = self.sql(expression, "format") 3838 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3839 to_sql = self.sql(expression, "to") 3840 to_sql = f" {to_sql}" if to_sql else "" 3841 action = self.sql(expression, "action") 3842 action = f" {action}" if action else "" 3843 default = self.sql(expression, "default") 3844 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3845 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3846 3847 # Base implementation that excludes safe, zone, and target_type metadata args 3848 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3849 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3850 3851 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3852 zone = self.sql(expression, "this") 3853 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3854 3855 def collate_sql(self, expression: exp.Collate) -> str: 3856 if self.COLLATE_IS_FUNC: 3857 return self.function_fallback_sql(expression) 3858 return self.binary(expression, "COLLATE") 3859 3860 def command_sql(self, expression: exp.Command) -> str: 3861 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3862 3863 def comment_sql(self, expression: exp.Comment) -> str: 3864 this = self.sql(expression, "this") 3865 kind = expression.args["kind"] 3866 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3867 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3868 expression_sql = self.sql(expression, "expression") 3869 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3870 3871 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3872 this = self.sql(expression, "this") 3873 delete = " DELETE" if expression.args.get("delete") else "" 3874 recompress = self.sql(expression, "recompress") 3875 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3876 to_disk = self.sql(expression, "to_disk") 3877 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3878 to_volume = self.sql(expression, "to_volume") 3879 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3880 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3881 3882 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3883 where = self.sql(expression, "where") 3884 group = self.sql(expression, "group") 3885 aggregates = self.expressions(expression, key="aggregates") 3886 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3887 3888 if not (where or group or aggregates) and len(expression.expressions) == 1: 3889 return f"TTL {self.expressions(expression, flat=True)}" 3890 3891 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3892 3893 def transaction_sql(self, expression: exp.Transaction) -> str: 3894 modes = self.expressions(expression, key="modes") 3895 modes = f" {modes}" if modes else "" 3896 return f"BEGIN{modes}" 3897 3898 def commit_sql(self, expression: exp.Commit) -> str: 3899 chain = expression.args.get("chain") 3900 if chain is not None: 3901 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3902 3903 return f"COMMIT{chain or ''}" 3904 3905 def rollback_sql(self, expression: exp.Rollback) -> str: 3906 savepoint = expression.args.get("savepoint") 3907 savepoint = f" TO {savepoint}" if savepoint else "" 3908 return f"ROLLBACK{savepoint}" 3909 3910 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3911 this = self.sql(expression, "this") 3912 3913 dtype = self.sql(expression, "dtype") 3914 if dtype: 3915 collate = self.sql(expression, "collate") 3916 collate = f" COLLATE {collate}" if collate else "" 3917 using = self.sql(expression, "using") 3918 using = f" USING {using}" if using else "" 3919 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3920 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3921 3922 default = self.sql(expression, "default") 3923 if default: 3924 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3925 3926 comment = self.sql(expression, "comment") 3927 if comment: 3928 return f"ALTER COLUMN {this} COMMENT {comment}" 3929 3930 visible = expression.args.get("visible") 3931 if visible: 3932 return f"ALTER COLUMN {this} SET {visible}" 3933 3934 allow_null = expression.args.get("allow_null") 3935 drop = expression.args.get("drop") 3936 3937 if not drop and not allow_null: 3938 self.unsupported("Unsupported ALTER COLUMN syntax") 3939 3940 if allow_null is not None: 3941 keyword = "DROP" if drop else "SET" 3942 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3943 3944 return f"ALTER COLUMN {this} DROP DEFAULT" 3945 3946 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 3947 if not self.SUPPORTS_MODIFY_COLUMN: 3948 self.unsupported("MODIFY COLUMN is not supported in this dialect") 3949 return f"MODIFY COLUMN {self.sql(expression, 'this')}" 3950 3951 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3952 this = self.sql(expression, "this") 3953 3954 visible = expression.args.get("visible") 3955 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3956 3957 return f"ALTER INDEX {this} {visible_sql}" 3958 3959 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3960 this = self.sql(expression, "this") 3961 if not isinstance(expression.this, exp.Var): 3962 this = f"KEY DISTKEY {this}" 3963 return f"ALTER DISTSTYLE {this}" 3964 3965 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3966 compound = " COMPOUND" if expression.args.get("compound") else "" 3967 this = self.sql(expression, "this") 3968 expressions = self.expressions(expression, flat=True) 3969 expressions = f"({expressions})" if expressions else "" 3970 return f"ALTER{compound} SORTKEY {this or expressions}" 3971 3972 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3973 if not self.RENAME_TABLE_WITH_DB: 3974 # Remove db from tables 3975 expression = expression.transform( 3976 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3977 ).assert_is(exp.AlterRename) 3978 this = self.sql(expression, "this") 3979 to_kw = " TO" if include_to else "" 3980 return f"RENAME{to_kw} {this}" 3981 3982 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3983 exists = " IF EXISTS" if expression.args.get("exists") else "" 3984 old_column = self.sql(expression, "this") 3985 new_column = self.sql(expression, "to") 3986 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3987 3988 def alterset_sql(self, expression: exp.AlterSet) -> str: 3989 exprs = self.expressions(expression, flat=True) 3990 if self.ALTER_SET_WRAPPED: 3991 exprs = f"({exprs})" 3992 3993 return f"SET {exprs}" 3994 3995 def alter_sql(self, expression: exp.Alter) -> str: 3996 actions = expression.args["actions"] 3997 3998 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3999 actions[0], exp.ColumnDef 4000 ): 4001 actions_sql = self.expressions(expression, key="actions", flat=True) 4002 actions_sql = f"ADD {actions_sql}" 4003 else: 4004 actions_list = [] 4005 for action in actions: 4006 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4007 action_sql = self.add_column_sql(action) 4008 else: 4009 action_sql = self.sql(action) 4010 if isinstance(action, exp.Query): 4011 action_sql = f"AS {action_sql}" 4012 4013 actions_list.append(action_sql) 4014 4015 actions_sql = self.format_args(*actions_list).lstrip("\n") 4016 4017 iceberg = ( 4018 "ICEBERG " 4019 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4020 else "" 4021 ) 4022 exists = " IF EXISTS" if expression.args.get("exists") else "" 4023 on_cluster = self.sql(expression, "cluster") 4024 on_cluster = f" {on_cluster}" if on_cluster else "" 4025 only = " ONLY" if expression.args.get("only") else "" 4026 options = self.expressions(expression, key="options") 4027 options = f", {options}" if options else "" 4028 kind = self.sql(expression, "kind") 4029 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4030 check = " WITH CHECK" if expression.args.get("check") else "" 4031 cascade = ( 4032 " CASCADE" 4033 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4034 else "" 4035 ) 4036 this = self.sql(expression, "this") 4037 this = f" {this}" if this else "" 4038 4039 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4040 4041 def altersession_sql(self, expression: exp.AlterSession) -> str: 4042 items_sql = self.expressions(expression, flat=True) 4043 keyword = "UNSET" if expression.args.get("unset") else "SET" 4044 return f"{keyword} {items_sql}" 4045 4046 def add_column_sql(self, expression: exp.Expr) -> str: 4047 sql = self.sql(expression) 4048 if isinstance(expression, exp.Schema): 4049 column_text = " COLUMNS" 4050 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4051 column_text = " COLUMN" 4052 else: 4053 column_text = "" 4054 4055 return f"ADD{column_text} {sql}" 4056 4057 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4058 expressions = self.expressions(expression) 4059 exists = " IF EXISTS " if expression.args.get("exists") else " " 4060 return f"DROP{exists}{expressions}" 4061 4062 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4063 return "DROP PRIMARY KEY" 4064 4065 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4066 return f"ADD {self.expressions(expression, indent=False)}" 4067 4068 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4069 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4070 location = self.sql(expression, "location") 4071 location = f" {location}" if location else "" 4072 return f"ADD {exists}{self.sql(expression.this)}{location}" 4073 4074 def distinct_sql(self, expression: exp.Distinct) -> str: 4075 this = self.expressions(expression, flat=True) 4076 4077 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4078 case = exp.case() 4079 for arg in expression.expressions: 4080 case = case.when(arg.is_(exp.null()), exp.null()) 4081 this = self.sql(case.else_(f"({this})")) 4082 4083 this = f" {this}" if this else "" 4084 4085 on = self.sql(expression, "on") 4086 on = f" ON {on}" if on else "" 4087 return f"DISTINCT{this}{on}" 4088 4089 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4090 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4091 4092 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4093 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4094 4095 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4096 this_sql = self.sql(expression, "this") 4097 expression_sql = self.sql(expression, "expression") 4098 kind = "MAX" if expression.args.get("max") else "MIN" 4099 return f"{this_sql} HAVING {kind} {expression_sql}" 4100 4101 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4102 return self.sql( 4103 exp.Cast( 4104 this=exp.Div(this=expression.this, expression=expression.expression), 4105 to=exp.DataType(this=exp.DType.INT), 4106 ) 4107 ) 4108 4109 def dpipe_sql(self, expression: exp.DPipe) -> str: 4110 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4111 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4112 return self.binary(expression, "||") 4113 4114 def div_sql(self, expression: exp.Div) -> str: 4115 l, r = expression.left, expression.right 4116 4117 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4118 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4119 4120 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4121 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4122 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4123 4124 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4125 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4126 return self.sql( 4127 exp.cast( 4128 l / r, 4129 to=exp.DType.BIGINT, 4130 ) 4131 ) 4132 4133 return self.binary(expression, "/") 4134 4135 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4136 n = exp._wrap(expression.this, exp.Binary) 4137 d = exp._wrap(expression.expression, exp.Binary) 4138 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4139 4140 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4141 return self.binary(expression, "OVERLAPS") 4142 4143 def distance_sql(self, expression: exp.Distance) -> str: 4144 return self.binary(expression, "<->") 4145 4146 def dot_sql(self, expression: exp.Dot) -> str: 4147 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4148 4149 def eq_sql(self, expression: exp.EQ) -> str: 4150 return self.binary(expression, "=") 4151 4152 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4153 return self.binary(expression, ":=") 4154 4155 def escape_sql(self, expression: exp.Escape) -> str: 4156 this = expression.this 4157 if ( 4158 isinstance(this, (exp.Like, exp.ILike)) 4159 and isinstance(this.expression, (exp.All, exp.Any)) 4160 and not self.SUPPORTS_LIKE_QUANTIFIERS 4161 ): 4162 return self._like_sql(this, escape=expression) 4163 return self.binary(expression, "ESCAPE") 4164 4165 def glob_sql(self, expression: exp.Glob) -> str: 4166 return self.binary(expression, "GLOB") 4167 4168 def gt_sql(self, expression: exp.GT) -> str: 4169 return self.binary(expression, ">") 4170 4171 def gte_sql(self, expression: exp.GTE) -> str: 4172 return self.binary(expression, ">=") 4173 4174 def is_sql(self, expression: exp.Is) -> str: 4175 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4176 return self.sql( 4177 expression.this if expression.expression.this else exp.not_(expression.this) 4178 ) 4179 return self.binary(expression, "IS") 4180 4181 def _like_sql( 4182 self, 4183 expression: exp.Like | exp.ILike, 4184 escape: exp.Escape | None = None, 4185 ) -> str: 4186 this = expression.this 4187 rhs = expression.expression 4188 4189 if isinstance(expression, exp.Like): 4190 exp_class: type[exp.Like | exp.ILike] = exp.Like 4191 op = "LIKE" 4192 else: 4193 exp_class = exp.ILike 4194 op = "ILIKE" 4195 4196 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4197 exprs = rhs.this.unnest() 4198 4199 if isinstance(exprs, exp.Tuple): 4200 exprs = exprs.expressions 4201 else: 4202 exprs = [exprs] 4203 4204 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4205 4206 def _make_like(expr: exp.Expression) -> exp.Expression: 4207 like: exp.Expression = exp_class(this=this, expression=expr) 4208 if escape: 4209 like = exp.Escape(this=like, expression=escape.expression.copy()) 4210 return like 4211 4212 like_expr: exp.Expr = _make_like(exprs[0]) 4213 for expr in exprs[1:]: 4214 like_expr = connective(like_expr, _make_like(expr), copy=False) 4215 4216 parent = escape.parent if escape else expression.parent 4217 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4218 parent, exp.Condition 4219 ): 4220 like_expr = exp.paren(like_expr, copy=False) 4221 4222 return self.sql(like_expr) 4223 4224 return self.binary(expression, op) 4225 4226 def like_sql(self, expression: exp.Like) -> str: 4227 return self._like_sql(expression) 4228 4229 def ilike_sql(self, expression: exp.ILike) -> str: 4230 return self._like_sql(expression) 4231 4232 def match_sql(self, expression: exp.Match) -> str: 4233 return self.binary(expression, "MATCH") 4234 4235 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4236 return self.binary(expression, "SIMILAR TO") 4237 4238 def lt_sql(self, expression: exp.LT) -> str: 4239 return self.binary(expression, "<") 4240 4241 def lte_sql(self, expression: exp.LTE) -> str: 4242 return self.binary(expression, "<=") 4243 4244 def mod_sql(self, expression: exp.Mod) -> str: 4245 return self.binary(expression, "%") 4246 4247 def mul_sql(self, expression: exp.Mul) -> str: 4248 return self.binary(expression, "*") 4249 4250 def neq_sql(self, expression: exp.NEQ) -> str: 4251 return self.binary(expression, "<>") 4252 4253 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4254 return self.binary(expression, "IS NOT DISTINCT FROM") 4255 4256 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4257 return self.binary(expression, "IS DISTINCT FROM") 4258 4259 def sub_sql(self, expression: exp.Sub) -> str: 4260 return self.binary(expression, "-") 4261 4262 def trycast_sql(self, expression: exp.TryCast) -> str: 4263 return self.cast_sql(expression, safe_prefix="TRY_") 4264 4265 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4266 return self.cast_sql(expression) 4267 4268 def try_sql(self, expression: exp.Try) -> str: 4269 if not self.TRY_SUPPORTED: 4270 self.unsupported("Unsupported TRY function") 4271 return self.sql(expression, "this") 4272 4273 return self.func("TRY", expression.this) 4274 4275 def log_sql(self, expression: exp.Log) -> str: 4276 this = expression.this 4277 expr = expression.expression 4278 4279 if self.dialect.LOG_BASE_FIRST is False: 4280 this, expr = expr, this 4281 elif self.dialect.LOG_BASE_FIRST is None and expr: 4282 if this.name in ("2", "10"): 4283 return self.func(f"LOG{this.name}", expr) 4284 4285 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4286 4287 return self.func("LOG", this, expr) 4288 4289 def use_sql(self, expression: exp.Use) -> str: 4290 kind = self.sql(expression, "kind") 4291 kind = f" {kind}" if kind else "" 4292 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4293 this = f" {this}" if this else "" 4294 return f"USE{kind}{this}" 4295 4296 def binary(self, expression: exp.Binary, op: str) -> str: 4297 sqls: list[str] = [] 4298 stack: list[None | str | exp.Expr] = [expression] 4299 binary_type = type(expression) 4300 4301 while stack: 4302 node = stack.pop() 4303 4304 if type(node) is binary_type: 4305 op_func = node.args.get("operator") 4306 if op_func: 4307 op = f"OPERATOR({self.sql(op_func)})" 4308 4309 stack.append(node.args.get("expression")) 4310 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4311 stack.append(node.args.get("this")) 4312 else: 4313 sqls.append(self.sql(node)) 4314 4315 return "".join(sqls) 4316 4317 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4318 to_clause = self.sql(expression, "to") 4319 if to_clause: 4320 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4321 4322 return self.function_fallback_sql(expression) 4323 4324 def function_fallback_sql(self, expression: exp.Func) -> str: 4325 args = [] 4326 4327 for key in expression.arg_types: 4328 arg_value = expression.args.get(key) 4329 4330 if isinstance(arg_value, list): 4331 for value in arg_value: 4332 args.append(value) 4333 elif arg_value is not None: 4334 args.append(arg_value) 4335 4336 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4337 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4338 else: 4339 name = expression.sql_name() 4340 4341 return self.func(name, *args) 4342 4343 def func( 4344 self, 4345 name: str, 4346 *args: t.Any, 4347 prefix: str = "(", 4348 suffix: str = ")", 4349 normalize: bool = True, 4350 ) -> str: 4351 name = self.normalize_func(name) if normalize else name 4352 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4353 4354 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4355 arg_sqls = tuple( 4356 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4357 ) 4358 if self.pretty and self.too_wide(arg_sqls): 4359 return self.indent( 4360 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4361 ) 4362 return sep.join(arg_sqls) 4363 4364 def too_wide(self, args: t.Iterable) -> bool: 4365 return sum(len(arg) for arg in args) > self.max_text_width 4366 4367 def format_time( 4368 self, 4369 expression: exp.Expr, 4370 inverse_time_mapping: dict[str, str] | None = None, 4371 inverse_time_trie: dict | None = None, 4372 ) -> str | None: 4373 return format_time( 4374 self.sql(expression, "format"), 4375 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4376 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4377 ) 4378 4379 def expressions( 4380 self, 4381 expression: exp.Expr | None = None, 4382 key: str | None = None, 4383 sqls: t.Collection[str | exp.Expr] | None = None, 4384 flat: bool = False, 4385 indent: bool = True, 4386 skip_first: bool = False, 4387 skip_last: bool = False, 4388 sep: str = ", ", 4389 prefix: str = "", 4390 dynamic: bool = False, 4391 new_line: bool = False, 4392 ) -> str: 4393 expressions = expression.args.get(key or "expressions") if expression else sqls 4394 4395 if not expressions: 4396 return "" 4397 4398 if flat: 4399 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4400 4401 num_sqls = len(expressions) 4402 result_sqls = [] 4403 4404 for i, e in enumerate(expressions): 4405 sql = self.sql(e, comment=False) 4406 if not sql: 4407 continue 4408 4409 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4410 4411 if self.pretty: 4412 if self.leading_comma: 4413 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4414 else: 4415 result_sqls.append( 4416 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4417 ) 4418 else: 4419 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4420 4421 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4422 if new_line: 4423 result_sqls.insert(0, "") 4424 result_sqls.append("") 4425 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4426 else: 4427 result_sql = "".join(result_sqls) 4428 4429 return ( 4430 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4431 if indent 4432 else result_sql 4433 ) 4434 4435 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4436 flat = flat or isinstance(expression.parent, exp.Properties) 4437 expressions_sql = self.expressions(expression, flat=flat) 4438 if flat: 4439 return f"{op} {expressions_sql}" 4440 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4441 4442 def naked_property(self, expression: exp.Property) -> str: 4443 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4444 if not property_name: 4445 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4446 return f"{property_name} {self.sql(expression, 'this')}" 4447 4448 def tag_sql(self, expression: exp.Tag) -> str: 4449 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4450 4451 def token_sql(self, token_type: TokenType) -> str: 4452 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4453 4454 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4455 this = self.sql(expression, "this") 4456 expressions = self.no_identify(self.expressions, expression) 4457 expressions = ( 4458 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4459 ) 4460 return f"{this}{expressions}" if expressions.strip() != "" else this 4461 4462 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4463 this = self.sql(expression, "this") 4464 expressions = self.expressions(expression, flat=True) 4465 return f"{this}({expressions})" 4466 4467 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4468 return self.binary(expression, "=>") 4469 4470 def when_sql(self, expression: exp.When) -> str: 4471 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4472 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4473 condition = self.sql(expression, "condition") 4474 condition = f" AND {condition}" if condition else "" 4475 4476 then_expression = expression.args.get("then") 4477 if isinstance(then_expression, exp.Insert): 4478 this = self.sql(then_expression, "this") 4479 this = f"INSERT {this}" if this else "INSERT" 4480 then = self.sql(then_expression, "expression") 4481 then = f"{this} VALUES {then}" if then else this 4482 elif isinstance(then_expression, exp.Update): 4483 if isinstance(then_expression.args.get("expressions"), exp.Star): 4484 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4485 else: 4486 expressions_sql = self.expressions(then_expression) 4487 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4488 else: 4489 then = self.sql(then_expression) 4490 4491 if isinstance(then_expression, (exp.Insert, exp.Update)): 4492 where = self.sql(then_expression, "where") 4493 if where and not self.SUPPORTS_MERGE_WHERE: 4494 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4495 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4496 where = "" 4497 then = f"{then}{where}" 4498 return f"WHEN {matched}{source}{condition} THEN {then}" 4499 4500 def whens_sql(self, expression: exp.Whens) -> str: 4501 return self.expressions(expression, sep=" ", indent=False) 4502 4503 def merge_sql(self, expression: exp.Merge) -> str: 4504 table = expression.this 4505 table_alias = "" 4506 4507 hints = table.args.get("hints") 4508 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4509 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4510 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4511 4512 this = self.sql(table) 4513 using = f"USING {self.sql(expression, 'using')}" 4514 whens = self.sql(expression, "whens") 4515 4516 on = self.sql(expression, "on") 4517 on = f"ON {on}" if on else "" 4518 4519 if not on: 4520 on = self.expressions(expression, key="using_cond") 4521 on = f"USING ({on})" if on else "" 4522 4523 returning = self.sql(expression, "returning") 4524 if returning: 4525 whens = f"{whens}{returning}" 4526 4527 sep = self.sep() 4528 4529 return self.prepend_ctes( 4530 expression, 4531 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4532 ) 4533 4534 @unsupported_args("format") 4535 def tochar_sql(self, expression: exp.ToChar) -> str: 4536 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4537 4538 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4539 if not self.SUPPORTS_TO_NUMBER: 4540 self.unsupported("Unsupported TO_NUMBER function") 4541 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4542 4543 fmt = expression.args.get("format") 4544 if not fmt: 4545 self.unsupported("Conversion format is required for TO_NUMBER") 4546 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4547 4548 return self.func("TO_NUMBER", expression.this, fmt) 4549 4550 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4551 this = self.sql(expression, "this") 4552 kind = self.sql(expression, "kind") 4553 settings_sql = self.expressions(expression, key="settings", sep=" ") 4554 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4555 return f"{this}({kind}{args})" 4556 4557 def dictrange_sql(self, expression: exp.DictRange) -> str: 4558 this = self.sql(expression, "this") 4559 max = self.sql(expression, "max") 4560 min = self.sql(expression, "min") 4561 return f"{this}(MIN {min} MAX {max})" 4562 4563 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4564 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4565 4566 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4567 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4568 4569 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4570 def uniquekeyproperty_sql( 4571 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4572 ) -> str: 4573 return f"{prefix} ({self.expressions(expression, flat=True)})" 4574 4575 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4576 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4577 expressions = self.expressions(expression, flat=True) 4578 expressions = f" {self.wrap(expressions)}" if expressions else "" 4579 buckets = self.sql(expression, "buckets") 4580 kind = self.sql(expression, "kind") 4581 buckets = f" BUCKETS {buckets}" if buckets else "" 4582 order = self.sql(expression, "order") 4583 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4584 4585 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4586 return "" 4587 4588 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4589 expressions = self.expressions(expression, key="expressions", flat=True) 4590 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4591 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4592 buckets = self.sql(expression, "buckets") 4593 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4594 4595 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4596 this = self.sql(expression, "this") 4597 having = self.sql(expression, "having") 4598 4599 if having: 4600 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4601 4602 return self.func("ANY_VALUE", this) 4603 4604 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4605 transform = self.func("TRANSFORM", *expression.expressions) 4606 row_format_before = self.sql(expression, "row_format_before") 4607 row_format_before = f" {row_format_before}" if row_format_before else "" 4608 record_writer = self.sql(expression, "record_writer") 4609 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4610 using = f" USING {self.sql(expression, 'command_script')}" 4611 schema = self.sql(expression, "schema") 4612 schema = f" AS {schema}" if schema else "" 4613 row_format_after = self.sql(expression, "row_format_after") 4614 row_format_after = f" {row_format_after}" if row_format_after else "" 4615 record_reader = self.sql(expression, "record_reader") 4616 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4617 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4618 4619 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4620 key_block_size = self.sql(expression, "key_block_size") 4621 if key_block_size: 4622 return f"KEY_BLOCK_SIZE = {key_block_size}" 4623 4624 using = self.sql(expression, "using") 4625 if using: 4626 return f"USING {using}" 4627 4628 parser = self.sql(expression, "parser") 4629 if parser: 4630 return f"WITH PARSER {parser}" 4631 4632 comment = self.sql(expression, "comment") 4633 if comment: 4634 return f"COMMENT {comment}" 4635 4636 visible = expression.args.get("visible") 4637 if visible is not None: 4638 return "VISIBLE" if visible else "INVISIBLE" 4639 4640 engine_attr = self.sql(expression, "engine_attr") 4641 if engine_attr: 4642 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4643 4644 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4645 if secondary_engine_attr: 4646 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4647 4648 self.unsupported("Unsupported index constraint option.") 4649 return "" 4650 4651 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4652 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4653 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4654 4655 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4656 kind = self.sql(expression, "kind") 4657 kind = f"{kind} INDEX" if kind else "INDEX" 4658 this = self.sql(expression, "this") 4659 this = f" {this}" if this else "" 4660 index_type = self.sql(expression, "index_type") 4661 index_type = f" USING {index_type}" if index_type else "" 4662 expressions = self.expressions(expression, flat=True) 4663 expressions = f" ({expressions})" if expressions else "" 4664 options = self.expressions(expression, key="options", sep=" ") 4665 options = f" {options}" if options else "" 4666 return f"{kind}{this}{index_type}{expressions}{options}" 4667 4668 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4669 if self.NVL2_SUPPORTED: 4670 return self.function_fallback_sql(expression) 4671 4672 case = exp.Case().when( 4673 expression.this.is_(exp.null()).not_(copy=False), 4674 expression.args["true"], 4675 copy=False, 4676 ) 4677 else_cond = expression.args.get("false") 4678 if else_cond: 4679 case.else_(else_cond, copy=False) 4680 4681 return self.sql(case) 4682 4683 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4684 this = self.sql(expression, "this") 4685 expr = self.sql(expression, "expression") 4686 position = self.sql(expression, "position") 4687 position = f", {position}" if position else "" 4688 iterator = self.sql(expression, "iterator") 4689 condition = self.sql(expression, "condition") 4690 condition = f" IF {condition}" if condition else "" 4691 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4692 4693 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4694 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4695 4696 def opclass_sql(self, expression: exp.Opclass) -> str: 4697 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4698 4699 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4700 model = self.sql(expression, "this") 4701 model = f"MODEL {model}" 4702 expr = expression.expression 4703 if expr: 4704 expr_sql = self.sql(expression, "expression") 4705 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 4706 else: 4707 expr_sql = None 4708 4709 parameters = self.sql(expression, "params_struct") or None 4710 4711 return self.func(name, model, expr_sql, parameters) 4712 4713 def predict_sql(self, expression: exp.Predict) -> str: 4714 return self._ml_sql(expression, "PREDICT") 4715 4716 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4717 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4718 return self._ml_sql(expression, name) 4719 4720 def generatetext_sql(self, expression: exp.GenerateText) -> str: 4721 return self._ml_sql(expression, "GENERATE_TEXT") 4722 4723 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 4724 return self._ml_sql(expression, "GENERATE_TABLE") 4725 4726 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 4727 return self._ml_sql(expression, "GENERATE_BOOL") 4728 4729 def generateint_sql(self, expression: exp.GenerateInt) -> str: 4730 return self._ml_sql(expression, "GENERATE_INT") 4731 4732 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 4733 return self._ml_sql(expression, "GENERATE_DOUBLE") 4734 4735 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4736 return self._ml_sql(expression, "TRANSLATE") 4737 4738 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4739 return self._ml_sql(expression, "FORECAST") 4740 4741 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4742 this_sql = self.sql(expression, "this") 4743 if isinstance(expression.this, exp.Table): 4744 this_sql = f"TABLE {this_sql}" 4745 4746 return self.func( 4747 "FORECAST", 4748 this_sql, 4749 expression.args.get("data_col"), 4750 expression.args.get("timestamp_col"), 4751 expression.args.get("model"), 4752 expression.args.get("id_cols"), 4753 expression.args.get("horizon"), 4754 expression.args.get("forecast_end_timestamp"), 4755 expression.args.get("confidence_level"), 4756 expression.args.get("output_historical_time_series"), 4757 expression.args.get("context_window"), 4758 ) 4759 4760 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4761 this_sql = self.sql(expression, "this") 4762 if isinstance(expression.this, exp.Table): 4763 this_sql = f"TABLE {this_sql}" 4764 4765 return self.func( 4766 "FEATURES_AT_TIME", 4767 this_sql, 4768 expression.args.get("time"), 4769 expression.args.get("num_rows"), 4770 expression.args.get("ignore_feature_nulls"), 4771 ) 4772 4773 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4774 this_sql = self.sql(expression, "this") 4775 if isinstance(expression.this, exp.Table): 4776 this_sql = f"TABLE {this_sql}" 4777 4778 query_table = self.sql(expression, "query_table") 4779 if isinstance(expression.args["query_table"], exp.Table): 4780 query_table = f"TABLE {query_table}" 4781 4782 return self.func( 4783 "VECTOR_SEARCH", 4784 this_sql, 4785 expression.args.get("column_to_search"), 4786 query_table, 4787 expression.args.get("query_column_to_search"), 4788 expression.args.get("top_k"), 4789 expression.args.get("distance_type"), 4790 expression.args.get("options"), 4791 ) 4792 4793 def forin_sql(self, expression: exp.ForIn) -> str: 4794 this = self.sql(expression, "this") 4795 expression_sql = self.sql(expression, "expression") 4796 return f"FOR {this} DO {expression_sql}" 4797 4798 def refresh_sql(self, expression: exp.Refresh) -> str: 4799 this = self.sql(expression, "this") 4800 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4801 return f"REFRESH {kind}{this}" 4802 4803 def toarray_sql(self, expression: exp.ToArray) -> str: 4804 arg = expression.this 4805 if not arg.type: 4806 import sqlglot.optimizer.annotate_types 4807 4808 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 4809 4810 if arg.is_type(exp.DType.ARRAY): 4811 return self.sql(arg) 4812 4813 cond_for_null = arg.is_(exp.null()) 4814 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4815 4816 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4817 this = expression.this 4818 time_format = self.format_time(expression) 4819 4820 if time_format: 4821 return self.sql( 4822 exp.cast( 4823 exp.StrToTime(this=this, format=expression.args["format"]), 4824 exp.DType.TIME, 4825 ) 4826 ) 4827 4828 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 4829 return self.sql(this) 4830 4831 return self.sql(exp.cast(this, exp.DType.TIME)) 4832 4833 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4834 this = expression.this 4835 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 4836 return self.sql(this) 4837 4838 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 4839 4840 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4841 this = expression.this 4842 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 4843 return self.sql(this) 4844 4845 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 4846 4847 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4848 this = expression.this 4849 time_format = self.format_time(expression) 4850 safe = expression.args.get("safe") 4851 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4852 return self.sql( 4853 exp.cast( 4854 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4855 exp.DType.DATE, 4856 ) 4857 ) 4858 4859 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 4860 return self.sql(this) 4861 4862 if safe: 4863 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 4864 4865 return self.sql(exp.cast(this, exp.DType.DATE)) 4866 4867 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4868 return self.sql( 4869 exp.func( 4870 "DATEDIFF", 4871 expression.this, 4872 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 4873 "day", 4874 ) 4875 ) 4876 4877 def lastday_sql(self, expression: exp.LastDay) -> str: 4878 if self.LAST_DAY_SUPPORTS_DATE_PART: 4879 return self.function_fallback_sql(expression) 4880 4881 unit = expression.text("unit") 4882 if unit and unit != "MONTH": 4883 self.unsupported("Date parts are not supported in LAST_DAY.") 4884 4885 return self.func("LAST_DAY", expression.this) 4886 4887 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4888 import sqlglot.dialects.dialect 4889 4890 return self.func( 4891 "DATE_ADD", 4892 expression.this, 4893 expression.expression, 4894 sqlglot.dialects.dialect.unit_to_str(expression), 4895 ) 4896 4897 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4898 if self.CAN_IMPLEMENT_ARRAY_ANY: 4899 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4900 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4901 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4902 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4903 4904 import sqlglot.dialects.dialect 4905 4906 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4907 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 4908 self.unsupported("ARRAY_ANY is unsupported") 4909 4910 return self.function_fallback_sql(expression) 4911 4912 def struct_sql(self, expression: exp.Struct) -> str: 4913 expression.set( 4914 "expressions", 4915 [ 4916 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4917 if isinstance(e, exp.PropertyEQ) 4918 else e 4919 for e in expression.expressions 4920 ], 4921 ) 4922 4923 return self.function_fallback_sql(expression) 4924 4925 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4926 low = self.sql(expression, "this") 4927 high = self.sql(expression, "expression") 4928 4929 return f"{low} TO {high}" 4930 4931 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4932 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4933 tables = f" {self.expressions(expression)}" 4934 4935 exists = " IF EXISTS" if expression.args.get("exists") else "" 4936 4937 on_cluster = self.sql(expression, "cluster") 4938 on_cluster = f" {on_cluster}" if on_cluster else "" 4939 4940 identity = self.sql(expression, "identity") 4941 identity = f" {identity} IDENTITY" if identity else "" 4942 4943 option = self.sql(expression, "option") 4944 option = f" {option}" if option else "" 4945 4946 partition = self.sql(expression, "partition") 4947 partition = f" {partition}" if partition else "" 4948 4949 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4950 4951 # This transpiles T-SQL's CONVERT function 4952 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4953 def convert_sql(self, expression: exp.Convert) -> str: 4954 to = expression.this 4955 value = expression.expression 4956 style = expression.args.get("style") 4957 safe = expression.args.get("safe") 4958 strict = expression.args.get("strict") 4959 4960 if not to or not value: 4961 return "" 4962 4963 # Retrieve length of datatype and override to default if not specified 4964 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4965 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4966 4967 transformed: exp.Expr | None = None 4968 cast = exp.Cast if strict else exp.TryCast 4969 4970 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4971 if isinstance(style, exp.Literal) and style.is_int: 4972 import sqlglot.dialects.tsql 4973 4974 style_value = style.name 4975 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4976 if not converted_style: 4977 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4978 4979 fmt = exp.Literal.string(converted_style) 4980 4981 if to.this == exp.DType.DATE: 4982 transformed = exp.StrToDate(this=value, format=fmt) 4983 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 4984 transformed = exp.StrToTime(this=value, format=fmt) 4985 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4986 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4987 elif to.this == exp.DType.TEXT: 4988 transformed = exp.TimeToStr(this=value, format=fmt) 4989 4990 if not transformed: 4991 transformed = cast(this=value, to=to, safe=safe) 4992 4993 return self.sql(transformed) 4994 4995 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4996 this = expression.this 4997 if isinstance(this, exp.JSONPathWildcard): 4998 this = self.json_path_part(this) 4999 return f".{this}" if this else "" 5000 5001 if self.SAFE_JSON_PATH_KEY_RE.match(this): 5002 return f".{this}" 5003 5004 this = self.json_path_part(this) 5005 return ( 5006 f"[{this}]" 5007 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5008 else f".{this}" 5009 ) 5010 5011 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5012 this = self.json_path_part(expression.this) 5013 return f"[{this}]" if this else "" 5014 5015 def _simplify_unless_literal(self, expression: E) -> E: 5016 if not isinstance(expression, exp.Literal): 5017 import sqlglot.optimizer.simplify 5018 5019 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5020 5021 return expression 5022 5023 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5024 this = expression.this 5025 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5026 self.unsupported( 5027 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5028 ) 5029 return self.sql(this) 5030 5031 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 5032 if self.IGNORE_NULLS_BEFORE_ORDER: 5033 # The first modifier here will be the one closest to the AggFunc's arg 5034 mods = sorted( 5035 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5036 key=lambda x: ( 5037 0 5038 if isinstance(x, exp.HavingMax) 5039 else (1 if isinstance(x, exp.Order) else 2) 5040 ), 5041 ) 5042 5043 if mods: 5044 mod = mods[0] 5045 this = expression.__class__(this=mod.this.copy()) 5046 this.meta["inline"] = True 5047 mod.this.replace(this) 5048 return self.sql(expression.this) 5049 5050 agg_func = expression.find(exp.AggFunc) 5051 5052 if agg_func: 5053 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5054 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5055 5056 return f"{self.sql(expression, 'this')} {text}" 5057 5058 def _replace_line_breaks(self, string: str) -> str: 5059 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5060 if self.pretty: 5061 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5062 return string 5063 5064 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5065 option = self.sql(expression, "this") 5066 5067 if expression.expressions: 5068 upper = option.upper() 5069 5070 # Snowflake FILE_FORMAT options are separated by whitespace 5071 sep = " " if upper == "FILE_FORMAT" else ", " 5072 5073 # Databricks copy/format options do not set their list of values with EQ 5074 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5075 values = self.expressions(expression, flat=True, sep=sep) 5076 return f"{option}{op}({values})" 5077 5078 value = self.sql(expression, "expression") 5079 5080 if not value: 5081 return option 5082 5083 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5084 5085 return f"{option}{op}{value}" 5086 5087 def credentials_sql(self, expression: exp.Credentials) -> str: 5088 cred_expr = expression.args.get("credentials") 5089 if isinstance(cred_expr, exp.Literal): 5090 # Redshift case: CREDENTIALS <string> 5091 credentials = self.sql(expression, "credentials") 5092 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5093 else: 5094 # Snowflake case: CREDENTIALS = (...) 5095 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5096 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5097 5098 storage = self.sql(expression, "storage") 5099 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5100 5101 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5102 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5103 5104 iam_role = self.sql(expression, "iam_role") 5105 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5106 5107 region = self.sql(expression, "region") 5108 region = f" REGION {region}" if region else "" 5109 5110 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5111 5112 def copy_sql(self, expression: exp.Copy) -> str: 5113 this = self.sql(expression, "this") 5114 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5115 5116 credentials = self.sql(expression, "credentials") 5117 credentials = self.seg(credentials) if credentials else "" 5118 files = self.expressions(expression, key="files", flat=True) 5119 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5120 5121 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5122 params = self.expressions( 5123 expression, 5124 key="params", 5125 sep=sep, 5126 new_line=True, 5127 skip_last=True, 5128 skip_first=True, 5129 indent=self.COPY_PARAMS_ARE_WRAPPED, 5130 ) 5131 5132 if params: 5133 if self.COPY_PARAMS_ARE_WRAPPED: 5134 params = f" WITH ({params})" 5135 elif not self.pretty and (files or credentials): 5136 params = f" {params}" 5137 5138 return f"COPY{this}{kind} {files}{credentials}{params}" 5139 5140 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5141 return "" 5142 5143 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5144 on_sql = "ON" if expression.args.get("on") else "OFF" 5145 filter_col: str | None = self.sql(expression, "filter_column") 5146 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5147 retention_period: str | None = self.sql(expression, "retention_period") 5148 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5149 5150 if filter_col or retention_period: 5151 on_sql = self.func("ON", filter_col, retention_period) 5152 5153 return f"DATA_DELETION={on_sql}" 5154 5155 def maskingpolicycolumnconstraint_sql( 5156 self, expression: exp.MaskingPolicyColumnConstraint 5157 ) -> str: 5158 this = self.sql(expression, "this") 5159 expressions = self.expressions(expression, flat=True) 5160 expressions = f" USING ({expressions})" if expressions else "" 5161 return f"MASKING POLICY {this}{expressions}" 5162 5163 def gapfill_sql(self, expression: exp.GapFill) -> str: 5164 this = self.sql(expression, "this") 5165 this = f"TABLE {this}" 5166 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5167 5168 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5169 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5170 5171 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5172 this = self.sql(expression, "this") 5173 expr = expression.expression 5174 5175 if isinstance(expr, exp.Func): 5176 # T-SQL's CLR functions are case sensitive 5177 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5178 else: 5179 expr = self.sql(expression, "expression") 5180 5181 return self.scope_resolution(expr, this) 5182 5183 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5184 if self.PARSE_JSON_NAME is None: 5185 return self.sql(expression.this) 5186 5187 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5188 5189 def rand_sql(self, expression: exp.Rand) -> str: 5190 lower = self.sql(expression, "lower") 5191 upper = self.sql(expression, "upper") 5192 5193 if lower and upper: 5194 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5195 return self.func("RAND", expression.this) 5196 5197 def changes_sql(self, expression: exp.Changes) -> str: 5198 information = self.sql(expression, "information") 5199 information = f"INFORMATION => {information}" 5200 at_before = self.sql(expression, "at_before") 5201 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5202 end = self.sql(expression, "end") 5203 end = f"{self.seg('')}{end}" if end else "" 5204 5205 return f"CHANGES ({information}){at_before}{end}" 5206 5207 def pad_sql(self, expression: exp.Pad) -> str: 5208 prefix = "L" if expression.args.get("is_left") else "R" 5209 5210 fill_pattern = self.sql(expression, "fill_pattern") or None 5211 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5212 fill_pattern = "' '" 5213 5214 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5215 5216 def summarize_sql(self, expression: exp.Summarize) -> str: 5217 table = " TABLE" if expression.args.get("table") else "" 5218 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5219 5220 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5221 generate_series = exp.GenerateSeries(**expression.args) 5222 5223 parent = expression.parent 5224 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5225 parent = parent.parent 5226 5227 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5228 return self.sql(exp.Unnest(expressions=[generate_series])) 5229 5230 if isinstance(parent, exp.Select): 5231 self.unsupported("GenerateSeries projection unnesting is not supported.") 5232 5233 return self.sql(generate_series) 5234 5235 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5236 if self.SUPPORTS_CONVERT_TIMEZONE: 5237 return self.function_fallback_sql(expression) 5238 5239 source_tz = expression.args.get("source_tz") 5240 target_tz = expression.args.get("target_tz") 5241 timestamp = expression.args.get("timestamp") 5242 5243 if source_tz and timestamp: 5244 timestamp = exp.AtTimeZone( 5245 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5246 ) 5247 5248 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5249 5250 return self.sql(expr) 5251 5252 def json_sql(self, expression: exp.JSON) -> str: 5253 this = self.sql(expression, "this") 5254 this = f" {this}" if this else "" 5255 5256 _with = expression.args.get("with_") 5257 5258 if _with is None: 5259 with_sql = "" 5260 elif not _with: 5261 with_sql = " WITHOUT" 5262 else: 5263 with_sql = " WITH" 5264 5265 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5266 5267 return f"JSON{this}{with_sql}{unique_sql}" 5268 5269 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5270 path = self.sql(expression, "path") 5271 returning = self.sql(expression, "returning") 5272 returning = f" RETURNING {returning}" if returning else "" 5273 5274 on_condition = self.sql(expression, "on_condition") 5275 on_condition = f" {on_condition}" if on_condition else "" 5276 5277 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5278 5279 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5280 regexp = " REGEXP" if expression.args.get("regexp") else "" 5281 return f"SKIP{regexp} {self.sql(expression.expression)}" 5282 5283 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5284 else_ = "ELSE " if expression.args.get("else_") else "" 5285 condition = self.sql(expression, "expression") 5286 condition = f"WHEN {condition} THEN " if condition else else_ 5287 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5288 return f"{condition}{insert}" 5289 5290 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5291 kind = self.sql(expression, "kind") 5292 expressions = self.seg(self.expressions(expression, sep=" ")) 5293 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5294 return res 5295 5296 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5297 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5298 empty = expression.args.get("empty") 5299 empty = ( 5300 f"DEFAULT {empty} ON EMPTY" 5301 if isinstance(empty, exp.Expr) 5302 else self.sql(expression, "empty") 5303 ) 5304 5305 error = expression.args.get("error") 5306 error = ( 5307 f"DEFAULT {error} ON ERROR" 5308 if isinstance(error, exp.Expr) 5309 else self.sql(expression, "error") 5310 ) 5311 5312 if error and empty: 5313 error = ( 5314 f"{empty} {error}" 5315 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5316 else f"{error} {empty}" 5317 ) 5318 empty = "" 5319 5320 null = self.sql(expression, "null") 5321 5322 return f"{empty}{error}{null}" 5323 5324 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5325 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5326 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5327 5328 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5329 this = self.sql(expression, "this") 5330 path = self.sql(expression, "path") 5331 5332 passing = self.expressions(expression, "passing") 5333 passing = f" PASSING {passing}" if passing else "" 5334 5335 on_condition = self.sql(expression, "on_condition") 5336 on_condition = f" {on_condition}" if on_condition else "" 5337 5338 path = f"{path}{passing}{on_condition}" 5339 5340 return self.func("JSON_EXISTS", this, path) 5341 5342 def _add_arrayagg_null_filter( 5343 self, 5344 array_agg_sql: str, 5345 array_agg_expr: exp.ArrayAgg, 5346 column_expr: exp.Expr, 5347 ) -> str: 5348 """ 5349 Add NULL filter to ARRAY_AGG if dialect requires it. 5350 5351 Args: 5352 array_agg_sql: The generated ARRAY_AGG SQL string 5353 array_agg_expr: The ArrayAgg expression node 5354 column_expr: The column/expression to filter (before ORDER BY wrapping) 5355 5356 Returns: 5357 SQL string with FILTER clause added if needed 5358 """ 5359 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5360 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5361 if not ( 5362 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5363 ): 5364 return array_agg_sql 5365 5366 parent = array_agg_expr.parent 5367 if isinstance(parent, exp.Filter): 5368 parent_cond = parent.expression.this 5369 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5370 elif column_expr.find(exp.Column): 5371 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5372 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5373 this_sql = ( 5374 self.expressions(column_expr) 5375 if isinstance(column_expr, exp.Distinct) 5376 else self.sql(column_expr) 5377 ) 5378 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5379 5380 return array_agg_sql 5381 5382 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5383 array_agg = self.function_fallback_sql(expression) 5384 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5385 5386 def slice_sql(self, expression: exp.Slice) -> str: 5387 step = self.sql(expression, "step") 5388 end = self.sql(expression.expression) 5389 begin = self.sql(expression.this) 5390 5391 sql = f"{end}:{step}" if step else end 5392 return f"{begin}:{sql}" if sql else f"{begin}:" 5393 5394 def apply_sql(self, expression: exp.Apply) -> str: 5395 this = self.sql(expression, "this") 5396 expr = self.sql(expression, "expression") 5397 5398 return f"{this} APPLY({expr})" 5399 5400 def _grant_or_revoke_sql( 5401 self, 5402 expression: exp.Grant | exp.Revoke, 5403 keyword: str, 5404 preposition: str, 5405 grant_option_prefix: str = "", 5406 grant_option_suffix: str = "", 5407 ) -> str: 5408 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5409 5410 kind = self.sql(expression, "kind") 5411 kind = f" {kind}" if kind else "" 5412 5413 securable = self.sql(expression, "securable") 5414 securable = f" {securable}" if securable else "" 5415 5416 principals = self.expressions(expression, key="principals", flat=True) 5417 5418 if not expression.args.get("grant_option"): 5419 grant_option_prefix = grant_option_suffix = "" 5420 5421 # cascade for revoke only 5422 cascade = self.sql(expression, "cascade") 5423 cascade = f" {cascade}" if cascade else "" 5424 5425 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5426 5427 def grant_sql(self, expression: exp.Grant) -> str: 5428 return self._grant_or_revoke_sql( 5429 expression, 5430 keyword="GRANT", 5431 preposition="TO", 5432 grant_option_suffix=" WITH GRANT OPTION", 5433 ) 5434 5435 def revoke_sql(self, expression: exp.Revoke) -> str: 5436 return self._grant_or_revoke_sql( 5437 expression, 5438 keyword="REVOKE", 5439 preposition="FROM", 5440 grant_option_prefix="GRANT OPTION FOR ", 5441 ) 5442 5443 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5444 this = self.sql(expression, "this") 5445 columns = self.expressions(expression, flat=True) 5446 columns = f"({columns})" if columns else "" 5447 5448 return f"{this}{columns}" 5449 5450 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5451 this = self.sql(expression, "this") 5452 5453 kind = self.sql(expression, "kind") 5454 kind = f"{kind} " if kind else "" 5455 5456 return f"{kind}{this}" 5457 5458 def columns_sql(self, expression: exp.Columns) -> str: 5459 func = self.function_fallback_sql(expression) 5460 if expression.args.get("unpack"): 5461 func = f"*{func}" 5462 5463 return func 5464 5465 def overlay_sql(self, expression: exp.Overlay) -> str: 5466 this = self.sql(expression, "this") 5467 expr = self.sql(expression, "expression") 5468 from_sql = self.sql(expression, "from_") 5469 for_sql = self.sql(expression, "for_") 5470 for_sql = f" FOR {for_sql}" if for_sql else "" 5471 5472 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5473 5474 @unsupported_args("format") 5475 def todouble_sql(self, expression: exp.ToDouble) -> str: 5476 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5477 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5478 5479 def string_sql(self, expression: exp.String) -> str: 5480 this = expression.this 5481 zone = expression.args.get("zone") 5482 5483 if zone: 5484 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5485 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5486 # set for source_tz to transpile the time conversion before the STRING cast 5487 this = exp.ConvertTimezone( 5488 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5489 ) 5490 5491 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5492 5493 def median_sql(self, expression: exp.Median) -> str: 5494 if not self.SUPPORTS_MEDIAN: 5495 return self.sql( 5496 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5497 ) 5498 5499 return self.function_fallback_sql(expression) 5500 5501 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5502 filler = self.sql(expression, "this") 5503 filler = f" {filler}" if filler else "" 5504 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5505 return f"TRUNCATE{filler} {with_count}" 5506 5507 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5508 if self.SUPPORTS_UNIX_SECONDS: 5509 return self.function_fallback_sql(expression) 5510 5511 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5512 5513 return self.sql( 5514 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5515 ) 5516 5517 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5518 dim = expression.expression 5519 5520 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5521 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5522 if not (dim.is_int and dim.name == "1"): 5523 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5524 dim = None 5525 5526 # If dimension is required but not specified, default initialize it 5527 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5528 dim = exp.Literal.number(1) 5529 5530 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5531 5532 def attach_sql(self, expression: exp.Attach) -> str: 5533 this = self.sql(expression, "this") 5534 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5535 expressions = self.expressions(expression) 5536 expressions = f" ({expressions})" if expressions else "" 5537 5538 return f"ATTACH{exists_sql} {this}{expressions}" 5539 5540 def detach_sql(self, expression: exp.Detach) -> str: 5541 kind = self.sql(expression, "kind") 5542 kind = f" {kind}" if kind else "" 5543 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5544 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5545 exists = " IF EXISTS" if expression.args.get("exists") else "" 5546 if exists: 5547 kind = kind or " DATABASE" 5548 5549 this = self.sql(expression, "this") 5550 this = f" {this}" if this else "" 5551 cluster = self.sql(expression, "cluster") 5552 cluster = f" {cluster}" if cluster else "" 5553 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5554 sync = " SYNC" if expression.args.get("sync") else "" 5555 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5556 5557 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5558 this = self.sql(expression, "this") 5559 value = self.sql(expression, "expression") 5560 value = f" {value}" if value else "" 5561 return f"{this}{value}" 5562 5563 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5564 return ( 5565 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5566 ) 5567 5568 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5569 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5570 encode = f"{encode} {self.sql(expression, 'this')}" 5571 5572 properties = expression.args.get("properties") 5573 if properties: 5574 encode = f"{encode} {self.properties(properties)}" 5575 5576 return encode 5577 5578 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5579 this = self.sql(expression, "this") 5580 include = f"INCLUDE {this}" 5581 5582 column_def = self.sql(expression, "column_def") 5583 if column_def: 5584 include = f"{include} {column_def}" 5585 5586 alias = self.sql(expression, "alias") 5587 if alias: 5588 include = f"{include} AS {alias}" 5589 5590 return include 5591 5592 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5593 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5594 name = f"{prefix} {self.sql(expression, 'this')}" 5595 return self.func("XMLELEMENT", name, *expression.expressions) 5596 5597 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5598 this = self.sql(expression, "this") 5599 expr = self.sql(expression, "expression") 5600 expr = f"({expr})" if expr else "" 5601 return f"{this}{expr}" 5602 5603 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5604 partitions = self.expressions(expression, "partition_expressions") 5605 create = self.expressions(expression, "create_expressions") 5606 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5607 5608 def partitionbyrangepropertydynamic_sql( 5609 self, expression: exp.PartitionByRangePropertyDynamic 5610 ) -> str: 5611 start = self.sql(expression, "start") 5612 end = self.sql(expression, "end") 5613 5614 every = expression.args["every"] 5615 if isinstance(every, exp.Interval) and every.this.is_string: 5616 every.this.replace(exp.Literal.number(every.name)) 5617 5618 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5619 5620 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5621 name = self.sql(expression, "this") 5622 values = self.expressions(expression, flat=True) 5623 5624 return f"NAME {name} VALUE {values}" 5625 5626 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5627 kind = self.sql(expression, "kind") 5628 sample = self.sql(expression, "sample") 5629 return f"SAMPLE {sample} {kind}" 5630 5631 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5632 kind = self.sql(expression, "kind") 5633 option = self.sql(expression, "option") 5634 option = f" {option}" if option else "" 5635 this = self.sql(expression, "this") 5636 this = f" {this}" if this else "" 5637 columns = self.expressions(expression) 5638 columns = f" {columns}" if columns else "" 5639 return f"{kind}{option} STATISTICS{this}{columns}" 5640 5641 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5642 this = self.sql(expression, "this") 5643 columns = self.expressions(expression) 5644 inner_expression = self.sql(expression, "expression") 5645 inner_expression = f" {inner_expression}" if inner_expression else "" 5646 update_options = self.sql(expression, "update_options") 5647 update_options = f" {update_options} UPDATE" if update_options else "" 5648 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5649 5650 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5651 kind = self.sql(expression, "kind") 5652 kind = f" {kind}" if kind else "" 5653 return f"DELETE{kind} STATISTICS" 5654 5655 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5656 inner_expression = self.sql(expression, "expression") 5657 return f"LIST CHAINED ROWS{inner_expression}" 5658 5659 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5660 kind = self.sql(expression, "kind") 5661 this = self.sql(expression, "this") 5662 this = f" {this}" if this else "" 5663 inner_expression = self.sql(expression, "expression") 5664 return f"VALIDATE {kind}{this}{inner_expression}" 5665 5666 def analyze_sql(self, expression: exp.Analyze) -> str: 5667 options = self.expressions(expression, key="options", sep=" ") 5668 options = f" {options}" if options else "" 5669 kind = self.sql(expression, "kind") 5670 kind = f" {kind}" if kind else "" 5671 this = self.sql(expression, "this") 5672 this = f" {this}" if this else "" 5673 mode = self.sql(expression, "mode") 5674 mode = f" {mode}" if mode else "" 5675 properties = self.sql(expression, "properties") 5676 properties = f" {properties}" if properties else "" 5677 partition = self.sql(expression, "partition") 5678 partition = f" {partition}" if partition else "" 5679 inner_expression = self.sql(expression, "expression") 5680 inner_expression = f" {inner_expression}" if inner_expression else "" 5681 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5682 5683 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5684 this = self.sql(expression, "this") 5685 namespaces = self.expressions(expression, key="namespaces") 5686 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5687 passing = self.expressions(expression, key="passing") 5688 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5689 columns = self.expressions(expression, key="columns") 5690 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5691 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5692 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5693 5694 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5695 this = self.sql(expression, "this") 5696 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5697 5698 def export_sql(self, expression: exp.Export) -> str: 5699 this = self.sql(expression, "this") 5700 connection = self.sql(expression, "connection") 5701 connection = f"WITH CONNECTION {connection} " if connection else "" 5702 options = self.sql(expression, "options") 5703 return f"EXPORT DATA {connection}{options} AS {this}" 5704 5705 def declare_sql(self, expression: exp.Declare) -> str: 5706 replace = "OR REPLACE " if expression.args.get("replace") else "" 5707 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 5708 5709 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5710 variables = self.expressions(expression, "this") 5711 default = self.sql(expression, "default") 5712 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5713 5714 kind = self.sql(expression, "kind") 5715 if isinstance(expression.args.get("kind"), exp.Schema): 5716 kind = f"TABLE {kind}" 5717 5718 kind = f" {kind}" if kind else "" 5719 5720 return f"{variables}{kind}{default}" 5721 5722 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5723 kind = self.sql(expression, "kind") 5724 this = self.sql(expression, "this") 5725 set = self.sql(expression, "expression") 5726 using = self.sql(expression, "using") 5727 using = f" USING {using}" if using else "" 5728 5729 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5730 5731 return f"{kind_sql} {this} SET {set}{using}" 5732 5733 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5734 params = self.expressions(expression, key="params", flat=True) 5735 return self.func(expression.name, *expression.expressions) + f"({params})" 5736 5737 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5738 return self.func(expression.name, *expression.expressions) 5739 5740 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5741 return self.anonymousaggfunc_sql(expression) 5742 5743 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5744 return self.parameterizedagg_sql(expression) 5745 5746 def show_sql(self, expression: exp.Show) -> str: 5747 self.unsupported("Unsupported SHOW statement") 5748 return "" 5749 5750 def install_sql(self, expression: exp.Install) -> str: 5751 self.unsupported("Unsupported INSTALL statement") 5752 return "" 5753 5754 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5755 # Snowflake GET/PUT statements: 5756 # PUT <file> <internalStage> <properties> 5757 # GET <internalStage> <file> <properties> 5758 props = expression.args.get("properties") 5759 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5760 this = self.sql(expression, "this") 5761 target = self.sql(expression, "target") 5762 5763 if isinstance(expression, exp.Put): 5764 return f"PUT {this} {target}{props_sql}" 5765 else: 5766 return f"GET {target} {this}{props_sql}" 5767 5768 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5769 this = self.sql(expression, "this") 5770 expr = self.sql(expression, "expression") 5771 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5772 return f"TRANSLATE({this} USING {expr}{with_error})" 5773 5774 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5775 if self.SUPPORTS_DECODE_CASE: 5776 return self.func("DECODE", *expression.expressions) 5777 5778 decode_expr, *expressions = expression.expressions 5779 5780 ifs = [] 5781 for search, result in zip(expressions[::2], expressions[1::2]): 5782 if isinstance(search, exp.Literal): 5783 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 5784 elif isinstance(search, exp.Null): 5785 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 5786 else: 5787 if isinstance(search, exp.Binary): 5788 search = exp.paren(search) 5789 5790 cond = exp.or_( 5791 decode_expr.eq(search), 5792 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5793 copy=False, 5794 ) 5795 ifs.append(exp.If(this=cond, true=result)) 5796 5797 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5798 return self.sql(case) 5799 5800 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5801 this = self.sql(expression, "this") 5802 this = self.seg(this, sep="") 5803 dimensions = self.expressions( 5804 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5805 ) 5806 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5807 metrics = self.expressions( 5808 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5809 ) 5810 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5811 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5812 facts = self.seg(f"FACTS {facts}") if facts else "" 5813 where = self.sql(expression, "where") 5814 where = self.seg(f"WHERE {where}") if where else "" 5815 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5816 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5817 5818 def getextract_sql(self, expression: exp.GetExtract) -> str: 5819 this = expression.this 5820 expr = expression.expression 5821 5822 if not this.type or not expression.type: 5823 import sqlglot.optimizer.annotate_types 5824 5825 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 5826 5827 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 5828 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5829 5830 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5831 5832 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5833 return self.sql( 5834 exp.DateAdd( 5835 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5836 expression=expression.this, 5837 unit=exp.var("DAY"), 5838 ) 5839 ) 5840 5841 def space_sql(self: Generator, expression: exp.Space) -> str: 5842 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5843 5844 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5845 return f"BUILD {self.sql(expression, 'this')}" 5846 5847 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5848 method = self.sql(expression, "method") 5849 kind = expression.args.get("kind") 5850 if not kind: 5851 return f"REFRESH {method}" 5852 5853 every = self.sql(expression, "every") 5854 unit = self.sql(expression, "unit") 5855 every = f" EVERY {every} {unit}" if every else "" 5856 starts = self.sql(expression, "starts") 5857 starts = f" STARTS {starts}" if starts else "" 5858 5859 return f"REFRESH {method} ON {kind}{every}{starts}" 5860 5861 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5862 self.unsupported("The model!attribute syntax is not supported") 5863 return "" 5864 5865 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5866 return self.func("DIRECTORY", expression.this) 5867 5868 def uuid_sql(self, expression: exp.Uuid) -> str: 5869 is_string = expression.args.get("is_string", False) 5870 uuid_func_sql = self.func("UUID") 5871 5872 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5873 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 5874 5875 return uuid_func_sql 5876 5877 def initcap_sql(self, expression: exp.Initcap) -> str: 5878 delimiters = expression.expression 5879 5880 if delimiters: 5881 # do not generate delimiters arg if we are round-tripping from default delimiters 5882 if ( 5883 delimiters.is_string 5884 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5885 ): 5886 delimiters = None 5887 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5888 self.unsupported("INITCAP does not support custom delimiters") 5889 delimiters = None 5890 5891 return self.func("INITCAP", expression.this, delimiters) 5892 5893 def localtime_sql(self, expression: exp.Localtime) -> str: 5894 this = expression.this 5895 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5896 5897 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 5898 this = expression.this 5899 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5900 5901 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5902 this = expression.this.name.upper() 5903 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5904 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5905 return "WEEK" 5906 5907 return self.func("WEEK", expression.this) 5908 5909 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5910 this = self.expressions(expression) 5911 charset = self.sql(expression, "charset") 5912 using = f" USING {charset}" if charset else "" 5913 return self.func(name, this + using) 5914 5915 def block_sql(self, expression: exp.Block) -> str: 5916 expressions = self.expressions(expression, sep="; ", flat=True) 5917 return f"{expressions}" if expressions else "" 5918 5919 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 5920 self.unsupported("Unsupported Stored Procedure syntax") 5921 return "" 5922 5923 def ifblock_sql(self, expression: exp.IfBlock) -> str: 5924 self.unsupported("Unsupported If block syntax") 5925 return "" 5926 5927 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 5928 self.unsupported("Unsupported While block syntax") 5929 return "" 5930 5931 def execute_sql(self, expression: exp.Execute) -> str: 5932 self.unsupported("Unsupported Execute syntax") 5933 return "" 5934 5935 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 5936 self.unsupported("Unsupported Execute syntax") 5937 return "" 5938 5939 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 5940 props = self.expressions(expression, sep=" ") 5941 return f"MODIFY {props}" 5942 5943 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 5944 kind = expression.args.get("kind") 5945 return f"USING {kind} {self.sql(expression, 'this')}" 5946 5947 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 5948 this = self.sql(expression, "this") 5949 to = self.sql(expression, "to") 5950 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 UNPIVOT aliases are Identifiers (False means they're Literals) 451 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 452 453 # What delimiter to use for separating JSON key/value pairs 454 JSON_KEY_VALUE_PAIR_SEP = ":" 455 456 # INSERT OVERWRITE TABLE x override 457 INSERT_OVERWRITE = " OVERWRITE TABLE" 458 459 # Whether the SELECT .. INTO syntax is used instead of CTAS 460 SUPPORTS_SELECT_INTO = False 461 462 # Whether UNLOGGED tables can be created 463 SUPPORTS_UNLOGGED_TABLES = False 464 465 # Whether the CREATE TABLE LIKE statement is supported 466 SUPPORTS_CREATE_TABLE_LIKE = True 467 468 # Whether ALTER TABLE ... MODIFY COLUMN column-redefinition syntax is supported 469 SUPPORTS_MODIFY_COLUMN = False 470 471 # Whether the LikeProperty needs to be specified inside of the schema clause 472 LIKE_PROPERTY_INSIDE_SCHEMA = False 473 474 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 475 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 476 MULTI_ARG_DISTINCT = True 477 478 # Whether the JSON extraction operators expect a value of type JSON 479 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 480 481 # Whether bracketed keys like ["foo"] are supported in JSON paths 482 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 483 484 # Whether to escape keys using single quotes in JSON paths 485 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 486 487 # The JSONPathPart expressions supported by this dialect 488 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 489 490 # Whether any(f(x) for x in array) can be implemented by this dialect 491 CAN_IMPLEMENT_ARRAY_ANY = False 492 493 # Whether the function TO_NUMBER is supported 494 SUPPORTS_TO_NUMBER = True 495 496 # Whether EXCLUDE in window specification is supported 497 SUPPORTS_WINDOW_EXCLUDE = False 498 499 # Whether or not set op modifiers apply to the outer set op or select. 500 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 501 # True means limit 1 happens after the set op, False means it it happens on y. 502 SET_OP_MODIFIERS = True 503 504 # Whether parameters from COPY statement are wrapped in parentheses 505 COPY_PARAMS_ARE_WRAPPED = True 506 507 # Whether values of params are set with "=" token or empty space 508 COPY_PARAMS_EQ_REQUIRED = False 509 510 # Whether COPY statement has INTO keyword 511 COPY_HAS_INTO_KEYWORD = True 512 513 # Whether the conditional TRY(expression) function is supported 514 TRY_SUPPORTED = True 515 516 # Whether the UESCAPE syntax in unicode strings is supported 517 SUPPORTS_UESCAPE = True 518 519 # Function used to replace escaped unicode codes in unicode strings 520 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 521 522 # The keyword to use when generating a star projection with excluded columns 523 STAR_EXCEPT = "EXCEPT" 524 525 # The HEX function name 526 HEX_FUNC = "HEX" 527 528 # The keywords to use when prefixing & separating WITH based properties 529 WITH_PROPERTIES_PREFIX = "WITH" 530 531 # Whether to quote the generated expression of exp.JsonPath 532 QUOTE_JSON_PATH = True 533 534 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 535 PAD_FILL_PATTERN_IS_REQUIRED = False 536 537 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 538 SUPPORTS_EXPLODING_PROJECTIONS = True 539 540 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 541 ARRAY_CONCAT_IS_VAR_LEN = True 542 543 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 544 SUPPORTS_CONVERT_TIMEZONE = False 545 546 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 547 SUPPORTS_MEDIAN = True 548 549 # Whether UNIX_SECONDS(timestamp) is supported 550 SUPPORTS_UNIX_SECONDS = False 551 552 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 553 ALTER_SET_WRAPPED = False 554 555 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 556 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 557 # TODO: The normalization should be done by default once we've tested it across all dialects. 558 NORMALIZE_EXTRACT_DATE_PARTS = False 559 560 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 561 PARSE_JSON_NAME: str | None = "PARSE_JSON" 562 563 # The function name of the exp.ArraySize expression 564 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 565 566 # The syntax to use when altering the type of a column 567 ALTER_SET_TYPE = "SET DATA TYPE" 568 569 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 570 # None -> Doesn't support it at all 571 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 572 # True (Postgres) -> Explicitly requires it 573 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 574 575 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 576 SUPPORTS_DECODE_CASE = True 577 578 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 579 SUPPORTS_BETWEEN_FLAGS = False 580 581 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 582 SUPPORTS_LIKE_QUANTIFIERS = True 583 584 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 585 MATCH_AGAINST_TABLE_PREFIX: str | None = None 586 587 # Whether to include the VARIABLE keyword for SET assignments 588 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 589 590 # The keyword to use for default value assignment in DECLARE statements 591 DECLARE_DEFAULT_ASSIGNMENT = "=" 592 593 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 594 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 595 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 596 UPDATE_STATEMENT_SUPPORTS_FROM = True 597 598 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 599 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 600 601 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 602 # - Snowflake: DROP ICEBERG TABLE a.b; 603 # - DuckDB: DROP TABLE a.b; 604 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 605 606 TYPE_MAPPING: t.ClassVar = { 607 exp.DType.DATETIME2: "TIMESTAMP", 608 exp.DType.NCHAR: "CHAR", 609 exp.DType.NVARCHAR: "VARCHAR", 610 exp.DType.MEDIUMTEXT: "TEXT", 611 exp.DType.LONGTEXT: "TEXT", 612 exp.DType.TINYTEXT: "TEXT", 613 exp.DType.BLOB: "VARBINARY", 614 exp.DType.MEDIUMBLOB: "BLOB", 615 exp.DType.LONGBLOB: "BLOB", 616 exp.DType.TINYBLOB: "BLOB", 617 exp.DType.INET: "INET", 618 exp.DType.ROWVERSION: "VARBINARY", 619 exp.DType.SMALLDATETIME: "TIMESTAMP", 620 } 621 622 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 623 624 TIME_PART_SINGULARS: t.ClassVar = { 625 "MICROSECONDS": "MICROSECOND", 626 "SECONDS": "SECOND", 627 "MINUTES": "MINUTE", 628 "HOURS": "HOUR", 629 "DAYS": "DAY", 630 "WEEKS": "WEEK", 631 "MONTHS": "MONTH", 632 "QUARTERS": "QUARTER", 633 "YEARS": "YEAR", 634 } 635 636 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 637 "cluster": lambda self, e: self.sql(e, "cluster"), 638 "distribute": lambda self, e: self.sql(e, "distribute"), 639 "sort": lambda self, e: self.sql(e, "sort"), 640 **AFTER_HAVING_MODIFIER_TRANSFORMS, 641 } 642 643 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 644 645 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 646 647 PARAMETER_TOKEN = "@" 648 NAMED_PLACEHOLDER_TOKEN = ":" 649 650 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 651 652 PROPERTIES_LOCATION: t.ClassVar = { 653 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 654 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 655 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 656 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 657 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 658 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 659 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 660 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 661 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 662 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 663 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 664 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 665 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 666 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 667 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 668 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 669 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 671 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 672 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 673 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 674 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 675 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 676 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 677 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 678 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 679 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 681 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 682 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 683 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 684 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 685 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 686 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 687 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 688 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 689 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 690 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 691 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 692 exp.HeapProperty: exp.Properties.Location.POST_WITH, 693 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 694 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 695 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 696 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 697 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 698 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 699 exp.JournalProperty: exp.Properties.Location.POST_NAME, 700 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 701 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 702 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 703 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 704 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 705 exp.LogProperty: exp.Properties.Location.POST_NAME, 706 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 707 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 708 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 709 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 710 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 711 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 712 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 713 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 714 exp.Order: exp.Properties.Location.POST_SCHEMA, 715 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 716 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 717 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 718 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 719 exp.Property: exp.Properties.Location.POST_WITH, 720 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 721 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 723 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 724 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 725 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 728 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 729 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 730 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 731 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 732 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 733 exp.Set: exp.Properties.Location.POST_SCHEMA, 734 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 735 exp.SetProperty: exp.Properties.Location.POST_CREATE, 736 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 737 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 738 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 739 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 740 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 745 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 746 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 747 exp.Tags: exp.Properties.Location.POST_WITH, 748 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 749 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 750 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 751 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 753 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 754 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 755 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 756 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 757 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 758 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 759 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 760 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 761 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 762 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 763 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 764 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 765 } 766 767 # Keywords that can't be used as unquoted identifier names 768 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 769 770 # Exprs whose comments are separated from them for better formatting 771 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 772 exp.Command, 773 exp.Create, 774 exp.Describe, 775 exp.Delete, 776 exp.Drop, 777 exp.From, 778 exp.Insert, 779 exp.Join, 780 exp.MultitableInserts, 781 exp.Order, 782 exp.Group, 783 exp.Having, 784 exp.Select, 785 exp.SetOperation, 786 exp.Update, 787 exp.Where, 788 exp.With, 789 ) 790 791 # Exprs that should not have their comments generated in maybe_comment 792 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 793 exp.Binary, 794 exp.SetOperation, 795 ) 796 797 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 798 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 799 exp.Column, 800 exp.Literal, 801 exp.Neg, 802 exp.Paren, 803 ) 804 805 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 806 exp.DType.NVARCHAR, 807 exp.DType.VARCHAR, 808 exp.DType.CHAR, 809 exp.DType.NCHAR, 810 } 811 812 # Exprs that need to have all CTEs under them bubbled up to them 813 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 814 815 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 816 817 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 818 819 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 820 821 __slots__ = ( 822 "pretty", 823 "identify", 824 "normalize", 825 "pad", 826 "_indent", 827 "normalize_functions", 828 "unsupported_level", 829 "max_unsupported", 830 "leading_comma", 831 "max_text_width", 832 "comments", 833 "dialect", 834 "unsupported_messages", 835 "_escaped_quote_end", 836 "_escaped_byte_quote_end", 837 "_escaped_identifier_end", 838 "_next_name", 839 "_identifier_start", 840 "_identifier_end", 841 "_quote_json_path_key_using_brackets", 842 "_dispatch", 843 ) 844 845 def __init__( 846 self, 847 pretty: bool | int | None = None, 848 identify: str | bool = False, 849 normalize: bool = False, 850 pad: int = 2, 851 indent: int = 2, 852 normalize_functions: str | bool | None = None, 853 unsupported_level: ErrorLevel = ErrorLevel.WARN, 854 max_unsupported: int = 3, 855 leading_comma: bool = False, 856 max_text_width: int = 80, 857 comments: bool = True, 858 dialect: DialectType = None, 859 ): 860 import sqlglot 861 import sqlglot.dialects.dialect 862 863 self.pretty = pretty if pretty is not None else sqlglot.pretty 864 self.identify = identify 865 self.normalize = normalize 866 self.pad = pad 867 self._indent = indent 868 self.unsupported_level = unsupported_level 869 self.max_unsupported = max_unsupported 870 self.leading_comma = leading_comma 871 self.max_text_width = max_text_width 872 self.comments = comments 873 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 874 875 # This is both a Dialect property and a Generator argument, so we prioritize the latter 876 self.normalize_functions = ( 877 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 878 ) 879 880 self.unsupported_messages: list[str] = [] 881 self._escaped_quote_end: str = ( 882 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 883 ) 884 self._escaped_byte_quote_end: str = ( 885 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 886 if self.dialect.BYTE_END 887 else "" 888 ) 889 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 890 891 self._next_name = name_sequence("_t") 892 893 self._identifier_start = self.dialect.IDENTIFIER_START 894 self._identifier_end = self.dialect.IDENTIFIER_END 895 896 self._quote_json_path_key_using_brackets = True 897 898 cls = type(self) 899 dispatch = _DISPATCH_CACHE.get(cls) 900 if dispatch is None: 901 dispatch = _build_dispatch(cls) 902 _DISPATCH_CACHE[cls] = dispatch 903 self._dispatch = dispatch 904 905 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 906 """ 907 Generates the SQL string corresponding to the given syntax tree. 908 909 Args: 910 expression: The syntax tree. 911 copy: Whether to copy the expression. The generator performs mutations so 912 it is safer to copy. 913 914 Returns: 915 The SQL string corresponding to `expression`. 916 """ 917 if copy: 918 expression = expression.copy() 919 920 expression = self.preprocess(expression) 921 922 self.unsupported_messages = [] 923 sql = self.sql(expression).strip() 924 925 if self.pretty: 926 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 927 928 if self.unsupported_level == ErrorLevel.IGNORE: 929 return sql 930 931 if self.unsupported_level == ErrorLevel.WARN: 932 for msg in self.unsupported_messages: 933 logger.warning(msg) 934 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 935 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 936 937 return sql 938 939 def preprocess(self, expression: exp.Expr) -> exp.Expr: 940 """Apply generic preprocessing transformations to a given expression.""" 941 expression = self._move_ctes_to_top_level(expression) 942 943 if self.ENSURE_BOOLS: 944 import sqlglot.transforms 945 946 expression = sqlglot.transforms.ensure_bools(expression) 947 948 return expression 949 950 def _move_ctes_to_top_level(self, expression: E) -> E: 951 if ( 952 not expression.parent 953 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 954 and any(node.parent is not expression for node in expression.find_all(exp.With)) 955 ): 956 import sqlglot.transforms 957 958 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 959 return expression 960 961 def unsupported(self, message: str) -> None: 962 if self.unsupported_level == ErrorLevel.IMMEDIATE: 963 raise UnsupportedError(message) 964 self.unsupported_messages.append(message) 965 966 def sep(self, sep: str = " ") -> str: 967 return f"{sep.strip()}\n" if self.pretty else sep 968 969 def seg(self, sql: str, sep: str = " ") -> str: 970 return f"{self.sep(sep)}{sql}" 971 972 def sanitize_comment(self, comment: str) -> str: 973 comment = " " + comment if comment[0].strip() else comment 974 comment = comment + " " if comment[-1].strip() else comment 975 976 # Escape block comment markers to prevent premature closure or unintended nesting. 977 # This is necessary because single-line comments (--) are converted to block comments 978 # (/* */) on output, and any */ in the original text would close the comment early. 979 comment = comment.replace("*/", "* /").replace("/*", "/ *") 980 981 return comment 982 983 def maybe_comment( 984 self, 985 sql: str, 986 expression: exp.Expr | None = None, 987 comments: list[str] | None = None, 988 separated: bool = False, 989 ) -> str: 990 comments = ( 991 ((expression and expression.comments) if comments is None else comments) # type: ignore 992 if self.comments 993 else None 994 ) 995 996 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 997 return sql 998 999 comments_sql = " ".join( 1000 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 1001 ) 1002 1003 if not comments_sql: 1004 return sql 1005 1006 comments_sql = self._replace_line_breaks(comments_sql) 1007 1008 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1009 return ( 1010 f"{self.sep()}{comments_sql}{sql}" 1011 if not sql or sql[0].isspace() 1012 else f"{comments_sql}{self.sep()}{sql}" 1013 ) 1014 1015 return f"{sql} {comments_sql}" 1016 1017 def wrap(self, expression: exp.Expr | str) -> str: 1018 this_sql = ( 1019 self.sql(expression) 1020 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1021 else self.sql(expression, "this") 1022 ) 1023 if not this_sql: 1024 return "()" 1025 1026 this_sql = self.indent(this_sql, level=1, pad=0) 1027 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1028 1029 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1030 original = self.identify 1031 self.identify = False 1032 result = func(*args, **kwargs) 1033 self.identify = original 1034 return result 1035 1036 def normalize_func(self, name: str) -> str: 1037 if self.normalize_functions == "upper" or self.normalize_functions is True: 1038 return name.upper() 1039 if self.normalize_functions == "lower": 1040 return name.lower() 1041 return name 1042 1043 def indent( 1044 self, 1045 sql: str, 1046 level: int = 0, 1047 pad: int | None = None, 1048 skip_first: bool = False, 1049 skip_last: bool = False, 1050 ) -> str: 1051 if not self.pretty or not sql: 1052 return sql 1053 1054 pad = self.pad if pad is None else pad 1055 lines = sql.split("\n") 1056 1057 return "\n".join( 1058 ( 1059 line 1060 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1061 else f"{' ' * (level * self._indent + pad)}{line}" 1062 ) 1063 for i, line in enumerate(lines) 1064 ) 1065 1066 def sql( 1067 self, 1068 expression: str | exp.Expr | None, 1069 key: str | None = None, 1070 comment: bool = True, 1071 ) -> str: 1072 if not expression: 1073 return "" 1074 1075 if isinstance(expression, str): 1076 return expression 1077 1078 if key: 1079 value = expression.args.get(key) 1080 if value: 1081 return self.sql(value) 1082 return "" 1083 1084 handler = self._dispatch.get(expression.__class__) 1085 1086 if handler: 1087 sql = handler(self, expression) 1088 elif isinstance(expression, exp.Func): 1089 sql = self.function_fallback_sql(expression) 1090 elif isinstance(expression, exp.Property): 1091 sql = self.property_sql(expression) 1092 else: 1093 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1094 1095 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1096 1097 def uncache_sql(self, expression: exp.Uncache) -> str: 1098 table = self.sql(expression, "this") 1099 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1100 return f"UNCACHE TABLE{exists_sql} {table}" 1101 1102 def cache_sql(self, expression: exp.Cache) -> str: 1103 lazy = " LAZY" if expression.args.get("lazy") else "" 1104 table = self.sql(expression, "this") 1105 options = expression.args.get("options") 1106 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1107 sql = self.sql(expression, "expression") 1108 sql = f" AS{self.sep()}{sql}" if sql else "" 1109 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1110 return self.prepend_ctes(expression, sql) 1111 1112 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1113 default = "DEFAULT " if expression.args.get("default") else "" 1114 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1115 1116 def column_parts(self, expression: exp.Column) -> str: 1117 return ".".join( 1118 self.sql(part) 1119 for part in ( 1120 expression.args.get("catalog"), 1121 expression.args.get("db"), 1122 expression.args.get("table"), 1123 expression.args.get("this"), 1124 ) 1125 if part 1126 ) 1127 1128 def column_sql(self, expression: exp.Column) -> str: 1129 join_mark = " (+)" if expression.args.get("join_mark") else "" 1130 1131 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1132 join_mark = "" 1133 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1134 1135 return f"{self.column_parts(expression)}{join_mark}" 1136 1137 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1138 return self.column_sql(expression) 1139 1140 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1141 this = self.sql(expression, "this") 1142 this = f" {this}" if this else "" 1143 position = self.sql(expression, "position") 1144 return f"{position}{this}" 1145 1146 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1147 column = self.sql(expression, "this") 1148 kind = self.sql(expression, "kind") 1149 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1150 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1151 kind = f"{sep}{kind}" if kind else "" 1152 constraints = f" {constraints}" if constraints else "" 1153 position = self.sql(expression, "position") 1154 position = f" {position}" if position else "" 1155 1156 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1157 kind = "" 1158 1159 return f"{exists}{column}{kind}{constraints}{position}" 1160 1161 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1162 this = self.sql(expression, "this") 1163 kind_sql = self.sql(expression, "kind").strip() 1164 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1165 1166 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1167 this = self.sql(expression, "this") 1168 if expression.args.get("not_null"): 1169 persisted = " PERSISTED NOT NULL" 1170 elif expression.args.get("persisted"): 1171 persisted = " PERSISTED" 1172 else: 1173 persisted = "" 1174 1175 return f"AS {this}{persisted}" 1176 1177 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1178 return self.token_sql(TokenType.AUTO_INCREMENT) 1179 1180 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1181 if isinstance(expression.this, list): 1182 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1183 else: 1184 this = self.sql(expression, "this") 1185 1186 return f"COMPRESS {this}" 1187 1188 def generatedasidentitycolumnconstraint_sql( 1189 self, expression: exp.GeneratedAsIdentityColumnConstraint 1190 ) -> str: 1191 this = "" 1192 if expression.this is not None: 1193 on_null = " ON NULL" if expression.args.get("on_null") else "" 1194 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1195 1196 start = expression.args.get("start") 1197 start = f"START WITH {start}" if start else "" 1198 increment = expression.args.get("increment") 1199 increment = f" INCREMENT BY {increment}" if increment else "" 1200 minvalue = expression.args.get("minvalue") 1201 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1202 maxvalue = expression.args.get("maxvalue") 1203 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1204 cycle = expression.args.get("cycle") 1205 cycle_sql = "" 1206 1207 if cycle is not None: 1208 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1209 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1210 1211 sequence_opts = "" 1212 if start or increment or cycle_sql: 1213 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1214 sequence_opts = f" ({sequence_opts.strip()})" 1215 1216 expr = self.sql(expression, "expression") 1217 expr = f"({expr})" if expr else "IDENTITY" 1218 1219 return f"GENERATED{this} AS {expr}{sequence_opts}" 1220 1221 def generatedasrowcolumnconstraint_sql( 1222 self, expression: exp.GeneratedAsRowColumnConstraint 1223 ) -> str: 1224 start = "START" if expression.args.get("start") else "END" 1225 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1226 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1227 1228 def periodforsystemtimeconstraint_sql( 1229 self, expression: exp.PeriodForSystemTimeConstraint 1230 ) -> str: 1231 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1232 1233 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1234 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1235 1236 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1237 desc = expression.args.get("desc") 1238 if desc is not None: 1239 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1240 options = self.expressions(expression, key="options", flat=True, sep=" ") 1241 options = f" {options}" if options else "" 1242 return f"PRIMARY KEY{options}" 1243 1244 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1245 this = self.sql(expression, "this") 1246 this = f" {this}" if this else "" 1247 index_type = expression.args.get("index_type") 1248 index_type = f" USING {index_type}" if index_type else "" 1249 on_conflict = self.sql(expression, "on_conflict") 1250 on_conflict = f" {on_conflict}" if on_conflict else "" 1251 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1252 options = self.expressions(expression, key="options", flat=True, sep=" ") 1253 options = f" {options}" if options else "" 1254 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1255 1256 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1257 input_ = expression.args.get("input_") 1258 output = expression.args.get("output") 1259 variadic = expression.args.get("variadic") 1260 1261 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1262 if variadic: 1263 return "VARIADIC" 1264 1265 if input_ and output: 1266 return f"IN{self.INOUT_SEPARATOR}OUT" 1267 if input_: 1268 return "IN" 1269 if output: 1270 return "OUT" 1271 1272 return "" 1273 1274 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1275 return self.sql(expression, "this") 1276 1277 def create_sql(self, expression: exp.Create) -> str: 1278 kind = self.sql(expression, "kind") 1279 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1280 1281 properties = expression.args.get("properties") 1282 1283 if ( 1284 kind == "TRIGGER" 1285 and properties 1286 and properties.expressions 1287 and isinstance(properties.expressions[0], exp.TriggerProperties) 1288 and properties.expressions[0].args.get("constraint") 1289 ): 1290 kind = f"CONSTRAINT {kind}" 1291 1292 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1293 1294 this = self.createable_sql(expression, properties_locs) 1295 1296 properties_sql = "" 1297 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1298 exp.Properties.Location.POST_WITH 1299 ): 1300 props_ast = exp.Properties( 1301 expressions=[ 1302 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1303 *properties_locs[exp.Properties.Location.POST_WITH], 1304 ] 1305 ) 1306 props_ast.parent = expression 1307 properties_sql = self.sql(props_ast) 1308 1309 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1310 properties_sql = self.sep() + properties_sql 1311 elif not self.pretty: 1312 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1313 properties_sql = f" {properties_sql}" 1314 1315 begin = " BEGIN" if expression.args.get("begin") else "" 1316 1317 expression_sql = self.sql(expression, "expression") 1318 if expression_sql: 1319 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1320 1321 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1322 postalias_props_sql = "" 1323 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1324 postalias_props_sql = self.properties( 1325 exp.Properties( 1326 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1327 ), 1328 wrapped=False, 1329 ) 1330 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1331 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1332 1333 postindex_props_sql = "" 1334 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1335 postindex_props_sql = self.properties( 1336 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1337 wrapped=False, 1338 prefix=" ", 1339 ) 1340 1341 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1342 indexes = f" {indexes}" if indexes else "" 1343 index_sql = indexes + postindex_props_sql 1344 1345 replace = " OR REPLACE" if expression.args.get("replace") else "" 1346 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1347 unique = " UNIQUE" if expression.args.get("unique") else "" 1348 1349 clustered = expression.args.get("clustered") 1350 if clustered is None: 1351 clustered_sql = "" 1352 elif clustered: 1353 clustered_sql = " CLUSTERED COLUMNSTORE" 1354 else: 1355 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1356 1357 postcreate_props_sql = "" 1358 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1359 postcreate_props_sql = self.properties( 1360 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1361 sep=" ", 1362 prefix=" ", 1363 wrapped=False, 1364 ) 1365 1366 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1367 1368 postexpression_props_sql = "" 1369 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1370 postexpression_props_sql = self.properties( 1371 exp.Properties( 1372 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1373 ), 1374 sep=" ", 1375 prefix=" ", 1376 wrapped=False, 1377 ) 1378 1379 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1380 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1381 no_schema_binding = ( 1382 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1383 ) 1384 1385 clone = self.sql(expression, "clone") 1386 clone = f" {clone}" if clone else "" 1387 1388 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1389 properties_expression = f"{expression_sql}{properties_sql}" 1390 else: 1391 properties_expression = f"{properties_sql}{expression_sql}" 1392 1393 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1394 return self.prepend_ctes(expression, expression_sql) 1395 1396 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1397 start = self.sql(expression, "start") 1398 start = f"START WITH {start}" if start else "" 1399 increment = self.sql(expression, "increment") 1400 increment = f" INCREMENT BY {increment}" if increment else "" 1401 minvalue = self.sql(expression, "minvalue") 1402 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1403 maxvalue = self.sql(expression, "maxvalue") 1404 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1405 owned = self.sql(expression, "owned") 1406 owned = f" OWNED BY {owned}" if owned else "" 1407 1408 cache = expression.args.get("cache") 1409 if cache is None: 1410 cache_str = "" 1411 elif cache is True: 1412 cache_str = " CACHE" 1413 else: 1414 cache_str = f" CACHE {cache}" 1415 1416 options = self.expressions(expression, key="options", flat=True, sep=" ") 1417 options = f" {options}" if options else "" 1418 1419 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1420 1421 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1422 timing = expression.args.get("timing", "") 1423 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1424 timing_events = f"{timing} {events}".strip() if timing or events else "" 1425 1426 parts = [timing_events, "ON", self.sql(expression, "table")] 1427 1428 if referenced_table := expression.args.get("referenced_table"): 1429 parts.extend(["FROM", self.sql(referenced_table)]) 1430 1431 if deferrable := expression.args.get("deferrable"): 1432 parts.append(deferrable) 1433 1434 if initially := expression.args.get("initially"): 1435 parts.append(f"INITIALLY {initially}") 1436 1437 if referencing := expression.args.get("referencing"): 1438 parts.append(self.sql(referencing)) 1439 1440 if for_each := expression.args.get("for_each"): 1441 parts.append(f"FOR EACH {for_each}") 1442 1443 if when := expression.args.get("when"): 1444 parts.append(f"WHEN ({self.sql(when)})") 1445 1446 parts.append(self.sql(expression, "execute")) 1447 1448 return self.sep().join(parts) 1449 1450 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1451 parts = [] 1452 1453 if old_alias := expression.args.get("old"): 1454 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1455 1456 if new_alias := expression.args.get("new"): 1457 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1458 1459 return f"REFERENCING {' '.join(parts)}" 1460 1461 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1462 columns = expression.args.get("columns") 1463 if columns: 1464 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1465 1466 return self.sql(expression, "this") 1467 1468 def clone_sql(self, expression: exp.Clone) -> str: 1469 this = self.sql(expression, "this") 1470 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1471 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1472 return f"{shallow}{keyword} {this}" 1473 1474 def describe_sql(self, expression: exp.Describe) -> str: 1475 style = expression.args.get("style") 1476 style = f" {style}" if style else "" 1477 partition = self.sql(expression, "partition") 1478 partition = f" {partition}" if partition else "" 1479 format = self.sql(expression, "format") 1480 format = f" {format}" if format else "" 1481 as_json = " AS JSON" if expression.args.get("as_json") else "" 1482 1483 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1484 1485 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1486 tag = self.sql(expression, "tag") 1487 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1488 1489 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1490 with_ = self.sql(expression, "with_") 1491 if with_: 1492 sql = f"{with_}{self.sep()}{sql}" 1493 return sql 1494 1495 def with_sql(self, expression: exp.With) -> str: 1496 sql = self.expressions(expression, flat=True) 1497 recursive = ( 1498 "RECURSIVE " 1499 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1500 else "" 1501 ) 1502 search = self.sql(expression, "search") 1503 search = f" {search}" if search else "" 1504 1505 return f"WITH {recursive}{sql}{search}" 1506 1507 def cte_sql(self, expression: exp.CTE) -> str: 1508 alias = expression.args.get("alias") 1509 if alias: 1510 alias.add_comments(expression.pop_comments()) 1511 1512 alias_sql = self.sql(expression, "alias") 1513 1514 materialized = expression.args.get("materialized") 1515 if materialized is False: 1516 materialized = "NOT MATERIALIZED " 1517 elif materialized: 1518 materialized = "MATERIALIZED " 1519 1520 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1521 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1522 1523 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1524 1525 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1526 alias = self.sql(expression, "this") 1527 columns = self.expressions(expression, key="columns", flat=True) 1528 columns = f"({columns})" if columns else "" 1529 1530 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1531 columns = "" 1532 self.unsupported("Named columns are not supported in table alias.") 1533 1534 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1535 alias = self._next_name() 1536 1537 return f"{alias}{columns}" 1538 1539 def bitstring_sql(self, expression: exp.BitString) -> str: 1540 this = self.sql(expression, "this") 1541 if self.dialect.BIT_START: 1542 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1543 return f"{int(this, 2)}" 1544 1545 def hexstring_sql( 1546 self, expression: exp.HexString, binary_function_repr: str | None = None 1547 ) -> str: 1548 this = self.sql(expression, "this") 1549 is_integer_type = expression.args.get("is_integer") 1550 1551 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1552 not self.dialect.HEX_START and not binary_function_repr 1553 ): 1554 # Integer representation will be returned if: 1555 # - The read dialect treats the hex value as integer literal but not the write 1556 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1557 return f"{int(this, 16)}" 1558 1559 if not is_integer_type: 1560 # Read dialect treats the hex value as BINARY/BLOB 1561 if binary_function_repr: 1562 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1563 return self.func(binary_function_repr, exp.Literal.string(this)) 1564 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1565 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1566 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1567 1568 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1569 1570 def bytestring_sql(self, expression: exp.ByteString) -> str: 1571 this = self.sql(expression, "this") 1572 if self.dialect.BYTE_START: 1573 escaped_byte_string = self.escape_str( 1574 this, 1575 escape_backslash=False, 1576 delimiter=self.dialect.BYTE_END, 1577 escaped_delimiter=self._escaped_byte_quote_end, 1578 is_byte_string=True, 1579 ) 1580 is_bytes = expression.args.get("is_bytes", False) 1581 delimited_byte_string = ( 1582 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1583 ) 1584 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1585 return self.sql( 1586 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1587 ) 1588 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1589 return self.sql( 1590 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1591 ) 1592 1593 return delimited_byte_string 1594 return this 1595 1596 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1597 this = self.sql(expression, "this") 1598 escape = expression.args.get("escape") 1599 1600 if self.dialect.UNICODE_START: 1601 escape_substitute = r"\\\1" 1602 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1603 else: 1604 escape_substitute = r"\\u\1" 1605 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1606 1607 if escape: 1608 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1609 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1610 else: 1611 escape_pattern = ESCAPED_UNICODE_RE 1612 escape_sql = "" 1613 1614 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1615 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1616 1617 return f"{left_quote}{this}{right_quote}{escape_sql}" 1618 1619 def rawstring_sql(self, expression: exp.RawString) -> str: 1620 string = expression.this 1621 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1622 string = string.replace("\\", "\\\\") 1623 1624 string = self.escape_str(string, escape_backslash=False) 1625 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1626 1627 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1628 this = self.sql(expression, "this") 1629 specifier = self.sql(expression, "expression") 1630 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1631 return f"{this}{specifier}" 1632 1633 def datatype_sql(self, expression: exp.DataType) -> str: 1634 nested = "" 1635 values = "" 1636 1637 expr_nested = expression.args.get("nested") 1638 interior = ( 1639 self.expressions( 1640 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1641 ) 1642 if expr_nested and self.pretty 1643 else self.expressions(expression, flat=True) 1644 ) 1645 1646 type_value = expression.this 1647 if type_value in self.UNSUPPORTED_TYPES: 1648 self.unsupported( 1649 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1650 ) 1651 1652 type_sql: t.Any = "" 1653 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1654 type_sql = self.sql(expression, "kind") 1655 elif type_value == exp.DType.CHARACTER_SET: 1656 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1657 else: 1658 type_sql = ( 1659 self.TYPE_MAPPING.get(type_value, type_value.value) 1660 if isinstance(type_value, exp.DType) 1661 else type_value 1662 ) 1663 1664 if interior: 1665 if expr_nested: 1666 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1667 if expression.args.get("values") is not None: 1668 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1669 values = self.expressions(expression, key="values", flat=True) 1670 values = f"{delimiters[0]}{values}{delimiters[1]}" 1671 elif type_value == exp.DType.INTERVAL: 1672 nested = f" {interior}" 1673 else: 1674 nested = f"({interior})" 1675 1676 type_sql = f"{type_sql}{nested}{values}" 1677 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1678 exp.DType.TIMETZ, 1679 exp.DType.TIMESTAMPTZ, 1680 ): 1681 type_sql = f"{type_sql} WITH TIME ZONE" 1682 1683 return type_sql 1684 1685 def directory_sql(self, expression: exp.Directory) -> str: 1686 local = "LOCAL " if expression.args.get("local") else "" 1687 row_format = self.sql(expression, "row_format") 1688 row_format = f" {row_format}" if row_format else "" 1689 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1690 1691 def delete_sql(self, expression: exp.Delete) -> str: 1692 hint = self.sql(expression, "hint") 1693 this = self.sql(expression, "this") 1694 this = f" FROM {this}" if this else "" 1695 using = self.expressions(expression, key="using") 1696 using = f" USING {using}" if using else "" 1697 cluster = self.sql(expression, "cluster") 1698 cluster = f" {cluster}" if cluster else "" 1699 where = self.sql(expression, "where") 1700 returning = self.sql(expression, "returning") 1701 order = self.sql(expression, "order") 1702 limit = self.sql(expression, "limit") 1703 tables = self.expressions(expression, key="tables") 1704 tables = f" {tables}" if tables else "" 1705 if self.RETURNING_END: 1706 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1707 else: 1708 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1709 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1710 1711 def drop_sql(self, expression: exp.Drop) -> str: 1712 this = self.sql(expression, "this") 1713 expressions = self.expressions(expression, flat=True) 1714 expressions = f" ({expressions})" if expressions else "" 1715 kind = expression.args["kind"] 1716 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1717 iceberg = ( 1718 " ICEBERG" 1719 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1720 else "" 1721 ) 1722 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1723 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1724 on_cluster = self.sql(expression, "cluster") 1725 on_cluster = f" {on_cluster}" if on_cluster else "" 1726 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1727 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1728 cascade = " CASCADE" if expression.args.get("cascade") else "" 1729 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1730 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1731 purge = " PURGE" if expression.args.get("purge") else "" 1732 sync = " SYNC" if expression.args.get("sync") else "" 1733 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}" 1734 1735 def set_operation(self, expression: exp.SetOperation) -> str: 1736 op_type = type(expression) 1737 op_name = op_type.key.upper() 1738 1739 distinct = expression.args.get("distinct") 1740 if ( 1741 distinct is False 1742 and op_type in (exp.Except, exp.Intersect) 1743 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1744 ): 1745 self.unsupported(f"{op_name} ALL is not supported") 1746 1747 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1748 1749 if distinct is None: 1750 distinct = default_distinct 1751 if distinct is None: 1752 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1753 1754 if distinct is default_distinct: 1755 distinct_or_all = "" 1756 else: 1757 distinct_or_all = " DISTINCT" if distinct else " ALL" 1758 1759 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1760 side_kind = f"{side_kind} " if side_kind else "" 1761 1762 by_name = " BY NAME" if expression.args.get("by_name") else "" 1763 on = self.expressions(expression, key="on", flat=True) 1764 on = f" ON ({on})" if on else "" 1765 1766 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1767 1768 def set_operations(self, expression: exp.SetOperation) -> str: 1769 if not self.SET_OP_MODIFIERS: 1770 limit = expression.args.get("limit") 1771 order = expression.args.get("order") 1772 1773 if limit or order: 1774 select = self._move_ctes_to_top_level( 1775 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1776 ) 1777 1778 if limit: 1779 select = select.limit(limit.pop(), copy=False) 1780 if order: 1781 select = select.order_by(order.pop(), copy=False) 1782 return self.sql(select) 1783 1784 sqls: list[str] = [] 1785 stack: list[str | exp.Expr] = [expression] 1786 1787 while stack: 1788 node = stack.pop() 1789 1790 if isinstance(node, exp.SetOperation): 1791 stack.append(node.expression) 1792 stack.append( 1793 self.maybe_comment( 1794 self.set_operation(node), comments=node.comments, separated=True 1795 ) 1796 ) 1797 stack.append(node.this) 1798 else: 1799 sqls.append(self.sql(node)) 1800 1801 this = self.sep().join(sqls) 1802 this = self.query_modifiers(expression, this) 1803 return self.prepend_ctes(expression, this) 1804 1805 def fetch_sql(self, expression: exp.Fetch) -> str: 1806 direction = expression.args.get("direction") 1807 direction = f" {direction}" if direction else "" 1808 count = self.sql(expression, "count") 1809 count = f" {count}" if count else "" 1810 limit_options = self.sql(expression, "limit_options") 1811 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1812 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1813 1814 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1815 percent = " PERCENT" if expression.args.get("percent") else "" 1816 rows = " ROWS" if expression.args.get("rows") else "" 1817 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1818 if not with_ties and rows: 1819 with_ties = " ONLY" 1820 return f"{percent}{rows}{with_ties}" 1821 1822 def filter_sql(self, expression: exp.Filter) -> str: 1823 if self.AGGREGATE_FILTER_SUPPORTED: 1824 this = self.sql(expression, "this") 1825 where = self.sql(expression, "expression").strip() 1826 return f"{this} FILTER({where})" 1827 1828 agg = expression.this 1829 agg_arg = agg.this 1830 cond = expression.expression.this 1831 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1832 return self.sql(agg) 1833 1834 def hint_sql(self, expression: exp.Hint) -> str: 1835 if not self.QUERY_HINTS: 1836 self.unsupported("Hints are not supported") 1837 return "" 1838 1839 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1840 1841 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1842 using = self.sql(expression, "using") 1843 using = f" USING {using}" if using else "" 1844 columns = self.expressions(expression, key="columns", flat=True) 1845 columns = f"({columns})" if columns else "" 1846 partition_by = self.expressions(expression, key="partition_by", flat=True) 1847 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1848 where = self.sql(expression, "where") 1849 include = self.expressions(expression, key="include", flat=True) 1850 if include: 1851 include = f" INCLUDE ({include})" 1852 with_storage = self.expressions(expression, key="with_storage", flat=True) 1853 with_storage = f" WITH ({with_storage})" if with_storage else "" 1854 tablespace = self.sql(expression, "tablespace") 1855 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1856 on = self.sql(expression, "on") 1857 on = f" ON {on}" if on else "" 1858 1859 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1860 1861 def index_sql(self, expression: exp.Index) -> str: 1862 unique = "UNIQUE " if expression.args.get("unique") else "" 1863 primary = "PRIMARY " if expression.args.get("primary") else "" 1864 amp = "AMP " if expression.args.get("amp") else "" 1865 name = self.sql(expression, "this") 1866 name = f"{name} " if name else "" 1867 table = self.sql(expression, "table") 1868 table = f"{self.INDEX_ON} {table}" if table else "" 1869 1870 index = "INDEX " if not table else "" 1871 1872 params = self.sql(expression, "params") 1873 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1874 1875 def identifier_sql(self, expression: exp.Identifier) -> str: 1876 text = expression.name 1877 lower = text.lower() 1878 quoted = expression.quoted 1879 text = lower if self.normalize and not quoted else text 1880 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1881 if ( 1882 quoted 1883 or self.dialect.can_quote(expression, self.identify) 1884 or lower in self.RESERVED_KEYWORDS 1885 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1886 ): 1887 text = ( 1888 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1889 ) 1890 return text 1891 1892 def hex_sql(self, expression: exp.Hex) -> str: 1893 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1894 if self.dialect.HEX_LOWERCASE: 1895 text = self.func("LOWER", text) 1896 1897 return text 1898 1899 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1900 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1901 if not self.dialect.HEX_LOWERCASE: 1902 text = self.func("LOWER", text) 1903 return text 1904 1905 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1906 input_format = self.sql(expression, "input_format") 1907 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1908 output_format = self.sql(expression, "output_format") 1909 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1910 return self.sep().join((input_format, output_format)) 1911 1912 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1913 string = self.sql(exp.Literal.string(expression.name)) 1914 return f"{prefix}{string}" 1915 1916 def partition_sql(self, expression: exp.Partition) -> str: 1917 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1918 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1919 1920 def properties_sql(self, expression: exp.Properties) -> str: 1921 root_properties = [] 1922 with_properties = [] 1923 1924 for p in expression.expressions: 1925 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1926 if p_loc == exp.Properties.Location.POST_WITH: 1927 with_properties.append(p) 1928 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1929 root_properties.append(p) 1930 1931 root_props_ast = exp.Properties(expressions=root_properties) 1932 root_props_ast.parent = expression.parent 1933 1934 with_props_ast = exp.Properties(expressions=with_properties) 1935 with_props_ast.parent = expression.parent 1936 1937 root_props = self.root_properties(root_props_ast) 1938 with_props = self.with_properties(with_props_ast) 1939 1940 if root_props and with_props and not self.pretty: 1941 with_props = " " + with_props 1942 1943 return root_props + with_props 1944 1945 def root_properties(self, properties: exp.Properties) -> str: 1946 if properties.expressions: 1947 return self.expressions(properties, indent=False, sep=" ") 1948 return "" 1949 1950 def properties( 1951 self, 1952 properties: exp.Properties, 1953 prefix: str = "", 1954 sep: str = ", ", 1955 suffix: str = "", 1956 wrapped: bool = True, 1957 ) -> str: 1958 if properties.expressions: 1959 expressions = self.expressions(properties, sep=sep, indent=False) 1960 if expressions: 1961 expressions = self.wrap(expressions) if wrapped else expressions 1962 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1963 return "" 1964 1965 def with_properties(self, properties: exp.Properties) -> str: 1966 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1967 1968 def locate_properties(self, properties: exp.Properties) -> defaultdict: 1969 properties_locs = defaultdict(list) 1970 for p in properties.expressions: 1971 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1972 if p_loc != exp.Properties.Location.UNSUPPORTED: 1973 properties_locs[p_loc].append(p) 1974 else: 1975 self.unsupported(f"Unsupported property {p.key}") 1976 1977 return properties_locs 1978 1979 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1980 if isinstance(expression.this, exp.Dot): 1981 return self.sql(expression, "this") 1982 return f"'{expression.name}'" if string_key else expression.name 1983 1984 def property_sql(self, expression: exp.Property) -> str: 1985 property_cls = expression.__class__ 1986 if property_cls == exp.Property: 1987 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1988 1989 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1990 if not property_name: 1991 self.unsupported(f"Unsupported property {expression.key}") 1992 1993 return f"{property_name}={self.sql(expression, 'this')}" 1994 1995 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 1996 return f"UUID {self.sql(expression, 'this')}" 1997 1998 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1999 if self.SUPPORTS_CREATE_TABLE_LIKE: 2000 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2001 options = f" {options}" if options else "" 2002 2003 like = f"LIKE {self.sql(expression, 'this')}{options}" 2004 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2005 like = f"({like})" 2006 2007 return like 2008 2009 if expression.expressions: 2010 self.unsupported("Transpilation of LIKE property options is unsupported") 2011 2012 select = exp.select("*").from_(expression.this).limit(0) 2013 return f"AS {self.sql(select)}" 2014 2015 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2016 no = "NO " if expression.args.get("no") else "" 2017 protection = " PROTECTION" if expression.args.get("protection") else "" 2018 return f"{no}FALLBACK{protection}" 2019 2020 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2021 no = "NO " if expression.args.get("no") else "" 2022 local = expression.args.get("local") 2023 local = f"{local} " if local else "" 2024 dual = "DUAL " if expression.args.get("dual") else "" 2025 before = "BEFORE " if expression.args.get("before") else "" 2026 after = "AFTER " if expression.args.get("after") else "" 2027 return f"{no}{local}{dual}{before}{after}JOURNAL" 2028 2029 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2030 freespace = self.sql(expression, "this") 2031 percent = " PERCENT" if expression.args.get("percent") else "" 2032 return f"FREESPACE={freespace}{percent}" 2033 2034 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2035 if expression.args.get("default"): 2036 property = "DEFAULT" 2037 elif expression.args.get("on"): 2038 property = "ON" 2039 else: 2040 property = "OFF" 2041 return f"CHECKSUM={property}" 2042 2043 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2044 if expression.args.get("no"): 2045 return "NO MERGEBLOCKRATIO" 2046 if expression.args.get("default"): 2047 return "DEFAULT MERGEBLOCKRATIO" 2048 2049 percent = " PERCENT" if expression.args.get("percent") else "" 2050 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2051 2052 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2053 expressions = self.expressions(expression, flat=True) 2054 expressions = f"({expressions})" if expressions else "" 2055 return f"USING {self.sql(expression, 'this')}{expressions}" 2056 2057 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2058 default = expression.args.get("default") 2059 minimum = expression.args.get("minimum") 2060 maximum = expression.args.get("maximum") 2061 if default or minimum or maximum: 2062 if default: 2063 prop = "DEFAULT" 2064 elif minimum: 2065 prop = "MINIMUM" 2066 else: 2067 prop = "MAXIMUM" 2068 return f"{prop} DATABLOCKSIZE" 2069 units = expression.args.get("units") 2070 units = f" {units}" if units else "" 2071 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2072 2073 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2074 autotemp = expression.args.get("autotemp") 2075 always = expression.args.get("always") 2076 default = expression.args.get("default") 2077 manual = expression.args.get("manual") 2078 never = expression.args.get("never") 2079 2080 if autotemp is not None: 2081 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2082 elif always: 2083 prop = "ALWAYS" 2084 elif default: 2085 prop = "DEFAULT" 2086 elif manual: 2087 prop = "MANUAL" 2088 elif never: 2089 prop = "NEVER" 2090 return f"BLOCKCOMPRESSION={prop}" 2091 2092 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2093 no = expression.args.get("no") 2094 no = " NO" if no else "" 2095 concurrent = expression.args.get("concurrent") 2096 concurrent = " CONCURRENT" if concurrent else "" 2097 target = self.sql(expression, "target") 2098 target = f" {target}" if target else "" 2099 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2100 2101 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2102 if isinstance(expression.this, list): 2103 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2104 if expression.this: 2105 modulus = self.sql(expression, "this") 2106 remainder = self.sql(expression, "expression") 2107 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2108 2109 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2110 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2111 return f"FROM ({from_expressions}) TO ({to_expressions})" 2112 2113 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2114 this = self.sql(expression, "this") 2115 2116 for_values_or_default = expression.expression 2117 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2118 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2119 else: 2120 for_values_or_default = " DEFAULT" 2121 2122 return f"PARTITION OF {this}{for_values_or_default}" 2123 2124 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2125 kind = expression.args.get("kind") 2126 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2127 for_or_in = expression.args.get("for_or_in") 2128 for_or_in = f" {for_or_in}" if for_or_in else "" 2129 lock_type = expression.args.get("lock_type") 2130 override = " OVERRIDE" if expression.args.get("override") else "" 2131 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2132 2133 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2134 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2135 statistics = expression.args.get("statistics") 2136 statistics_sql = "" 2137 if statistics is not None: 2138 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2139 return f"{data_sql}{statistics_sql}" 2140 2141 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2142 this = self.sql(expression, "this") 2143 this = f"HISTORY_TABLE={this}" if this else "" 2144 data_consistency: str | None = self.sql(expression, "data_consistency") 2145 data_consistency = ( 2146 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2147 ) 2148 retention_period: str | None = self.sql(expression, "retention_period") 2149 retention_period = ( 2150 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2151 ) 2152 2153 if this: 2154 on_sql = self.func("ON", this, data_consistency, retention_period) 2155 else: 2156 on_sql = "ON" if expression.args.get("on") else "OFF" 2157 2158 sql = f"SYSTEM_VERSIONING={on_sql}" 2159 2160 return f"WITH({sql})" if expression.args.get("with_") else sql 2161 2162 def insert_sql(self, expression: exp.Insert) -> str: 2163 hint = self.sql(expression, "hint") 2164 overwrite = expression.args.get("overwrite") 2165 2166 if isinstance(expression.this, exp.Directory): 2167 this = " OVERWRITE" if overwrite else " INTO" 2168 else: 2169 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2170 2171 stored = self.sql(expression, "stored") 2172 stored = f" {stored}" if stored else "" 2173 alternative = expression.args.get("alternative") 2174 alternative = f" OR {alternative}" if alternative else "" 2175 ignore = " IGNORE" if expression.args.get("ignore") else "" 2176 is_function = expression.args.get("is_function") 2177 if is_function: 2178 this = f"{this} FUNCTION" 2179 this = f"{this} {self.sql(expression, 'this')}" 2180 2181 exists = " IF EXISTS" if expression.args.get("exists") else "" 2182 where = self.sql(expression, "where") 2183 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2184 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2185 on_conflict = self.sql(expression, "conflict") 2186 on_conflict = f" {on_conflict}" if on_conflict else "" 2187 by_name = " BY NAME" if expression.args.get("by_name") else "" 2188 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2189 returning = self.sql(expression, "returning") 2190 2191 if self.RETURNING_END: 2192 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2193 else: 2194 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2195 2196 partition_by = self.sql(expression, "partition") 2197 partition_by = f" {partition_by}" if partition_by else "" 2198 settings = self.sql(expression, "settings") 2199 settings = f" {settings}" if settings else "" 2200 2201 source = self.sql(expression, "source") 2202 source = f"TABLE {source}" if source else "" 2203 2204 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2205 return self.prepend_ctes(expression, sql) 2206 2207 def introducer_sql(self, expression: exp.Introducer) -> str: 2208 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2209 2210 def kill_sql(self, expression: exp.Kill) -> str: 2211 kind = self.sql(expression, "kind") 2212 kind = f" {kind}" if kind else "" 2213 this = self.sql(expression, "this") 2214 this = f" {this}" if this else "" 2215 return f"KILL{kind}{this}" 2216 2217 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2218 return expression.name 2219 2220 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2221 return expression.name 2222 2223 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2224 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2225 2226 constraint = self.sql(expression, "constraint") 2227 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2228 2229 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2230 if conflict_keys: 2231 conflict_keys = f"({conflict_keys})" 2232 2233 index_predicate = self.sql(expression, "index_predicate") 2234 conflict_keys = f"{conflict_keys}{index_predicate} " 2235 2236 action = self.sql(expression, "action") 2237 2238 expressions = self.expressions(expression, flat=True) 2239 if expressions: 2240 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2241 expressions = f" {set_keyword}{expressions}" 2242 2243 where = self.sql(expression, "where") 2244 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2245 2246 def returning_sql(self, expression: exp.Returning) -> str: 2247 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2248 2249 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2250 fields = self.sql(expression, "fields") 2251 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2252 escaped = self.sql(expression, "escaped") 2253 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2254 items = self.sql(expression, "collection_items") 2255 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2256 keys = self.sql(expression, "map_keys") 2257 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2258 lines = self.sql(expression, "lines") 2259 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2260 null = self.sql(expression, "null") 2261 null = f" NULL DEFINED AS {null}" if null else "" 2262 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2263 2264 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2265 return f"WITH ({self.expressions(expression, flat=True)})" 2266 2267 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2268 this = f"{self.sql(expression, 'this')} INDEX" 2269 target = self.sql(expression, "target") 2270 target = f" FOR {target}" if target else "" 2271 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2272 2273 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2274 this = self.sql(expression, "this") 2275 kind = self.sql(expression, "kind") 2276 expr = self.sql(expression, "expression") 2277 return f"{this} ({kind} => {expr})" 2278 2279 def table_parts(self, expression: exp.Table) -> str: 2280 return ".".join( 2281 self.sql(part) 2282 for part in ( 2283 expression.args.get("catalog"), 2284 expression.args.get("db"), 2285 expression.args.get("this"), 2286 ) 2287 if part is not None 2288 ) 2289 2290 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2291 table = self.table_parts(expression) 2292 only = "ONLY " if expression.args.get("only") else "" 2293 partition = self.sql(expression, "partition") 2294 partition = f" {partition}" if partition else "" 2295 version = self.sql(expression, "version") 2296 version = f" {version}" if version else "" 2297 alias = self.sql(expression, "alias") 2298 alias = f"{sep}{alias}" if alias else "" 2299 2300 sample = self.sql(expression, "sample") 2301 post_alias = "" 2302 pre_alias = "" 2303 2304 if self.dialect.ALIAS_POST_TABLESAMPLE: 2305 pre_alias = sample 2306 else: 2307 post_alias = sample 2308 2309 if self.dialect.ALIAS_POST_VERSION: 2310 pre_alias = f"{pre_alias}{version}" 2311 else: 2312 post_alias = f"{post_alias}{version}" 2313 2314 hints = self.expressions(expression, key="hints", sep=" ") 2315 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2316 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2317 joins = self.indent( 2318 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2319 ) 2320 laterals = self.expressions(expression, key="laterals", sep="") 2321 2322 file_format = self.sql(expression, "format") 2323 if file_format: 2324 pattern = self.sql(expression, "pattern") 2325 pattern = f", PATTERN => {pattern}" if pattern else "" 2326 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2327 2328 ordinality = expression.args.get("ordinality") or "" 2329 if ordinality: 2330 ordinality = f" WITH ORDINALITY{alias}" 2331 alias = "" 2332 2333 when = self.sql(expression, "when") 2334 if when: 2335 table = f"{table} {when}" 2336 2337 changes = self.sql(expression, "changes") 2338 changes = f" {changes}" if changes else "" 2339 2340 rows_from = self.expressions(expression, key="rows_from") 2341 if rows_from: 2342 table = f"ROWS FROM {self.wrap(rows_from)}" 2343 2344 indexed = expression.args.get("indexed") 2345 if indexed is not None: 2346 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2347 else: 2348 indexed = "" 2349 2350 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2351 2352 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2353 table = self.func("TABLE", expression.this) 2354 alias = self.sql(expression, "alias") 2355 alias = f" AS {alias}" if alias else "" 2356 sample = self.sql(expression, "sample") 2357 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2358 joins = self.indent( 2359 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2360 ) 2361 return f"{table}{alias}{pivots}{sample}{joins}" 2362 2363 def tablesample_sql( 2364 self, 2365 expression: exp.TableSample, 2366 tablesample_keyword: str | None = None, 2367 ) -> str: 2368 method = self.sql(expression, "method") 2369 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2370 numerator = self.sql(expression, "bucket_numerator") 2371 denominator = self.sql(expression, "bucket_denominator") 2372 field = self.sql(expression, "bucket_field") 2373 field = f" ON {field}" if field else "" 2374 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2375 seed = self.sql(expression, "seed") 2376 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2377 2378 size = self.sql(expression, "size") 2379 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2380 size = f"{size} ROWS" 2381 2382 percent = self.sql(expression, "percent") 2383 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2384 percent = f"{percent} PERCENT" 2385 2386 expr = f"{bucket}{percent}{size}" 2387 if self.TABLESAMPLE_REQUIRES_PARENS: 2388 expr = f"({expr})" 2389 2390 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2391 2392 def pivot_sql(self, expression: exp.Pivot) -> str: 2393 expressions = self.expressions(expression, flat=True) 2394 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2395 2396 group = self.sql(expression, "group") 2397 2398 if expression.this: 2399 this = self.sql(expression, "this") 2400 if not expressions: 2401 sql = f"UNPIVOT {this}" 2402 else: 2403 on = f"{self.seg('ON')} {expressions}" 2404 into = self.sql(expression, "into") 2405 into = f"{self.seg('INTO')} {into}" if into else "" 2406 using = self.expressions(expression, key="using", flat=True) 2407 using = f"{self.seg('USING')} {using}" if using else "" 2408 sql = f"{direction} {this}{on}{into}{using}{group}" 2409 return self.prepend_ctes(expression, sql) 2410 2411 alias = self.sql(expression, "alias") 2412 alias = f" AS {alias}" if alias else "" 2413 2414 fields = self.expressions( 2415 expression, 2416 "fields", 2417 sep=" ", 2418 dynamic=True, 2419 new_line=True, 2420 skip_first=True, 2421 skip_last=True, 2422 ) 2423 2424 include_nulls = expression.args.get("include_nulls") 2425 if include_nulls is not None: 2426 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2427 else: 2428 nulls = "" 2429 2430 default_on_null = self.sql(expression, "default_on_null") 2431 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2432 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2433 return self.prepend_ctes(expression, sql) 2434 2435 def version_sql(self, expression: exp.Version) -> str: 2436 this = f"FOR {expression.name}" 2437 kind = expression.text("kind") 2438 expr = self.sql(expression, "expression") 2439 return f"{this} {kind} {expr}" 2440 2441 def tuple_sql(self, expression: exp.Tuple) -> str: 2442 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2443 2444 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2445 """ 2446 Returns (join_sql, from_sql) for UPDATE statements. 2447 - join_sql: placed after UPDATE table, before SET 2448 - from_sql: placed after SET clause (standard position) 2449 Dialects like MySQL need to convert FROM to JOIN syntax. 2450 """ 2451 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2452 return ("", self.sql(expression, "from_")) 2453 2454 # Qualify unqualified columns in SET clause with the target table 2455 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2456 target_table = expression.this 2457 if isinstance(target_table, exp.Table): 2458 target_name = exp.to_identifier(target_table.alias_or_name) 2459 for eq in expression.expressions: 2460 col = eq.this 2461 if isinstance(col, exp.Column) and not col.table: 2462 col.set("table", target_name) 2463 2464 table = from_expr.this 2465 if nested_joins := table.args.get("joins", []): 2466 table.set("joins", None) 2467 2468 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2469 for nested in nested_joins: 2470 if not nested.args.get("on") and not nested.args.get("using"): 2471 nested.set("on", exp.true()) 2472 join_sql += self.sql(nested) 2473 2474 return (join_sql, "") 2475 2476 def update_sql(self, expression: exp.Update) -> str: 2477 hint = self.sql(expression, "hint") 2478 this = self.sql(expression, "this") 2479 join_sql, from_sql = self._update_from_joins_sql(expression) 2480 set_sql = self.expressions(expression, flat=True) 2481 where_sql = self.sql(expression, "where") 2482 returning = self.sql(expression, "returning") 2483 order = self.sql(expression, "order") 2484 limit = self.sql(expression, "limit") 2485 if self.RETURNING_END: 2486 expression_sql = f"{from_sql}{where_sql}{returning}" 2487 else: 2488 expression_sql = f"{returning}{from_sql}{where_sql}" 2489 options = self.expressions(expression, key="options") 2490 options = f" OPTION({options})" if options else "" 2491 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2492 return self.prepend_ctes(expression, sql) 2493 2494 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2495 values_as_table = values_as_table and self.VALUES_AS_TABLE 2496 2497 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2498 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2499 args = self.expressions(expression) 2500 alias = self.sql(expression, "alias") 2501 values = f"VALUES{self.seg('')}{args}" 2502 values = ( 2503 f"({values})" 2504 if self.WRAP_DERIVED_VALUES 2505 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2506 else values 2507 ) 2508 values = self.query_modifiers(expression, values) 2509 return f"{values} AS {alias}" if alias else values 2510 2511 # Converts `VALUES...` expression into a series of select unions. 2512 alias_node = expression.args.get("alias") 2513 column_names = alias_node and alias_node.columns 2514 2515 selects: list[exp.Query] = [] 2516 2517 for i, tup in enumerate(expression.expressions): 2518 row = tup.expressions 2519 2520 if i == 0 and column_names: 2521 row = [ 2522 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2523 ] 2524 2525 selects.append(exp.Select(expressions=row)) 2526 2527 if self.pretty: 2528 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2529 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2530 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2531 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2532 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2533 2534 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2535 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2536 return f"({unions}){alias}" 2537 2538 def var_sql(self, expression: exp.Var) -> str: 2539 return self.sql(expression, "this") 2540 2541 @unsupported_args("expressions") 2542 def into_sql(self, expression: exp.Into) -> str: 2543 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2544 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2545 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2546 2547 def from_sql(self, expression: exp.From) -> str: 2548 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2549 2550 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2551 grouping_sets = self.expressions(expression, indent=False) 2552 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2553 2554 def rollup_sql(self, expression: exp.Rollup) -> str: 2555 expressions = self.expressions(expression, indent=False) 2556 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2557 2558 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2559 this = self.sql(expression, "this") 2560 2561 columns = self.expressions(expression, flat=True) 2562 2563 from_sql = self.sql(expression, "from_index") 2564 from_sql = f" FROM {from_sql}" if from_sql else "" 2565 2566 properties = expression.args.get("properties") 2567 properties_sql = ( 2568 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2569 ) 2570 2571 return f"{this}({columns}){from_sql}{properties_sql}" 2572 2573 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2574 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2575 2576 def cube_sql(self, expression: exp.Cube) -> str: 2577 expressions = self.expressions(expression, indent=False) 2578 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2579 2580 def group_sql(self, expression: exp.Group) -> str: 2581 group_by_all = expression.args.get("all") 2582 if group_by_all is True: 2583 modifier = " ALL" 2584 elif group_by_all is False: 2585 modifier = " DISTINCT" 2586 else: 2587 modifier = "" 2588 2589 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2590 2591 grouping_sets = self.expressions(expression, key="grouping_sets") 2592 cube = self.expressions(expression, key="cube") 2593 rollup = self.expressions(expression, key="rollup") 2594 2595 groupings = csv( 2596 self.seg(grouping_sets) if grouping_sets else "", 2597 self.seg(cube) if cube else "", 2598 self.seg(rollup) if rollup else "", 2599 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2600 sep=self.GROUPINGS_SEP, 2601 ) 2602 2603 if ( 2604 expression.expressions 2605 and groupings 2606 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2607 ): 2608 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2609 2610 return f"{group_by}{groupings}" 2611 2612 def having_sql(self, expression: exp.Having) -> str: 2613 this = self.indent(self.sql(expression, "this")) 2614 return f"{self.seg('HAVING')}{self.sep()}{this}" 2615 2616 def connect_sql(self, expression: exp.Connect) -> str: 2617 start = self.sql(expression, "start") 2618 start = self.seg(f"START WITH {start}") if start else "" 2619 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2620 connect = self.sql(expression, "connect") 2621 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2622 return start + connect 2623 2624 def prior_sql(self, expression: exp.Prior) -> str: 2625 return f"PRIOR {self.sql(expression, 'this')}" 2626 2627 def join_sql(self, expression: exp.Join) -> str: 2628 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2629 side = None 2630 else: 2631 side = expression.side 2632 2633 op_sql = " ".join( 2634 op 2635 for op in ( 2636 expression.method, 2637 "GLOBAL" if expression.args.get("global_") else None, 2638 side, 2639 expression.kind, 2640 expression.hint if self.JOIN_HINTS else None, 2641 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2642 ) 2643 if op 2644 ) 2645 match_cond = self.sql(expression, "match_condition") 2646 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2647 on_sql = self.sql(expression, "on") 2648 using = expression.args.get("using") 2649 2650 if not on_sql and using: 2651 on_sql = csv(*(self.sql(column) for column in using)) 2652 2653 this = expression.this 2654 this_sql = self.sql(this) 2655 2656 exprs = self.expressions(expression) 2657 if exprs: 2658 this_sql = f"{this_sql},{self.seg(exprs)}" 2659 2660 if on_sql: 2661 on_sql = self.indent(on_sql, skip_first=True) 2662 space = self.seg(" " * self.pad) if self.pretty else " " 2663 if using: 2664 on_sql = f"{space}USING ({on_sql})" 2665 else: 2666 on_sql = f"{space}ON {on_sql}" 2667 elif not op_sql: 2668 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2669 return f" {this_sql}" 2670 2671 return f", {this_sql}" 2672 2673 if op_sql != "STRAIGHT_JOIN": 2674 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2675 2676 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2677 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2678 2679 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2680 args = self.expressions(expression, flat=True) 2681 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2682 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2683 2684 def lateral_op(self, expression: exp.Lateral) -> str: 2685 cross_apply = expression.args.get("cross_apply") 2686 2687 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2688 if cross_apply is True: 2689 op = "INNER JOIN " 2690 elif cross_apply is False: 2691 op = "LEFT JOIN " 2692 else: 2693 op = "" 2694 2695 return f"{op}LATERAL" 2696 2697 def lateral_sql(self, expression: exp.Lateral) -> str: 2698 this = self.sql(expression, "this") 2699 2700 if expression.args.get("view"): 2701 alias = expression.args["alias"] 2702 columns = self.expressions(alias, key="columns", flat=True) 2703 table = f" {alias.name}" if alias.name else "" 2704 columns = f" AS {columns}" if columns else "" 2705 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2706 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2707 2708 alias = self.sql(expression, "alias") 2709 alias = f" AS {alias}" if alias else "" 2710 2711 ordinality = expression.args.get("ordinality") or "" 2712 if ordinality: 2713 ordinality = f" WITH ORDINALITY{alias}" 2714 alias = "" 2715 2716 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2717 2718 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2719 this = self.sql(expression, "this") 2720 2721 args = [ 2722 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2723 for e in (expression.args.get(k) for k in ("offset", "expression")) 2724 if e 2725 ] 2726 2727 args_sql = ", ".join(self.sql(e) for e in args) 2728 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2729 expressions = self.expressions(expression, flat=True) 2730 limit_options = self.sql(expression, "limit_options") 2731 expressions = f" BY {expressions}" if expressions else "" 2732 2733 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2734 2735 def offset_sql(self, expression: exp.Offset) -> str: 2736 this = self.sql(expression, "this") 2737 value = expression.expression 2738 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2739 expressions = self.expressions(expression, flat=True) 2740 expressions = f" BY {expressions}" if expressions else "" 2741 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2742 2743 def setitem_sql(self, expression: exp.SetItem) -> str: 2744 kind = self.sql(expression, "kind") 2745 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2746 kind = "" 2747 else: 2748 kind = f"{kind} " if kind else "" 2749 this = self.sql(expression, "this") 2750 expressions = self.expressions(expression) 2751 collate = self.sql(expression, "collate") 2752 collate = f" COLLATE {collate}" if collate else "" 2753 global_ = "GLOBAL " if expression.args.get("global_") else "" 2754 return f"{global_}{kind}{this}{expressions}{collate}" 2755 2756 def set_sql(self, expression: exp.Set) -> str: 2757 expressions = f" {self.expressions(expression, flat=True)}" 2758 tag = " TAG" if expression.args.get("tag") else "" 2759 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2760 2761 def queryband_sql(self, expression: exp.QueryBand) -> str: 2762 this = self.sql(expression, "this") 2763 update = " UPDATE" if expression.args.get("update") else "" 2764 scope = self.sql(expression, "scope") 2765 scope = f" FOR {scope}" if scope else "" 2766 2767 return f"QUERY_BAND = {this}{update}{scope}" 2768 2769 def pragma_sql(self, expression: exp.Pragma) -> str: 2770 return f"PRAGMA {self.sql(expression, 'this')}" 2771 2772 def lock_sql(self, expression: exp.Lock) -> str: 2773 if not self.LOCKING_READS_SUPPORTED: 2774 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2775 return "" 2776 2777 update = expression.args["update"] 2778 key = expression.args.get("key") 2779 if update: 2780 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2781 else: 2782 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2783 expressions = self.expressions(expression, flat=True) 2784 expressions = f" OF {expressions}" if expressions else "" 2785 wait = expression.args.get("wait") 2786 2787 if wait is not None: 2788 if isinstance(wait, exp.Literal): 2789 wait = f" WAIT {self.sql(wait)}" 2790 else: 2791 wait = " NOWAIT" if wait else " SKIP LOCKED" 2792 2793 return f"{lock_type}{expressions}{wait or ''}" 2794 2795 def literal_sql(self, expression: exp.Literal) -> str: 2796 text = expression.this or "" 2797 if expression.is_string: 2798 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2799 return text 2800 2801 def escape_str( 2802 self, 2803 text: str, 2804 escape_backslash: bool = True, 2805 delimiter: str | None = None, 2806 escaped_delimiter: str | None = None, 2807 is_byte_string: bool = False, 2808 ) -> str: 2809 if is_byte_string: 2810 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2811 else: 2812 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2813 2814 if supports_escape_sequences: 2815 text = "".join( 2816 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2817 for ch in text 2818 ) 2819 2820 delimiter = delimiter or self.dialect.QUOTE_END 2821 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2822 2823 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2824 2825 def loaddata_sql(self, expression: exp.LoadData) -> str: 2826 is_overwrite = expression.args.get("overwrite") 2827 overwrite = " OVERWRITE" if is_overwrite else "" 2828 this = self.sql(expression, "this") 2829 2830 files = expression.args.get("files") 2831 if files: 2832 files_sql = self.expressions(files, flat=True) 2833 files_sql = f"FILES{self.wrap(files_sql)}" 2834 this = f" {this}" if is_overwrite else f" INTO TABLE {this}" 2835 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2836 2837 local = " LOCAL" if expression.args.get("local") else "" 2838 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2839 this = f" INTO TABLE {this}" 2840 partition = self.sql(expression, "partition") 2841 partition = f" {partition}" if partition else "" 2842 input_format = self.sql(expression, "input_format") 2843 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2844 serde = self.sql(expression, "serde") 2845 serde = f" SERDE {serde}" if serde else "" 2846 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2847 2848 def null_sql(self, *_) -> str: 2849 return "NULL" 2850 2851 def boolean_sql(self, expression: exp.Boolean) -> str: 2852 return "TRUE" if expression.this else "FALSE" 2853 2854 def booland_sql(self, expression: exp.Booland) -> str: 2855 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2856 2857 def boolor_sql(self, expression: exp.Boolor) -> str: 2858 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2859 2860 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2861 this = self.sql(expression, "this") 2862 this = f"{this} " if this else this 2863 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2864 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 2865 2866 def withfill_sql(self, expression: exp.WithFill) -> str: 2867 from_sql = self.sql(expression, "from_") 2868 from_sql = f" FROM {from_sql}" if from_sql else "" 2869 to_sql = self.sql(expression, "to") 2870 to_sql = f" TO {to_sql}" if to_sql else "" 2871 step_sql = self.sql(expression, "step") 2872 step_sql = f" STEP {step_sql}" if step_sql else "" 2873 interpolated_values = [ 2874 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2875 if isinstance(e, exp.Alias) 2876 else self.sql(e, "this") 2877 for e in expression.args.get("interpolate") or [] 2878 ] 2879 interpolate = ( 2880 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2881 ) 2882 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2883 2884 def cluster_sql(self, expression: exp.Cluster) -> str: 2885 return self.op_expressions("CLUSTER BY", expression) 2886 2887 def distribute_sql(self, expression: exp.Distribute) -> str: 2888 return self.op_expressions("DISTRIBUTE BY", expression) 2889 2890 def sort_sql(self, expression: exp.Sort) -> str: 2891 return self.op_expressions("SORT BY", expression) 2892 2893 def ordered_sql(self, expression: exp.Ordered) -> str: 2894 desc = expression.args.get("desc") 2895 asc = not desc 2896 2897 nulls_first = expression.args.get("nulls_first") 2898 nulls_last = not nulls_first 2899 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2900 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2901 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2902 2903 this = self.sql(expression, "this") 2904 2905 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2906 nulls_sort_change = "" 2907 if nulls_first and ( 2908 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2909 ): 2910 nulls_sort_change = " NULLS FIRST" 2911 elif ( 2912 nulls_last 2913 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2914 and not nulls_are_last 2915 ): 2916 nulls_sort_change = " NULLS LAST" 2917 2918 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2919 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2920 window = expression.find_ancestor(exp.Window, exp.Select) 2921 2922 if isinstance(window, exp.Window): 2923 window_this = window.this 2924 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 2925 window_this = window_this.this 2926 spec = window.args.get("spec") 2927 else: 2928 window_this = None 2929 spec = None 2930 2931 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 2932 # without a spec or with a ROWS spec, but not with RANGE 2933 if not ( 2934 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 2935 and (not spec or spec.text("kind").upper() == "ROWS") 2936 ): 2937 if window_this and spec: 2938 self.unsupported( 2939 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 2940 ) 2941 nulls_sort_change = "" 2942 elif self.NULL_ORDERING_SUPPORTED is False and ( 2943 (asc and nulls_sort_change == " NULLS LAST") 2944 or (desc and nulls_sort_change == " NULLS FIRST") 2945 ): 2946 # BigQuery does not allow these ordering/nulls combinations when used under 2947 # an aggregation func or under a window containing one 2948 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2949 2950 if isinstance(ancestor, exp.Window): 2951 ancestor = ancestor.this 2952 if isinstance(ancestor, exp.AggFunc): 2953 self.unsupported( 2954 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 2955 ) 2956 nulls_sort_change = "" 2957 elif self.NULL_ORDERING_SUPPORTED is None: 2958 if expression.this.is_int: 2959 self.unsupported( 2960 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2961 ) 2962 elif not isinstance(expression.this, exp.Rand): 2963 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2964 this = ( 2965 f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2966 ) 2967 nulls_sort_change = "" 2968 2969 with_fill = self.sql(expression, "with_fill") 2970 with_fill = f" {with_fill}" if with_fill else "" 2971 2972 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2973 2974 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2975 window_frame = self.sql(expression, "window_frame") 2976 window_frame = f"{window_frame} " if window_frame else "" 2977 2978 this = self.sql(expression, "this") 2979 2980 return f"{window_frame}{this}" 2981 2982 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2983 partition = self.partition_by_sql(expression) 2984 order = self.sql(expression, "order") 2985 measures = self.expressions(expression, key="measures") 2986 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2987 rows = self.sql(expression, "rows") 2988 rows = self.seg(rows) if rows else "" 2989 after = self.sql(expression, "after") 2990 after = self.seg(after) if after else "" 2991 pattern = self.sql(expression, "pattern") 2992 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2993 definition_sqls = [ 2994 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2995 for definition in expression.args.get("define", []) 2996 ] 2997 definitions = self.expressions(sqls=definition_sqls) 2998 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2999 body = "".join( 3000 ( 3001 partition, 3002 order, 3003 measures, 3004 rows, 3005 after, 3006 pattern, 3007 define, 3008 ) 3009 ) 3010 alias = self.sql(expression, "alias") 3011 alias = f" {alias}" if alias else "" 3012 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3013 3014 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3015 limit = expression.args.get("limit") 3016 3017 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3018 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3019 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3020 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3021 3022 return csv( 3023 *sqls, 3024 *[self.sql(join) for join in expression.args.get("joins") or []], 3025 self.sql(expression, "match"), 3026 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3027 self.sql(expression, "prewhere"), 3028 self.sql(expression, "where"), 3029 self.sql(expression, "connect"), 3030 self.sql(expression, "group"), 3031 self.sql(expression, "having"), 3032 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3033 self.sql(expression, "order"), 3034 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3035 *self.after_limit_modifiers(expression), 3036 self.options_modifier(expression), 3037 self.for_modifiers(expression), 3038 sep="", 3039 ) 3040 3041 def options_modifier(self, expression: exp.Expr) -> str: 3042 options = self.expressions(expression, key="options") 3043 return f" {options}" if options else "" 3044 3045 def for_modifiers(self, expression: exp.Expr) -> str: 3046 for_modifiers = self.expressions(expression, key="for_") 3047 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 3048 3049 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3050 self.unsupported("Unsupported query option.") 3051 return "" 3052 3053 def offset_limit_modifiers( 3054 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3055 ) -> list[str]: 3056 return [ 3057 self.sql(expression, "offset") if fetch else self.sql(limit), 3058 self.sql(limit) if fetch else self.sql(expression, "offset"), 3059 ] 3060 3061 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3062 locks = self.expressions(expression, key="locks", sep=" ") 3063 locks = f" {locks}" if locks else "" 3064 return [locks, self.sql(expression, "sample")] 3065 3066 def select_sql(self, expression: exp.Select) -> str: 3067 into = expression.args.get("into") 3068 if not self.SUPPORTS_SELECT_INTO and into: 3069 into.pop() 3070 3071 hint = self.sql(expression, "hint") 3072 distinct = self.sql(expression, "distinct") 3073 distinct = f" {distinct}" if distinct else "" 3074 kind = self.sql(expression, "kind") 3075 3076 limit = expression.args.get("limit") 3077 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3078 top = self.limit_sql(limit, top=True) 3079 limit.pop() 3080 else: 3081 top = "" 3082 3083 expressions = self.expressions(expression) 3084 3085 if kind: 3086 if kind in self.SELECT_KINDS: 3087 kind = f" AS {kind}" 3088 else: 3089 if kind == "STRUCT": 3090 expressions = self.expressions( 3091 sqls=[ 3092 self.sql( 3093 exp.Struct( 3094 expressions=[ 3095 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3096 if isinstance(e, exp.Alias) 3097 else e 3098 for e in expression.expressions 3099 ] 3100 ) 3101 ) 3102 ] 3103 ) 3104 kind = "" 3105 3106 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3107 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3108 3109 exclude = expression.args.get("exclude") 3110 3111 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3112 exclude_sql = self.expressions(sqls=exclude, flat=True) 3113 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3114 3115 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3116 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3117 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3118 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3119 sql = self.query_modifiers( 3120 expression, 3121 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3122 self.sql(expression, "into", comment=False), 3123 self.sql(expression, "from_", comment=False), 3124 ) 3125 3126 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3127 if expression.args.get("with_"): 3128 sql = self.maybe_comment(sql, expression) 3129 expression.pop_comments() 3130 3131 sql = self.prepend_ctes(expression, sql) 3132 3133 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3134 expression.set("exclude", None) 3135 subquery = expression.subquery(copy=False) 3136 star = exp.Star(except_=exclude) 3137 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3138 3139 if not self.SUPPORTS_SELECT_INTO and into: 3140 if into.args.get("temporary"): 3141 table_kind = " TEMPORARY" 3142 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3143 table_kind = " UNLOGGED" 3144 else: 3145 table_kind = "" 3146 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3147 3148 return sql 3149 3150 def schema_sql(self, expression: exp.Schema) -> str: 3151 this = self.sql(expression, "this") 3152 sql = self.schema_columns_sql(expression) 3153 return f"{this} {sql}" if this and sql else this or sql 3154 3155 def schema_columns_sql(self, expression: exp.Expr) -> str: 3156 if expression.expressions: 3157 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3158 return "" 3159 3160 def star_sql(self, expression: exp.Star) -> str: 3161 except_ = self.expressions(expression, key="except_", flat=True) 3162 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3163 replace = self.expressions(expression, key="replace", flat=True) 3164 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3165 rename = self.expressions(expression, key="rename", flat=True) 3166 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3167 return f"*{except_}{replace}{rename}" 3168 3169 def parameter_sql(self, expression: exp.Parameter) -> str: 3170 this = self.sql(expression, "this") 3171 return f"{self.PARAMETER_TOKEN}{this}" 3172 3173 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3174 this = self.sql(expression, "this") 3175 kind = expression.text("kind") 3176 if kind: 3177 kind = f"{kind}." 3178 return f"@@{kind}{this}" 3179 3180 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3181 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3182 3183 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3184 alias = self.sql(expression, "alias") 3185 alias = f"{sep}{alias}" if alias else "" 3186 sample = self.sql(expression, "sample") 3187 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3188 alias = f"{sample}{alias}" 3189 3190 # Set to None so it's not generated again by self.query_modifiers() 3191 expression.set("sample", None) 3192 3193 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3194 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3195 return self.prepend_ctes(expression, sql) 3196 3197 def qualify_sql(self, expression: exp.Qualify) -> str: 3198 this = self.indent(self.sql(expression, "this")) 3199 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3200 3201 def unnest_sql(self, expression: exp.Unnest) -> str: 3202 args = self.expressions(expression, flat=True) 3203 3204 alias = expression.args.get("alias") 3205 offset = expression.args.get("offset") 3206 3207 if self.UNNEST_WITH_ORDINALITY: 3208 if alias and isinstance(offset, exp.Expr): 3209 alias.append("columns", offset) 3210 expression.set("offset", None) 3211 3212 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3213 columns = alias.columns 3214 alias = self.sql(columns[0]) if columns else "" 3215 else: 3216 alias = self.sql(alias) 3217 3218 alias = f" AS {alias}" if alias else alias 3219 if self.UNNEST_WITH_ORDINALITY: 3220 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3221 else: 3222 if isinstance(offset, exp.Expr): 3223 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3224 elif offset: 3225 suffix = f"{alias} WITH OFFSET" 3226 else: 3227 suffix = alias 3228 3229 return f"UNNEST({args}){suffix}" 3230 3231 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3232 return "" 3233 3234 def where_sql(self, expression: exp.Where) -> str: 3235 this = self.indent(self.sql(expression, "this")) 3236 return f"{self.seg('WHERE')}{self.sep()}{this}" 3237 3238 def window_sql(self, expression: exp.Window) -> str: 3239 this = self.sql(expression, "this") 3240 partition = self.partition_by_sql(expression) 3241 order = expression.args.get("order") 3242 order = self.order_sql(order, flat=True) if order else "" 3243 spec = self.sql(expression, "spec") 3244 alias = self.sql(expression, "alias") 3245 over = self.sql(expression, "over") or "OVER" 3246 3247 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3248 3249 first = expression.args.get("first") 3250 if first is None: 3251 first = "" 3252 else: 3253 first = "FIRST" if first else "LAST" 3254 3255 if not partition and not order and not spec and alias: 3256 return f"{this} {alias}" 3257 3258 args = self.format_args( 3259 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3260 ) 3261 return f"{this} ({args})" 3262 3263 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3264 partition = self.expressions(expression, key="partition_by", flat=True) 3265 return f"PARTITION BY {partition}" if partition else "" 3266 3267 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3268 kind = self.sql(expression, "kind") 3269 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3270 end = ( 3271 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3272 or "CURRENT ROW" 3273 ) 3274 3275 window_spec = f"{kind} BETWEEN {start} AND {end}" 3276 3277 exclude = self.sql(expression, "exclude") 3278 if exclude: 3279 if self.SUPPORTS_WINDOW_EXCLUDE: 3280 window_spec += f" EXCLUDE {exclude}" 3281 else: 3282 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3283 3284 return window_spec 3285 3286 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3287 this = self.sql(expression, "this") 3288 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3289 return f"{this} WITHIN GROUP ({expression_sql})" 3290 3291 def between_sql(self, expression: exp.Between) -> str: 3292 this = self.sql(expression, "this") 3293 low = self.sql(expression, "low") 3294 high = self.sql(expression, "high") 3295 symmetric = expression.args.get("symmetric") 3296 3297 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3298 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3299 3300 flag = ( 3301 " SYMMETRIC" 3302 if symmetric 3303 else " ASYMMETRIC" 3304 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3305 else "" # silently drop ASYMMETRIC – semantics identical 3306 ) 3307 return f"{this} BETWEEN{flag} {low} AND {high}" 3308 3309 def bracket_offset_expressions( 3310 self, expression: exp.Bracket, index_offset: int | None = None 3311 ) -> list[exp.Expr]: 3312 if expression.args.get("json_access"): 3313 return expression.expressions 3314 3315 return apply_index_offset( 3316 expression.this, 3317 expression.expressions, 3318 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3319 dialect=self.dialect, 3320 ) 3321 3322 def bracket_sql(self, expression: exp.Bracket) -> str: 3323 expressions = self.bracket_offset_expressions(expression) 3324 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3325 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3326 3327 def all_sql(self, expression: exp.All) -> str: 3328 this = self.sql(expression, "this") 3329 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3330 this = self.wrap(this) 3331 return f"ALL {this}" 3332 3333 def any_sql(self, expression: exp.Any) -> str: 3334 this = self.sql(expression, "this") 3335 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3336 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3337 this = self.wrap(this) 3338 return f"ANY{this}" 3339 return f"ANY {this}" 3340 3341 def exists_sql(self, expression: exp.Exists) -> str: 3342 return f"EXISTS{self.wrap(expression)}" 3343 3344 def case_sql(self, expression: exp.Case) -> str: 3345 this = self.sql(expression, "this") 3346 statements = [f"CASE {this}" if this else "CASE"] 3347 3348 for e in expression.args["ifs"]: 3349 statements.append(f"WHEN {self.sql(e, 'this')}") 3350 statements.append(f"THEN {self.sql(e, 'true')}") 3351 3352 default = self.sql(expression, "default") 3353 3354 if default: 3355 statements.append(f"ELSE {default}") 3356 3357 statements.append("END") 3358 3359 if self.pretty and self.too_wide(statements): 3360 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3361 3362 return " ".join(statements) 3363 3364 def constraint_sql(self, expression: exp.Constraint) -> str: 3365 this = self.sql(expression, "this") 3366 expressions = self.expressions(expression, flat=True) 3367 return f"CONSTRAINT {this} {expressions}" 3368 3369 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3370 order = expression.args.get("order") 3371 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3372 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3373 3374 def extract_sql(self, expression: exp.Extract) -> str: 3375 import sqlglot.dialects.dialect 3376 3377 this = ( 3378 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3379 if self.NORMALIZE_EXTRACT_DATE_PARTS 3380 else expression.this 3381 ) 3382 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3383 expression_sql = self.sql(expression, "expression") 3384 3385 return f"EXTRACT({this_sql} FROM {expression_sql})" 3386 3387 def trim_sql(self, expression: exp.Trim) -> str: 3388 trim_type = self.sql(expression, "position") 3389 3390 if trim_type == "LEADING": 3391 func_name = "LTRIM" 3392 elif trim_type == "TRAILING": 3393 func_name = "RTRIM" 3394 else: 3395 func_name = "TRIM" 3396 3397 return self.func(func_name, expression.this, expression.expression) 3398 3399 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3400 args = expression.expressions 3401 if isinstance(expression, exp.ConcatWs): 3402 args = args[1:] # Skip the delimiter 3403 3404 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3405 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3406 3407 concat_coalesce = ( 3408 self.dialect.CONCAT_WS_COALESCE 3409 if isinstance(expression, exp.ConcatWs) 3410 else self.dialect.CONCAT_COALESCE 3411 ) 3412 3413 if not concat_coalesce and expression.args.get("coalesce"): 3414 3415 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3416 if not e.type: 3417 import sqlglot.optimizer.annotate_types 3418 3419 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3420 3421 if e.is_string or e.is_type(exp.DType.ARRAY): 3422 return e 3423 3424 return exp.func("coalesce", e, exp.Literal.string("")) 3425 3426 args = [_wrap_with_coalesce(e) for e in args] 3427 3428 return args 3429 3430 def concat_sql(self, expression: exp.Concat) -> str: 3431 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3432 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3433 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3434 # instead of coalescing them to empty string. 3435 import sqlglot.dialects.dialect 3436 3437 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3438 3439 expressions = self.convert_concat_args(expression) 3440 3441 # Some dialects don't allow a single-argument CONCAT call 3442 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3443 return self.sql(expressions[0]) 3444 3445 return self.func("CONCAT", *expressions) 3446 3447 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3448 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3449 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3450 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3451 all_args = expression.expressions 3452 expression.set("coalesce", True) 3453 return self.sql( 3454 exp.case() 3455 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3456 .else_(expression) 3457 ) 3458 3459 return self.func( 3460 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3461 ) 3462 3463 def check_sql(self, expression: exp.Check) -> str: 3464 this = self.sql(expression, key="this") 3465 return f"CHECK ({this})" 3466 3467 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3468 expressions = self.expressions(expression, flat=True) 3469 expressions = f" ({expressions})" if expressions else "" 3470 reference = self.sql(expression, "reference") 3471 reference = f" {reference}" if reference else "" 3472 delete = self.sql(expression, "delete") 3473 delete = f" ON DELETE {delete}" if delete else "" 3474 update = self.sql(expression, "update") 3475 update = f" ON UPDATE {update}" if update else "" 3476 options = self.expressions(expression, key="options", flat=True, sep=" ") 3477 options = f" {options}" if options else "" 3478 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3479 3480 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3481 this = self.sql(expression, "this") 3482 this = f" {this}" if this else "" 3483 expressions = self.expressions(expression, flat=True) 3484 include = self.sql(expression, "include") 3485 options = self.expressions(expression, key="options", flat=True, sep=" ") 3486 options = f" {options}" if options else "" 3487 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3488 3489 def if_sql(self, expression: exp.If) -> str: 3490 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3491 3492 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3493 if self.MATCH_AGAINST_TABLE_PREFIX: 3494 expressions = [] 3495 for expr in expression.expressions: 3496 if isinstance(expr, exp.Table): 3497 expressions.append(f"TABLE {self.sql(expr)}") 3498 else: 3499 expressions.append(expr) 3500 else: 3501 expressions = expression.expressions 3502 3503 modifier = expression.args.get("modifier") 3504 modifier = f" {modifier}" if modifier else "" 3505 return ( 3506 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3507 ) 3508 3509 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3510 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3511 3512 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3513 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3514 3515 if expression.args.get("escape"): 3516 path = self.escape_str(path) 3517 3518 if self.QUOTE_JSON_PATH: 3519 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3520 3521 return path 3522 3523 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3524 if isinstance(expression, exp.JSONPathPart): 3525 transform = self.TRANSFORMS.get(expression.__class__) 3526 if not callable(transform): 3527 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3528 return "" 3529 3530 return transform(self, expression) 3531 3532 if isinstance(expression, int): 3533 return str(expression) 3534 3535 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3536 escaped = expression.replace("'", "\\'") 3537 escaped = f"\\'{expression}\\'" 3538 else: 3539 escaped = expression.replace('"', '\\"') 3540 escaped = f'"{escaped}"' 3541 3542 return escaped 3543 3544 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3545 return f"{self.sql(expression, 'this')} FORMAT JSON" 3546 3547 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3548 # Output the Teradata column FORMAT override. 3549 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3550 this = self.sql(expression, "this") 3551 fmt = self.sql(expression, "format") 3552 return f"{this} (FORMAT {fmt})" 3553 3554 def _jsonobject_sql( 3555 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3556 ) -> str: 3557 null_handling = expression.args.get("null_handling") 3558 null_handling = f" {null_handling}" if null_handling else "" 3559 3560 unique_keys = expression.args.get("unique_keys") 3561 if unique_keys is not None: 3562 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3563 else: 3564 unique_keys = "" 3565 3566 return_type = self.sql(expression, "return_type") 3567 return_type = f" RETURNING {return_type}" if return_type else "" 3568 encoding = self.sql(expression, "encoding") 3569 encoding = f" ENCODING {encoding}" if encoding else "" 3570 3571 if not name: 3572 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3573 3574 return self.func( 3575 name, 3576 *expression.expressions, 3577 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3578 ) 3579 3580 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3581 null_handling = expression.args.get("null_handling") 3582 null_handling = f" {null_handling}" if null_handling else "" 3583 return_type = self.sql(expression, "return_type") 3584 return_type = f" RETURNING {return_type}" if return_type else "" 3585 strict = " STRICT" if expression.args.get("strict") else "" 3586 return self.func( 3587 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3588 ) 3589 3590 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3591 this = self.sql(expression, "this") 3592 order = self.sql(expression, "order") 3593 null_handling = expression.args.get("null_handling") 3594 null_handling = f" {null_handling}" if null_handling else "" 3595 return_type = self.sql(expression, "return_type") 3596 return_type = f" RETURNING {return_type}" if return_type else "" 3597 strict = " STRICT" if expression.args.get("strict") else "" 3598 return self.func( 3599 "JSON_ARRAYAGG", 3600 this, 3601 suffix=f"{order}{null_handling}{return_type}{strict})", 3602 ) 3603 3604 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3605 path = self.sql(expression, "path") 3606 path = f" PATH {path}" if path else "" 3607 nested_schema = self.sql(expression, "nested_schema") 3608 3609 if nested_schema: 3610 return f"NESTED{path} {nested_schema}" 3611 3612 this = self.sql(expression, "this") 3613 kind = self.sql(expression, "kind") 3614 kind = f" {kind}" if kind else "" 3615 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3616 3617 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3618 return f"{this}{kind}{format_json}{path}{ordinality}" 3619 3620 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3621 return self.func("COLUMNS", *expression.expressions) 3622 3623 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3624 this = self.sql(expression, "this") 3625 path = self.sql(expression, "path") 3626 path = f", {path}" if path else "" 3627 error_handling = expression.args.get("error_handling") 3628 error_handling = f" {error_handling}" if error_handling else "" 3629 empty_handling = expression.args.get("empty_handling") 3630 empty_handling = f" {empty_handling}" if empty_handling else "" 3631 schema = self.sql(expression, "schema") 3632 return self.func( 3633 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3634 ) 3635 3636 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3637 this = self.sql(expression, "this") 3638 kind = self.sql(expression, "kind") 3639 path = self.sql(expression, "path") 3640 path = f" {path}" if path else "" 3641 as_json = " AS JSON" if expression.args.get("as_json") else "" 3642 return f"{this} {kind}{path}{as_json}" 3643 3644 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3645 this = self.sql(expression, "this") 3646 path = self.sql(expression, "path") 3647 path = f", {path}" if path else "" 3648 expressions = self.expressions(expression) 3649 with_ = ( 3650 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3651 if expressions 3652 else "" 3653 ) 3654 return f"OPENJSON({this}{path}){with_}" 3655 3656 def in_sql(self, expression: exp.In) -> str: 3657 query = expression.args.get("query") 3658 unnest = expression.args.get("unnest") 3659 field = expression.args.get("field") 3660 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3661 3662 if query: 3663 in_sql = self.sql(query) 3664 elif unnest: 3665 in_sql = self.in_unnest_op(unnest) 3666 elif field: 3667 in_sql = self.sql(field) 3668 else: 3669 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3670 3671 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3672 3673 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3674 return f"(SELECT {self.sql(unnest)})" 3675 3676 def interval_sql(self, expression: exp.Interval) -> str: 3677 unit_expression = expression.args.get("unit") 3678 unit = self.sql(unit_expression) if unit_expression else "" 3679 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3680 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3681 unit = f" {unit}" if unit else "" 3682 3683 if self.SINGLE_STRING_INTERVAL: 3684 this = expression.this.name if expression.this else "" 3685 if this: 3686 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3687 return f"INTERVAL '{this}'{unit}" 3688 return f"INTERVAL '{this}{unit}'" 3689 return f"INTERVAL{unit}" 3690 3691 this = self.sql(expression, "this") 3692 if this: 3693 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3694 this = f" {this}" if unwrapped else f" ({this})" 3695 3696 return f"INTERVAL{this}{unit}" 3697 3698 def return_sql(self, expression: exp.Return) -> str: 3699 return f"RETURN {self.sql(expression, 'this')}" 3700 3701 def reference_sql(self, expression: exp.Reference) -> str: 3702 this = self.sql(expression, "this") 3703 expressions = self.expressions(expression, flat=True) 3704 expressions = f"({expressions})" if expressions else "" 3705 options = self.expressions(expression, key="options", flat=True, sep=" ") 3706 options = f" {options}" if options else "" 3707 return f"REFERENCES {this}{expressions}{options}" 3708 3709 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3710 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3711 parent = expression.parent 3712 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3713 3714 return self.func( 3715 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3716 ) 3717 3718 def paren_sql(self, expression: exp.Paren) -> str: 3719 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3720 return f"({sql}{self.seg(')', sep='')}" 3721 3722 def neg_sql(self, expression: exp.Neg) -> str: 3723 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3724 this_sql = self.sql(expression, "this") 3725 sep = " " if this_sql[0] == "-" else "" 3726 return f"-{sep}{this_sql}" 3727 3728 def not_sql(self, expression: exp.Not) -> str: 3729 return f"NOT {self.sql(expression, 'this')}" 3730 3731 def alias_sql(self, expression: exp.Alias) -> str: 3732 alias = self.sql(expression, "alias") 3733 alias = f" AS {alias}" if alias else "" 3734 return f"{self.sql(expression, 'this')}{alias}" 3735 3736 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3737 alias = expression.args["alias"] 3738 3739 parent = expression.parent 3740 pivot = parent and parent.parent 3741 3742 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3743 identifier_alias = isinstance(alias, exp.Identifier) 3744 literal_alias = isinstance(alias, exp.Literal) 3745 3746 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3747 alias.replace(exp.Literal.string(alias.output_name)) 3748 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3749 alias.replace(exp.to_identifier(alias.output_name)) 3750 3751 return self.alias_sql(expression) 3752 3753 def aliases_sql(self, expression: exp.Aliases) -> str: 3754 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3755 3756 def atindex_sql(self, expression: exp.AtIndex) -> str: 3757 this = self.sql(expression, "this") 3758 index = self.sql(expression, "expression") 3759 return f"{this} AT {index}" 3760 3761 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3762 this = self.sql(expression, "this") 3763 zone = self.sql(expression, "zone") 3764 return f"{this} AT TIME ZONE {zone}" 3765 3766 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3767 this = self.sql(expression, "this") 3768 zone = self.sql(expression, "zone") 3769 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3770 3771 def add_sql(self, expression: exp.Add) -> str: 3772 return self.binary(expression, "+") 3773 3774 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 3775 return self.connector_sql(expression, "AND", stack) 3776 3777 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 3778 return self.connector_sql(expression, "OR", stack) 3779 3780 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 3781 return self.connector_sql(expression, "XOR", stack) 3782 3783 def connector_sql( 3784 self, 3785 expression: exp.Connector, 3786 op: str, 3787 stack: list[str | exp.Expr] | None = None, 3788 ) -> str: 3789 if stack is not None: 3790 if expression.expressions: 3791 stack.append(self.expressions(expression, sep=f" {op} ")) 3792 else: 3793 stack.append(expression.right) 3794 if expression.comments and self.comments: 3795 for comment in expression.comments: 3796 if comment: 3797 op += f" /*{self.sanitize_comment(comment)}*/" 3798 stack.extend((op, expression.left)) 3799 return op 3800 3801 stack = [expression] 3802 sqls: list[str] = [] 3803 ops = set() 3804 3805 while stack: 3806 node = stack.pop() 3807 if isinstance(node, exp.Connector): 3808 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3809 else: 3810 sql = self.sql(node) 3811 if sqls and sqls[-1] in ops: 3812 sqls[-1] += f" {sql}" 3813 else: 3814 sqls.append(sql) 3815 3816 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3817 return sep.join(sqls) 3818 3819 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3820 return self.binary(expression, "&") 3821 3822 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3823 return self.binary(expression, "<<") 3824 3825 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3826 return f"~{self.sql(expression, 'this')}" 3827 3828 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3829 return self.binary(expression, "|") 3830 3831 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3832 return self.binary(expression, ">>") 3833 3834 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3835 return self.binary(expression, "^") 3836 3837 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 3838 format_sql = self.sql(expression, "format") 3839 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3840 to_sql = self.sql(expression, "to") 3841 to_sql = f" {to_sql}" if to_sql else "" 3842 action = self.sql(expression, "action") 3843 action = f" {action}" if action else "" 3844 default = self.sql(expression, "default") 3845 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3846 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3847 3848 # Base implementation that excludes safe, zone, and target_type metadata args 3849 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3850 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3851 3852 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3853 zone = self.sql(expression, "this") 3854 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3855 3856 def collate_sql(self, expression: exp.Collate) -> str: 3857 if self.COLLATE_IS_FUNC: 3858 return self.function_fallback_sql(expression) 3859 return self.binary(expression, "COLLATE") 3860 3861 def command_sql(self, expression: exp.Command) -> str: 3862 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3863 3864 def comment_sql(self, expression: exp.Comment) -> str: 3865 this = self.sql(expression, "this") 3866 kind = expression.args["kind"] 3867 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3868 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3869 expression_sql = self.sql(expression, "expression") 3870 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3871 3872 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3873 this = self.sql(expression, "this") 3874 delete = " DELETE" if expression.args.get("delete") else "" 3875 recompress = self.sql(expression, "recompress") 3876 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3877 to_disk = self.sql(expression, "to_disk") 3878 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3879 to_volume = self.sql(expression, "to_volume") 3880 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3881 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3882 3883 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3884 where = self.sql(expression, "where") 3885 group = self.sql(expression, "group") 3886 aggregates = self.expressions(expression, key="aggregates") 3887 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3888 3889 if not (where or group or aggregates) and len(expression.expressions) == 1: 3890 return f"TTL {self.expressions(expression, flat=True)}" 3891 3892 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3893 3894 def transaction_sql(self, expression: exp.Transaction) -> str: 3895 modes = self.expressions(expression, key="modes") 3896 modes = f" {modes}" if modes else "" 3897 return f"BEGIN{modes}" 3898 3899 def commit_sql(self, expression: exp.Commit) -> str: 3900 chain = expression.args.get("chain") 3901 if chain is not None: 3902 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3903 3904 return f"COMMIT{chain or ''}" 3905 3906 def rollback_sql(self, expression: exp.Rollback) -> str: 3907 savepoint = expression.args.get("savepoint") 3908 savepoint = f" TO {savepoint}" if savepoint else "" 3909 return f"ROLLBACK{savepoint}" 3910 3911 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3912 this = self.sql(expression, "this") 3913 3914 dtype = self.sql(expression, "dtype") 3915 if dtype: 3916 collate = self.sql(expression, "collate") 3917 collate = f" COLLATE {collate}" if collate else "" 3918 using = self.sql(expression, "using") 3919 using = f" USING {using}" if using else "" 3920 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3921 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3922 3923 default = self.sql(expression, "default") 3924 if default: 3925 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3926 3927 comment = self.sql(expression, "comment") 3928 if comment: 3929 return f"ALTER COLUMN {this} COMMENT {comment}" 3930 3931 visible = expression.args.get("visible") 3932 if visible: 3933 return f"ALTER COLUMN {this} SET {visible}" 3934 3935 allow_null = expression.args.get("allow_null") 3936 drop = expression.args.get("drop") 3937 3938 if not drop and not allow_null: 3939 self.unsupported("Unsupported ALTER COLUMN syntax") 3940 3941 if allow_null is not None: 3942 keyword = "DROP" if drop else "SET" 3943 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3944 3945 return f"ALTER COLUMN {this} DROP DEFAULT" 3946 3947 def modifycolumn_sql(self, expression: exp.ModifyColumn) -> str: 3948 if not self.SUPPORTS_MODIFY_COLUMN: 3949 self.unsupported("MODIFY COLUMN is not supported in this dialect") 3950 return f"MODIFY COLUMN {self.sql(expression, 'this')}" 3951 3952 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3953 this = self.sql(expression, "this") 3954 3955 visible = expression.args.get("visible") 3956 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3957 3958 return f"ALTER INDEX {this} {visible_sql}" 3959 3960 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3961 this = self.sql(expression, "this") 3962 if not isinstance(expression.this, exp.Var): 3963 this = f"KEY DISTKEY {this}" 3964 return f"ALTER DISTSTYLE {this}" 3965 3966 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3967 compound = " COMPOUND" if expression.args.get("compound") else "" 3968 this = self.sql(expression, "this") 3969 expressions = self.expressions(expression, flat=True) 3970 expressions = f"({expressions})" if expressions else "" 3971 return f"ALTER{compound} SORTKEY {this or expressions}" 3972 3973 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3974 if not self.RENAME_TABLE_WITH_DB: 3975 # Remove db from tables 3976 expression = expression.transform( 3977 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3978 ).assert_is(exp.AlterRename) 3979 this = self.sql(expression, "this") 3980 to_kw = " TO" if include_to else "" 3981 return f"RENAME{to_kw} {this}" 3982 3983 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3984 exists = " IF EXISTS" if expression.args.get("exists") else "" 3985 old_column = self.sql(expression, "this") 3986 new_column = self.sql(expression, "to") 3987 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3988 3989 def alterset_sql(self, expression: exp.AlterSet) -> str: 3990 exprs = self.expressions(expression, flat=True) 3991 if self.ALTER_SET_WRAPPED: 3992 exprs = f"({exprs})" 3993 3994 return f"SET {exprs}" 3995 3996 def alter_sql(self, expression: exp.Alter) -> str: 3997 actions = expression.args["actions"] 3998 3999 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 4000 actions[0], exp.ColumnDef 4001 ): 4002 actions_sql = self.expressions(expression, key="actions", flat=True) 4003 actions_sql = f"ADD {actions_sql}" 4004 else: 4005 actions_list = [] 4006 for action in actions: 4007 if isinstance(action, (exp.ColumnDef, exp.Schema)): 4008 action_sql = self.add_column_sql(action) 4009 else: 4010 action_sql = self.sql(action) 4011 if isinstance(action, exp.Query): 4012 action_sql = f"AS {action_sql}" 4013 4014 actions_list.append(action_sql) 4015 4016 actions_sql = self.format_args(*actions_list).lstrip("\n") 4017 4018 iceberg = ( 4019 "ICEBERG " 4020 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4021 else "" 4022 ) 4023 exists = " IF EXISTS" if expression.args.get("exists") else "" 4024 on_cluster = self.sql(expression, "cluster") 4025 on_cluster = f" {on_cluster}" if on_cluster else "" 4026 only = " ONLY" if expression.args.get("only") else "" 4027 options = self.expressions(expression, key="options") 4028 options = f", {options}" if options else "" 4029 kind = self.sql(expression, "kind") 4030 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4031 check = " WITH CHECK" if expression.args.get("check") else "" 4032 cascade = ( 4033 " CASCADE" 4034 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4035 else "" 4036 ) 4037 this = self.sql(expression, "this") 4038 this = f" {this}" if this else "" 4039 4040 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4041 4042 def altersession_sql(self, expression: exp.AlterSession) -> str: 4043 items_sql = self.expressions(expression, flat=True) 4044 keyword = "UNSET" if expression.args.get("unset") else "SET" 4045 return f"{keyword} {items_sql}" 4046 4047 def add_column_sql(self, expression: exp.Expr) -> str: 4048 sql = self.sql(expression) 4049 if isinstance(expression, exp.Schema): 4050 column_text = " COLUMNS" 4051 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4052 column_text = " COLUMN" 4053 else: 4054 column_text = "" 4055 4056 return f"ADD{column_text} {sql}" 4057 4058 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4059 expressions = self.expressions(expression) 4060 exists = " IF EXISTS " if expression.args.get("exists") else " " 4061 return f"DROP{exists}{expressions}" 4062 4063 def dropprimarykey_sql(self, expression: exp.DropPrimaryKey) -> str: 4064 return "DROP PRIMARY KEY" 4065 4066 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4067 return f"ADD {self.expressions(expression, indent=False)}" 4068 4069 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4070 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4071 location = self.sql(expression, "location") 4072 location = f" {location}" if location else "" 4073 return f"ADD {exists}{self.sql(expression.this)}{location}" 4074 4075 def distinct_sql(self, expression: exp.Distinct) -> str: 4076 this = self.expressions(expression, flat=True) 4077 4078 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4079 case = exp.case() 4080 for arg in expression.expressions: 4081 case = case.when(arg.is_(exp.null()), exp.null()) 4082 this = self.sql(case.else_(f"({this})")) 4083 4084 this = f" {this}" if this else "" 4085 4086 on = self.sql(expression, "on") 4087 on = f" ON {on}" if on else "" 4088 return f"DISTINCT{this}{on}" 4089 4090 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4091 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4092 4093 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4094 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4095 4096 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4097 this_sql = self.sql(expression, "this") 4098 expression_sql = self.sql(expression, "expression") 4099 kind = "MAX" if expression.args.get("max") else "MIN" 4100 return f"{this_sql} HAVING {kind} {expression_sql}" 4101 4102 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4103 return self.sql( 4104 exp.Cast( 4105 this=exp.Div(this=expression.this, expression=expression.expression), 4106 to=exp.DataType(this=exp.DType.INT), 4107 ) 4108 ) 4109 4110 def dpipe_sql(self, expression: exp.DPipe) -> str: 4111 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4112 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4113 return self.binary(expression, "||") 4114 4115 def div_sql(self, expression: exp.Div) -> str: 4116 l, r = expression.left, expression.right 4117 4118 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4119 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4120 4121 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4122 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4123 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4124 4125 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4126 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4127 return self.sql( 4128 exp.cast( 4129 l / r, 4130 to=exp.DType.BIGINT, 4131 ) 4132 ) 4133 4134 return self.binary(expression, "/") 4135 4136 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4137 n = exp._wrap(expression.this, exp.Binary) 4138 d = exp._wrap(expression.expression, exp.Binary) 4139 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4140 4141 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4142 return self.binary(expression, "OVERLAPS") 4143 4144 def distance_sql(self, expression: exp.Distance) -> str: 4145 return self.binary(expression, "<->") 4146 4147 def dot_sql(self, expression: exp.Dot) -> str: 4148 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4149 4150 def eq_sql(self, expression: exp.EQ) -> str: 4151 return self.binary(expression, "=") 4152 4153 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4154 return self.binary(expression, ":=") 4155 4156 def escape_sql(self, expression: exp.Escape) -> str: 4157 this = expression.this 4158 if ( 4159 isinstance(this, (exp.Like, exp.ILike)) 4160 and isinstance(this.expression, (exp.All, exp.Any)) 4161 and not self.SUPPORTS_LIKE_QUANTIFIERS 4162 ): 4163 return self._like_sql(this, escape=expression) 4164 return self.binary(expression, "ESCAPE") 4165 4166 def glob_sql(self, expression: exp.Glob) -> str: 4167 return self.binary(expression, "GLOB") 4168 4169 def gt_sql(self, expression: exp.GT) -> str: 4170 return self.binary(expression, ">") 4171 4172 def gte_sql(self, expression: exp.GTE) -> str: 4173 return self.binary(expression, ">=") 4174 4175 def is_sql(self, expression: exp.Is) -> str: 4176 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4177 return self.sql( 4178 expression.this if expression.expression.this else exp.not_(expression.this) 4179 ) 4180 return self.binary(expression, "IS") 4181 4182 def _like_sql( 4183 self, 4184 expression: exp.Like | exp.ILike, 4185 escape: exp.Escape | None = None, 4186 ) -> str: 4187 this = expression.this 4188 rhs = expression.expression 4189 4190 if isinstance(expression, exp.Like): 4191 exp_class: type[exp.Like | exp.ILike] = exp.Like 4192 op = "LIKE" 4193 else: 4194 exp_class = exp.ILike 4195 op = "ILIKE" 4196 4197 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4198 exprs = rhs.this.unnest() 4199 4200 if isinstance(exprs, exp.Tuple): 4201 exprs = exprs.expressions 4202 else: 4203 exprs = [exprs] 4204 4205 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4206 4207 def _make_like(expr: exp.Expression) -> exp.Expression: 4208 like: exp.Expression = exp_class(this=this, expression=expr) 4209 if escape: 4210 like = exp.Escape(this=like, expression=escape.expression.copy()) 4211 return like 4212 4213 like_expr: exp.Expr = _make_like(exprs[0]) 4214 for expr in exprs[1:]: 4215 like_expr = connective(like_expr, _make_like(expr), copy=False) 4216 4217 parent = escape.parent if escape else expression.parent 4218 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4219 parent, exp.Condition 4220 ): 4221 like_expr = exp.paren(like_expr, copy=False) 4222 4223 return self.sql(like_expr) 4224 4225 return self.binary(expression, op) 4226 4227 def like_sql(self, expression: exp.Like) -> str: 4228 return self._like_sql(expression) 4229 4230 def ilike_sql(self, expression: exp.ILike) -> str: 4231 return self._like_sql(expression) 4232 4233 def match_sql(self, expression: exp.Match) -> str: 4234 return self.binary(expression, "MATCH") 4235 4236 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4237 return self.binary(expression, "SIMILAR TO") 4238 4239 def lt_sql(self, expression: exp.LT) -> str: 4240 return self.binary(expression, "<") 4241 4242 def lte_sql(self, expression: exp.LTE) -> str: 4243 return self.binary(expression, "<=") 4244 4245 def mod_sql(self, expression: exp.Mod) -> str: 4246 return self.binary(expression, "%") 4247 4248 def mul_sql(self, expression: exp.Mul) -> str: 4249 return self.binary(expression, "*") 4250 4251 def neq_sql(self, expression: exp.NEQ) -> str: 4252 return self.binary(expression, "<>") 4253 4254 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4255 return self.binary(expression, "IS NOT DISTINCT FROM") 4256 4257 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4258 return self.binary(expression, "IS DISTINCT FROM") 4259 4260 def sub_sql(self, expression: exp.Sub) -> str: 4261 return self.binary(expression, "-") 4262 4263 def trycast_sql(self, expression: exp.TryCast) -> str: 4264 return self.cast_sql(expression, safe_prefix="TRY_") 4265 4266 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4267 return self.cast_sql(expression) 4268 4269 def try_sql(self, expression: exp.Try) -> str: 4270 if not self.TRY_SUPPORTED: 4271 self.unsupported("Unsupported TRY function") 4272 return self.sql(expression, "this") 4273 4274 return self.func("TRY", expression.this) 4275 4276 def log_sql(self, expression: exp.Log) -> str: 4277 this = expression.this 4278 expr = expression.expression 4279 4280 if self.dialect.LOG_BASE_FIRST is False: 4281 this, expr = expr, this 4282 elif self.dialect.LOG_BASE_FIRST is None and expr: 4283 if this.name in ("2", "10"): 4284 return self.func(f"LOG{this.name}", expr) 4285 4286 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4287 4288 return self.func("LOG", this, expr) 4289 4290 def use_sql(self, expression: exp.Use) -> str: 4291 kind = self.sql(expression, "kind") 4292 kind = f" {kind}" if kind else "" 4293 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4294 this = f" {this}" if this else "" 4295 return f"USE{kind}{this}" 4296 4297 def binary(self, expression: exp.Binary, op: str) -> str: 4298 sqls: list[str] = [] 4299 stack: list[None | str | exp.Expr] = [expression] 4300 binary_type = type(expression) 4301 4302 while stack: 4303 node = stack.pop() 4304 4305 if type(node) is binary_type: 4306 op_func = node.args.get("operator") 4307 if op_func: 4308 op = f"OPERATOR({self.sql(op_func)})" 4309 4310 stack.append(node.args.get("expression")) 4311 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4312 stack.append(node.args.get("this")) 4313 else: 4314 sqls.append(self.sql(node)) 4315 4316 return "".join(sqls) 4317 4318 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4319 to_clause = self.sql(expression, "to") 4320 if to_clause: 4321 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4322 4323 return self.function_fallback_sql(expression) 4324 4325 def function_fallback_sql(self, expression: exp.Func) -> str: 4326 args = [] 4327 4328 for key in expression.arg_types: 4329 arg_value = expression.args.get(key) 4330 4331 if isinstance(arg_value, list): 4332 for value in arg_value: 4333 args.append(value) 4334 elif arg_value is not None: 4335 args.append(arg_value) 4336 4337 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4338 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4339 else: 4340 name = expression.sql_name() 4341 4342 return self.func(name, *args) 4343 4344 def func( 4345 self, 4346 name: str, 4347 *args: t.Any, 4348 prefix: str = "(", 4349 suffix: str = ")", 4350 normalize: bool = True, 4351 ) -> str: 4352 name = self.normalize_func(name) if normalize else name 4353 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4354 4355 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4356 arg_sqls = tuple( 4357 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4358 ) 4359 if self.pretty and self.too_wide(arg_sqls): 4360 return self.indent( 4361 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4362 ) 4363 return sep.join(arg_sqls) 4364 4365 def too_wide(self, args: t.Iterable) -> bool: 4366 return sum(len(arg) for arg in args) > self.max_text_width 4367 4368 def format_time( 4369 self, 4370 expression: exp.Expr, 4371 inverse_time_mapping: dict[str, str] | None = None, 4372 inverse_time_trie: dict | None = None, 4373 ) -> str | None: 4374 return format_time( 4375 self.sql(expression, "format"), 4376 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4377 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4378 ) 4379 4380 def expressions( 4381 self, 4382 expression: exp.Expr | None = None, 4383 key: str | None = None, 4384 sqls: t.Collection[str | exp.Expr] | None = None, 4385 flat: bool = False, 4386 indent: bool = True, 4387 skip_first: bool = False, 4388 skip_last: bool = False, 4389 sep: str = ", ", 4390 prefix: str = "", 4391 dynamic: bool = False, 4392 new_line: bool = False, 4393 ) -> str: 4394 expressions = expression.args.get(key or "expressions") if expression else sqls 4395 4396 if not expressions: 4397 return "" 4398 4399 if flat: 4400 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4401 4402 num_sqls = len(expressions) 4403 result_sqls = [] 4404 4405 for i, e in enumerate(expressions): 4406 sql = self.sql(e, comment=False) 4407 if not sql: 4408 continue 4409 4410 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4411 4412 if self.pretty: 4413 if self.leading_comma: 4414 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4415 else: 4416 result_sqls.append( 4417 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4418 ) 4419 else: 4420 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4421 4422 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4423 if new_line: 4424 result_sqls.insert(0, "") 4425 result_sqls.append("") 4426 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4427 else: 4428 result_sql = "".join(result_sqls) 4429 4430 return ( 4431 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4432 if indent 4433 else result_sql 4434 ) 4435 4436 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4437 flat = flat or isinstance(expression.parent, exp.Properties) 4438 expressions_sql = self.expressions(expression, flat=flat) 4439 if flat: 4440 return f"{op} {expressions_sql}" 4441 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4442 4443 def naked_property(self, expression: exp.Property) -> str: 4444 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4445 if not property_name: 4446 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4447 return f"{property_name} {self.sql(expression, 'this')}" 4448 4449 def tag_sql(self, expression: exp.Tag) -> str: 4450 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4451 4452 def token_sql(self, token_type: TokenType) -> str: 4453 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4454 4455 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4456 this = self.sql(expression, "this") 4457 expressions = self.no_identify(self.expressions, expression) 4458 expressions = ( 4459 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4460 ) 4461 return f"{this}{expressions}" if expressions.strip() != "" else this 4462 4463 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4464 this = self.sql(expression, "this") 4465 expressions = self.expressions(expression, flat=True) 4466 return f"{this}({expressions})" 4467 4468 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4469 return self.binary(expression, "=>") 4470 4471 def when_sql(self, expression: exp.When) -> str: 4472 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4473 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4474 condition = self.sql(expression, "condition") 4475 condition = f" AND {condition}" if condition else "" 4476 4477 then_expression = expression.args.get("then") 4478 if isinstance(then_expression, exp.Insert): 4479 this = self.sql(then_expression, "this") 4480 this = f"INSERT {this}" if this else "INSERT" 4481 then = self.sql(then_expression, "expression") 4482 then = f"{this} VALUES {then}" if then else this 4483 elif isinstance(then_expression, exp.Update): 4484 if isinstance(then_expression.args.get("expressions"), exp.Star): 4485 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4486 else: 4487 expressions_sql = self.expressions(then_expression) 4488 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4489 else: 4490 then = self.sql(then_expression) 4491 4492 if isinstance(then_expression, (exp.Insert, exp.Update)): 4493 where = self.sql(then_expression, "where") 4494 if where and not self.SUPPORTS_MERGE_WHERE: 4495 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4496 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4497 where = "" 4498 then = f"{then}{where}" 4499 return f"WHEN {matched}{source}{condition} THEN {then}" 4500 4501 def whens_sql(self, expression: exp.Whens) -> str: 4502 return self.expressions(expression, sep=" ", indent=False) 4503 4504 def merge_sql(self, expression: exp.Merge) -> str: 4505 table = expression.this 4506 table_alias = "" 4507 4508 hints = table.args.get("hints") 4509 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4510 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4511 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4512 4513 this = self.sql(table) 4514 using = f"USING {self.sql(expression, 'using')}" 4515 whens = self.sql(expression, "whens") 4516 4517 on = self.sql(expression, "on") 4518 on = f"ON {on}" if on else "" 4519 4520 if not on: 4521 on = self.expressions(expression, key="using_cond") 4522 on = f"USING ({on})" if on else "" 4523 4524 returning = self.sql(expression, "returning") 4525 if returning: 4526 whens = f"{whens}{returning}" 4527 4528 sep = self.sep() 4529 4530 return self.prepend_ctes( 4531 expression, 4532 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4533 ) 4534 4535 @unsupported_args("format") 4536 def tochar_sql(self, expression: exp.ToChar) -> str: 4537 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4538 4539 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4540 if not self.SUPPORTS_TO_NUMBER: 4541 self.unsupported("Unsupported TO_NUMBER function") 4542 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4543 4544 fmt = expression.args.get("format") 4545 if not fmt: 4546 self.unsupported("Conversion format is required for TO_NUMBER") 4547 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4548 4549 return self.func("TO_NUMBER", expression.this, fmt) 4550 4551 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4552 this = self.sql(expression, "this") 4553 kind = self.sql(expression, "kind") 4554 settings_sql = self.expressions(expression, key="settings", sep=" ") 4555 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4556 return f"{this}({kind}{args})" 4557 4558 def dictrange_sql(self, expression: exp.DictRange) -> str: 4559 this = self.sql(expression, "this") 4560 max = self.sql(expression, "max") 4561 min = self.sql(expression, "min") 4562 return f"{this}(MIN {min} MAX {max})" 4563 4564 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4565 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4566 4567 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4568 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4569 4570 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4571 def uniquekeyproperty_sql( 4572 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4573 ) -> str: 4574 return f"{prefix} ({self.expressions(expression, flat=True)})" 4575 4576 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4577 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4578 expressions = self.expressions(expression, flat=True) 4579 expressions = f" {self.wrap(expressions)}" if expressions else "" 4580 buckets = self.sql(expression, "buckets") 4581 kind = self.sql(expression, "kind") 4582 buckets = f" BUCKETS {buckets}" if buckets else "" 4583 order = self.sql(expression, "order") 4584 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4585 4586 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4587 return "" 4588 4589 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4590 expressions = self.expressions(expression, key="expressions", flat=True) 4591 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4592 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4593 buckets = self.sql(expression, "buckets") 4594 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4595 4596 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4597 this = self.sql(expression, "this") 4598 having = self.sql(expression, "having") 4599 4600 if having: 4601 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4602 4603 return self.func("ANY_VALUE", this) 4604 4605 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4606 transform = self.func("TRANSFORM", *expression.expressions) 4607 row_format_before = self.sql(expression, "row_format_before") 4608 row_format_before = f" {row_format_before}" if row_format_before else "" 4609 record_writer = self.sql(expression, "record_writer") 4610 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4611 using = f" USING {self.sql(expression, 'command_script')}" 4612 schema = self.sql(expression, "schema") 4613 schema = f" AS {schema}" if schema else "" 4614 row_format_after = self.sql(expression, "row_format_after") 4615 row_format_after = f" {row_format_after}" if row_format_after else "" 4616 record_reader = self.sql(expression, "record_reader") 4617 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4618 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4619 4620 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4621 key_block_size = self.sql(expression, "key_block_size") 4622 if key_block_size: 4623 return f"KEY_BLOCK_SIZE = {key_block_size}" 4624 4625 using = self.sql(expression, "using") 4626 if using: 4627 return f"USING {using}" 4628 4629 parser = self.sql(expression, "parser") 4630 if parser: 4631 return f"WITH PARSER {parser}" 4632 4633 comment = self.sql(expression, "comment") 4634 if comment: 4635 return f"COMMENT {comment}" 4636 4637 visible = expression.args.get("visible") 4638 if visible is not None: 4639 return "VISIBLE" if visible else "INVISIBLE" 4640 4641 engine_attr = self.sql(expression, "engine_attr") 4642 if engine_attr: 4643 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4644 4645 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4646 if secondary_engine_attr: 4647 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4648 4649 self.unsupported("Unsupported index constraint option.") 4650 return "" 4651 4652 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4653 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4654 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4655 4656 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4657 kind = self.sql(expression, "kind") 4658 kind = f"{kind} INDEX" if kind else "INDEX" 4659 this = self.sql(expression, "this") 4660 this = f" {this}" if this else "" 4661 index_type = self.sql(expression, "index_type") 4662 index_type = f" USING {index_type}" if index_type else "" 4663 expressions = self.expressions(expression, flat=True) 4664 expressions = f" ({expressions})" if expressions else "" 4665 options = self.expressions(expression, key="options", sep=" ") 4666 options = f" {options}" if options else "" 4667 return f"{kind}{this}{index_type}{expressions}{options}" 4668 4669 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4670 if self.NVL2_SUPPORTED: 4671 return self.function_fallback_sql(expression) 4672 4673 case = exp.Case().when( 4674 expression.this.is_(exp.null()).not_(copy=False), 4675 expression.args["true"], 4676 copy=False, 4677 ) 4678 else_cond = expression.args.get("false") 4679 if else_cond: 4680 case.else_(else_cond, copy=False) 4681 4682 return self.sql(case) 4683 4684 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4685 this = self.sql(expression, "this") 4686 expr = self.sql(expression, "expression") 4687 position = self.sql(expression, "position") 4688 position = f", {position}" if position else "" 4689 iterator = self.sql(expression, "iterator") 4690 condition = self.sql(expression, "condition") 4691 condition = f" IF {condition}" if condition else "" 4692 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4693 4694 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4695 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4696 4697 def opclass_sql(self, expression: exp.Opclass) -> str: 4698 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4699 4700 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4701 model = self.sql(expression, "this") 4702 model = f"MODEL {model}" 4703 expr = expression.expression 4704 if expr: 4705 expr_sql = self.sql(expression, "expression") 4706 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 4707 else: 4708 expr_sql = None 4709 4710 parameters = self.sql(expression, "params_struct") or None 4711 4712 return self.func(name, model, expr_sql, parameters) 4713 4714 def predict_sql(self, expression: exp.Predict) -> str: 4715 return self._ml_sql(expression, "PREDICT") 4716 4717 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4718 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4719 return self._ml_sql(expression, name) 4720 4721 def generatetext_sql(self, expression: exp.GenerateText) -> str: 4722 return self._ml_sql(expression, "GENERATE_TEXT") 4723 4724 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 4725 return self._ml_sql(expression, "GENERATE_TABLE") 4726 4727 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 4728 return self._ml_sql(expression, "GENERATE_BOOL") 4729 4730 def generateint_sql(self, expression: exp.GenerateInt) -> str: 4731 return self._ml_sql(expression, "GENERATE_INT") 4732 4733 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 4734 return self._ml_sql(expression, "GENERATE_DOUBLE") 4735 4736 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4737 return self._ml_sql(expression, "TRANSLATE") 4738 4739 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4740 return self._ml_sql(expression, "FORECAST") 4741 4742 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4743 this_sql = self.sql(expression, "this") 4744 if isinstance(expression.this, exp.Table): 4745 this_sql = f"TABLE {this_sql}" 4746 4747 return self.func( 4748 "FORECAST", 4749 this_sql, 4750 expression.args.get("data_col"), 4751 expression.args.get("timestamp_col"), 4752 expression.args.get("model"), 4753 expression.args.get("id_cols"), 4754 expression.args.get("horizon"), 4755 expression.args.get("forecast_end_timestamp"), 4756 expression.args.get("confidence_level"), 4757 expression.args.get("output_historical_time_series"), 4758 expression.args.get("context_window"), 4759 ) 4760 4761 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4762 this_sql = self.sql(expression, "this") 4763 if isinstance(expression.this, exp.Table): 4764 this_sql = f"TABLE {this_sql}" 4765 4766 return self.func( 4767 "FEATURES_AT_TIME", 4768 this_sql, 4769 expression.args.get("time"), 4770 expression.args.get("num_rows"), 4771 expression.args.get("ignore_feature_nulls"), 4772 ) 4773 4774 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4775 this_sql = self.sql(expression, "this") 4776 if isinstance(expression.this, exp.Table): 4777 this_sql = f"TABLE {this_sql}" 4778 4779 query_table = self.sql(expression, "query_table") 4780 if isinstance(expression.args["query_table"], exp.Table): 4781 query_table = f"TABLE {query_table}" 4782 4783 return self.func( 4784 "VECTOR_SEARCH", 4785 this_sql, 4786 expression.args.get("column_to_search"), 4787 query_table, 4788 expression.args.get("query_column_to_search"), 4789 expression.args.get("top_k"), 4790 expression.args.get("distance_type"), 4791 expression.args.get("options"), 4792 ) 4793 4794 def forin_sql(self, expression: exp.ForIn) -> str: 4795 this = self.sql(expression, "this") 4796 expression_sql = self.sql(expression, "expression") 4797 return f"FOR {this} DO {expression_sql}" 4798 4799 def refresh_sql(self, expression: exp.Refresh) -> str: 4800 this = self.sql(expression, "this") 4801 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4802 return f"REFRESH {kind}{this}" 4803 4804 def toarray_sql(self, expression: exp.ToArray) -> str: 4805 arg = expression.this 4806 if not arg.type: 4807 import sqlglot.optimizer.annotate_types 4808 4809 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 4810 4811 if arg.is_type(exp.DType.ARRAY): 4812 return self.sql(arg) 4813 4814 cond_for_null = arg.is_(exp.null()) 4815 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4816 4817 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4818 this = expression.this 4819 time_format = self.format_time(expression) 4820 4821 if time_format: 4822 return self.sql( 4823 exp.cast( 4824 exp.StrToTime(this=this, format=expression.args["format"]), 4825 exp.DType.TIME, 4826 ) 4827 ) 4828 4829 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 4830 return self.sql(this) 4831 4832 return self.sql(exp.cast(this, exp.DType.TIME)) 4833 4834 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4835 this = expression.this 4836 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 4837 return self.sql(this) 4838 4839 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 4840 4841 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4842 this = expression.this 4843 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 4844 return self.sql(this) 4845 4846 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 4847 4848 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4849 this = expression.this 4850 time_format = self.format_time(expression) 4851 safe = expression.args.get("safe") 4852 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4853 return self.sql( 4854 exp.cast( 4855 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4856 exp.DType.DATE, 4857 ) 4858 ) 4859 4860 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 4861 return self.sql(this) 4862 4863 if safe: 4864 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 4865 4866 return self.sql(exp.cast(this, exp.DType.DATE)) 4867 4868 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4869 return self.sql( 4870 exp.func( 4871 "DATEDIFF", 4872 expression.this, 4873 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 4874 "day", 4875 ) 4876 ) 4877 4878 def lastday_sql(self, expression: exp.LastDay) -> str: 4879 if self.LAST_DAY_SUPPORTS_DATE_PART: 4880 return self.function_fallback_sql(expression) 4881 4882 unit = expression.text("unit") 4883 if unit and unit != "MONTH": 4884 self.unsupported("Date parts are not supported in LAST_DAY.") 4885 4886 return self.func("LAST_DAY", expression.this) 4887 4888 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4889 import sqlglot.dialects.dialect 4890 4891 return self.func( 4892 "DATE_ADD", 4893 expression.this, 4894 expression.expression, 4895 sqlglot.dialects.dialect.unit_to_str(expression), 4896 ) 4897 4898 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4899 if self.CAN_IMPLEMENT_ARRAY_ANY: 4900 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4901 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4902 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4903 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4904 4905 import sqlglot.dialects.dialect 4906 4907 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4908 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 4909 self.unsupported("ARRAY_ANY is unsupported") 4910 4911 return self.function_fallback_sql(expression) 4912 4913 def struct_sql(self, expression: exp.Struct) -> str: 4914 expression.set( 4915 "expressions", 4916 [ 4917 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4918 if isinstance(e, exp.PropertyEQ) 4919 else e 4920 for e in expression.expressions 4921 ], 4922 ) 4923 4924 return self.function_fallback_sql(expression) 4925 4926 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4927 low = self.sql(expression, "this") 4928 high = self.sql(expression, "expression") 4929 4930 return f"{low} TO {high}" 4931 4932 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4933 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4934 tables = f" {self.expressions(expression)}" 4935 4936 exists = " IF EXISTS" if expression.args.get("exists") else "" 4937 4938 on_cluster = self.sql(expression, "cluster") 4939 on_cluster = f" {on_cluster}" if on_cluster else "" 4940 4941 identity = self.sql(expression, "identity") 4942 identity = f" {identity} IDENTITY" if identity else "" 4943 4944 option = self.sql(expression, "option") 4945 option = f" {option}" if option else "" 4946 4947 partition = self.sql(expression, "partition") 4948 partition = f" {partition}" if partition else "" 4949 4950 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4951 4952 # This transpiles T-SQL's CONVERT function 4953 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4954 def convert_sql(self, expression: exp.Convert) -> str: 4955 to = expression.this 4956 value = expression.expression 4957 style = expression.args.get("style") 4958 safe = expression.args.get("safe") 4959 strict = expression.args.get("strict") 4960 4961 if not to or not value: 4962 return "" 4963 4964 # Retrieve length of datatype and override to default if not specified 4965 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4966 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4967 4968 transformed: exp.Expr | None = None 4969 cast = exp.Cast if strict else exp.TryCast 4970 4971 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4972 if isinstance(style, exp.Literal) and style.is_int: 4973 import sqlglot.dialects.tsql 4974 4975 style_value = style.name 4976 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4977 if not converted_style: 4978 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4979 4980 fmt = exp.Literal.string(converted_style) 4981 4982 if to.this == exp.DType.DATE: 4983 transformed = exp.StrToDate(this=value, format=fmt) 4984 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 4985 transformed = exp.StrToTime(this=value, format=fmt) 4986 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4987 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4988 elif to.this == exp.DType.TEXT: 4989 transformed = exp.TimeToStr(this=value, format=fmt) 4990 4991 if not transformed: 4992 transformed = cast(this=value, to=to, safe=safe) 4993 4994 return self.sql(transformed) 4995 4996 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4997 this = expression.this 4998 if isinstance(this, exp.JSONPathWildcard): 4999 this = self.json_path_part(this) 5000 return f".{this}" if this else "" 5001 5002 if self.SAFE_JSON_PATH_KEY_RE.match(this): 5003 return f".{this}" 5004 5005 this = self.json_path_part(this) 5006 return ( 5007 f"[{this}]" 5008 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 5009 else f".{this}" 5010 ) 5011 5012 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 5013 this = self.json_path_part(expression.this) 5014 return f"[{this}]" if this else "" 5015 5016 def _simplify_unless_literal(self, expression: E) -> E: 5017 if not isinstance(expression, exp.Literal): 5018 import sqlglot.optimizer.simplify 5019 5020 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5021 5022 return expression 5023 5024 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5025 this = expression.this 5026 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5027 self.unsupported( 5028 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5029 ) 5030 return self.sql(this) 5031 5032 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 5033 if self.IGNORE_NULLS_BEFORE_ORDER: 5034 # The first modifier here will be the one closest to the AggFunc's arg 5035 mods = sorted( 5036 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5037 key=lambda x: ( 5038 0 5039 if isinstance(x, exp.HavingMax) 5040 else (1 if isinstance(x, exp.Order) else 2) 5041 ), 5042 ) 5043 5044 if mods: 5045 mod = mods[0] 5046 this = expression.__class__(this=mod.this.copy()) 5047 this.meta["inline"] = True 5048 mod.this.replace(this) 5049 return self.sql(expression.this) 5050 5051 agg_func = expression.find(exp.AggFunc) 5052 5053 if agg_func: 5054 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5055 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5056 5057 return f"{self.sql(expression, 'this')} {text}" 5058 5059 def _replace_line_breaks(self, string: str) -> str: 5060 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5061 if self.pretty: 5062 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5063 return string 5064 5065 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5066 option = self.sql(expression, "this") 5067 5068 if expression.expressions: 5069 upper = option.upper() 5070 5071 # Snowflake FILE_FORMAT options are separated by whitespace 5072 sep = " " if upper == "FILE_FORMAT" else ", " 5073 5074 # Databricks copy/format options do not set their list of values with EQ 5075 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5076 values = self.expressions(expression, flat=True, sep=sep) 5077 return f"{option}{op}({values})" 5078 5079 value = self.sql(expression, "expression") 5080 5081 if not value: 5082 return option 5083 5084 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5085 5086 return f"{option}{op}{value}" 5087 5088 def credentials_sql(self, expression: exp.Credentials) -> str: 5089 cred_expr = expression.args.get("credentials") 5090 if isinstance(cred_expr, exp.Literal): 5091 # Redshift case: CREDENTIALS <string> 5092 credentials = self.sql(expression, "credentials") 5093 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5094 else: 5095 # Snowflake case: CREDENTIALS = (...) 5096 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5097 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5098 5099 storage = self.sql(expression, "storage") 5100 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5101 5102 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5103 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5104 5105 iam_role = self.sql(expression, "iam_role") 5106 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5107 5108 region = self.sql(expression, "region") 5109 region = f" REGION {region}" if region else "" 5110 5111 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5112 5113 def copy_sql(self, expression: exp.Copy) -> str: 5114 this = self.sql(expression, "this") 5115 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5116 5117 credentials = self.sql(expression, "credentials") 5118 credentials = self.seg(credentials) if credentials else "" 5119 files = self.expressions(expression, key="files", flat=True) 5120 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5121 5122 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5123 params = self.expressions( 5124 expression, 5125 key="params", 5126 sep=sep, 5127 new_line=True, 5128 skip_last=True, 5129 skip_first=True, 5130 indent=self.COPY_PARAMS_ARE_WRAPPED, 5131 ) 5132 5133 if params: 5134 if self.COPY_PARAMS_ARE_WRAPPED: 5135 params = f" WITH ({params})" 5136 elif not self.pretty and (files or credentials): 5137 params = f" {params}" 5138 5139 return f"COPY{this}{kind} {files}{credentials}{params}" 5140 5141 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5142 return "" 5143 5144 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5145 on_sql = "ON" if expression.args.get("on") else "OFF" 5146 filter_col: str | None = self.sql(expression, "filter_column") 5147 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5148 retention_period: str | None = self.sql(expression, "retention_period") 5149 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5150 5151 if filter_col or retention_period: 5152 on_sql = self.func("ON", filter_col, retention_period) 5153 5154 return f"DATA_DELETION={on_sql}" 5155 5156 def maskingpolicycolumnconstraint_sql( 5157 self, expression: exp.MaskingPolicyColumnConstraint 5158 ) -> str: 5159 this = self.sql(expression, "this") 5160 expressions = self.expressions(expression, flat=True) 5161 expressions = f" USING ({expressions})" if expressions else "" 5162 return f"MASKING POLICY {this}{expressions}" 5163 5164 def gapfill_sql(self, expression: exp.GapFill) -> str: 5165 this = self.sql(expression, "this") 5166 this = f"TABLE {this}" 5167 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5168 5169 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5170 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5171 5172 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5173 this = self.sql(expression, "this") 5174 expr = expression.expression 5175 5176 if isinstance(expr, exp.Func): 5177 # T-SQL's CLR functions are case sensitive 5178 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5179 else: 5180 expr = self.sql(expression, "expression") 5181 5182 return self.scope_resolution(expr, this) 5183 5184 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5185 if self.PARSE_JSON_NAME is None: 5186 return self.sql(expression.this) 5187 5188 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5189 5190 def rand_sql(self, expression: exp.Rand) -> str: 5191 lower = self.sql(expression, "lower") 5192 upper = self.sql(expression, "upper") 5193 5194 if lower and upper: 5195 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5196 return self.func("RAND", expression.this) 5197 5198 def changes_sql(self, expression: exp.Changes) -> str: 5199 information = self.sql(expression, "information") 5200 information = f"INFORMATION => {information}" 5201 at_before = self.sql(expression, "at_before") 5202 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5203 end = self.sql(expression, "end") 5204 end = f"{self.seg('')}{end}" if end else "" 5205 5206 return f"CHANGES ({information}){at_before}{end}" 5207 5208 def pad_sql(self, expression: exp.Pad) -> str: 5209 prefix = "L" if expression.args.get("is_left") else "R" 5210 5211 fill_pattern = self.sql(expression, "fill_pattern") or None 5212 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5213 fill_pattern = "' '" 5214 5215 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5216 5217 def summarize_sql(self, expression: exp.Summarize) -> str: 5218 table = " TABLE" if expression.args.get("table") else "" 5219 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5220 5221 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5222 generate_series = exp.GenerateSeries(**expression.args) 5223 5224 parent = expression.parent 5225 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5226 parent = parent.parent 5227 5228 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5229 return self.sql(exp.Unnest(expressions=[generate_series])) 5230 5231 if isinstance(parent, exp.Select): 5232 self.unsupported("GenerateSeries projection unnesting is not supported.") 5233 5234 return self.sql(generate_series) 5235 5236 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5237 if self.SUPPORTS_CONVERT_TIMEZONE: 5238 return self.function_fallback_sql(expression) 5239 5240 source_tz = expression.args.get("source_tz") 5241 target_tz = expression.args.get("target_tz") 5242 timestamp = expression.args.get("timestamp") 5243 5244 if source_tz and timestamp: 5245 timestamp = exp.AtTimeZone( 5246 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5247 ) 5248 5249 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5250 5251 return self.sql(expr) 5252 5253 def json_sql(self, expression: exp.JSON) -> str: 5254 this = self.sql(expression, "this") 5255 this = f" {this}" if this else "" 5256 5257 _with = expression.args.get("with_") 5258 5259 if _with is None: 5260 with_sql = "" 5261 elif not _with: 5262 with_sql = " WITHOUT" 5263 else: 5264 with_sql = " WITH" 5265 5266 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5267 5268 return f"JSON{this}{with_sql}{unique_sql}" 5269 5270 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5271 path = self.sql(expression, "path") 5272 returning = self.sql(expression, "returning") 5273 returning = f" RETURNING {returning}" if returning else "" 5274 5275 on_condition = self.sql(expression, "on_condition") 5276 on_condition = f" {on_condition}" if on_condition else "" 5277 5278 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5279 5280 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5281 regexp = " REGEXP" if expression.args.get("regexp") else "" 5282 return f"SKIP{regexp} {self.sql(expression.expression)}" 5283 5284 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5285 else_ = "ELSE " if expression.args.get("else_") else "" 5286 condition = self.sql(expression, "expression") 5287 condition = f"WHEN {condition} THEN " if condition else else_ 5288 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5289 return f"{condition}{insert}" 5290 5291 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5292 kind = self.sql(expression, "kind") 5293 expressions = self.seg(self.expressions(expression, sep=" ")) 5294 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5295 return res 5296 5297 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5298 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5299 empty = expression.args.get("empty") 5300 empty = ( 5301 f"DEFAULT {empty} ON EMPTY" 5302 if isinstance(empty, exp.Expr) 5303 else self.sql(expression, "empty") 5304 ) 5305 5306 error = expression.args.get("error") 5307 error = ( 5308 f"DEFAULT {error} ON ERROR" 5309 if isinstance(error, exp.Expr) 5310 else self.sql(expression, "error") 5311 ) 5312 5313 if error and empty: 5314 error = ( 5315 f"{empty} {error}" 5316 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5317 else f"{error} {empty}" 5318 ) 5319 empty = "" 5320 5321 null = self.sql(expression, "null") 5322 5323 return f"{empty}{error}{null}" 5324 5325 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5326 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5327 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5328 5329 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5330 this = self.sql(expression, "this") 5331 path = self.sql(expression, "path") 5332 5333 passing = self.expressions(expression, "passing") 5334 passing = f" PASSING {passing}" if passing else "" 5335 5336 on_condition = self.sql(expression, "on_condition") 5337 on_condition = f" {on_condition}" if on_condition else "" 5338 5339 path = f"{path}{passing}{on_condition}" 5340 5341 return self.func("JSON_EXISTS", this, path) 5342 5343 def _add_arrayagg_null_filter( 5344 self, 5345 array_agg_sql: str, 5346 array_agg_expr: exp.ArrayAgg, 5347 column_expr: exp.Expr, 5348 ) -> str: 5349 """ 5350 Add NULL filter to ARRAY_AGG if dialect requires it. 5351 5352 Args: 5353 array_agg_sql: The generated ARRAY_AGG SQL string 5354 array_agg_expr: The ArrayAgg expression node 5355 column_expr: The column/expression to filter (before ORDER BY wrapping) 5356 5357 Returns: 5358 SQL string with FILTER clause added if needed 5359 """ 5360 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5361 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5362 if not ( 5363 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5364 ): 5365 return array_agg_sql 5366 5367 parent = array_agg_expr.parent 5368 if isinstance(parent, exp.Filter): 5369 parent_cond = parent.expression.this 5370 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5371 elif column_expr.find(exp.Column): 5372 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5373 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5374 this_sql = ( 5375 self.expressions(column_expr) 5376 if isinstance(column_expr, exp.Distinct) 5377 else self.sql(column_expr) 5378 ) 5379 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5380 5381 return array_agg_sql 5382 5383 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5384 array_agg = self.function_fallback_sql(expression) 5385 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5386 5387 def slice_sql(self, expression: exp.Slice) -> str: 5388 step = self.sql(expression, "step") 5389 end = self.sql(expression.expression) 5390 begin = self.sql(expression.this) 5391 5392 sql = f"{end}:{step}" if step else end 5393 return f"{begin}:{sql}" if sql else f"{begin}:" 5394 5395 def apply_sql(self, expression: exp.Apply) -> str: 5396 this = self.sql(expression, "this") 5397 expr = self.sql(expression, "expression") 5398 5399 return f"{this} APPLY({expr})" 5400 5401 def _grant_or_revoke_sql( 5402 self, 5403 expression: exp.Grant | exp.Revoke, 5404 keyword: str, 5405 preposition: str, 5406 grant_option_prefix: str = "", 5407 grant_option_suffix: str = "", 5408 ) -> str: 5409 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5410 5411 kind = self.sql(expression, "kind") 5412 kind = f" {kind}" if kind else "" 5413 5414 securable = self.sql(expression, "securable") 5415 securable = f" {securable}" if securable else "" 5416 5417 principals = self.expressions(expression, key="principals", flat=True) 5418 5419 if not expression.args.get("grant_option"): 5420 grant_option_prefix = grant_option_suffix = "" 5421 5422 # cascade for revoke only 5423 cascade = self.sql(expression, "cascade") 5424 cascade = f" {cascade}" if cascade else "" 5425 5426 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5427 5428 def grant_sql(self, expression: exp.Grant) -> str: 5429 return self._grant_or_revoke_sql( 5430 expression, 5431 keyword="GRANT", 5432 preposition="TO", 5433 grant_option_suffix=" WITH GRANT OPTION", 5434 ) 5435 5436 def revoke_sql(self, expression: exp.Revoke) -> str: 5437 return self._grant_or_revoke_sql( 5438 expression, 5439 keyword="REVOKE", 5440 preposition="FROM", 5441 grant_option_prefix="GRANT OPTION FOR ", 5442 ) 5443 5444 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5445 this = self.sql(expression, "this") 5446 columns = self.expressions(expression, flat=True) 5447 columns = f"({columns})" if columns else "" 5448 5449 return f"{this}{columns}" 5450 5451 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5452 this = self.sql(expression, "this") 5453 5454 kind = self.sql(expression, "kind") 5455 kind = f"{kind} " if kind else "" 5456 5457 return f"{kind}{this}" 5458 5459 def columns_sql(self, expression: exp.Columns) -> str: 5460 func = self.function_fallback_sql(expression) 5461 if expression.args.get("unpack"): 5462 func = f"*{func}" 5463 5464 return func 5465 5466 def overlay_sql(self, expression: exp.Overlay) -> str: 5467 this = self.sql(expression, "this") 5468 expr = self.sql(expression, "expression") 5469 from_sql = self.sql(expression, "from_") 5470 for_sql = self.sql(expression, "for_") 5471 for_sql = f" FOR {for_sql}" if for_sql else "" 5472 5473 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5474 5475 @unsupported_args("format") 5476 def todouble_sql(self, expression: exp.ToDouble) -> str: 5477 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5478 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5479 5480 def string_sql(self, expression: exp.String) -> str: 5481 this = expression.this 5482 zone = expression.args.get("zone") 5483 5484 if zone: 5485 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5486 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5487 # set for source_tz to transpile the time conversion before the STRING cast 5488 this = exp.ConvertTimezone( 5489 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5490 ) 5491 5492 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5493 5494 def median_sql(self, expression: exp.Median) -> str: 5495 if not self.SUPPORTS_MEDIAN: 5496 return self.sql( 5497 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5498 ) 5499 5500 return self.function_fallback_sql(expression) 5501 5502 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5503 filler = self.sql(expression, "this") 5504 filler = f" {filler}" if filler else "" 5505 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5506 return f"TRUNCATE{filler} {with_count}" 5507 5508 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5509 if self.SUPPORTS_UNIX_SECONDS: 5510 return self.function_fallback_sql(expression) 5511 5512 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5513 5514 return self.sql( 5515 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5516 ) 5517 5518 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5519 dim = expression.expression 5520 5521 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5522 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5523 if not (dim.is_int and dim.name == "1"): 5524 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5525 dim = None 5526 5527 # If dimension is required but not specified, default initialize it 5528 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5529 dim = exp.Literal.number(1) 5530 5531 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5532 5533 def attach_sql(self, expression: exp.Attach) -> str: 5534 this = self.sql(expression, "this") 5535 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5536 expressions = self.expressions(expression) 5537 expressions = f" ({expressions})" if expressions else "" 5538 5539 return f"ATTACH{exists_sql} {this}{expressions}" 5540 5541 def detach_sql(self, expression: exp.Detach) -> str: 5542 kind = self.sql(expression, "kind") 5543 kind = f" {kind}" if kind else "" 5544 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5545 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5546 exists = " IF EXISTS" if expression.args.get("exists") else "" 5547 if exists: 5548 kind = kind or " DATABASE" 5549 5550 this = self.sql(expression, "this") 5551 this = f" {this}" if this else "" 5552 cluster = self.sql(expression, "cluster") 5553 cluster = f" {cluster}" if cluster else "" 5554 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5555 sync = " SYNC" if expression.args.get("sync") else "" 5556 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5557 5558 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5559 this = self.sql(expression, "this") 5560 value = self.sql(expression, "expression") 5561 value = f" {value}" if value else "" 5562 return f"{this}{value}" 5563 5564 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5565 return ( 5566 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5567 ) 5568 5569 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5570 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5571 encode = f"{encode} {self.sql(expression, 'this')}" 5572 5573 properties = expression.args.get("properties") 5574 if properties: 5575 encode = f"{encode} {self.properties(properties)}" 5576 5577 return encode 5578 5579 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5580 this = self.sql(expression, "this") 5581 include = f"INCLUDE {this}" 5582 5583 column_def = self.sql(expression, "column_def") 5584 if column_def: 5585 include = f"{include} {column_def}" 5586 5587 alias = self.sql(expression, "alias") 5588 if alias: 5589 include = f"{include} AS {alias}" 5590 5591 return include 5592 5593 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5594 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5595 name = f"{prefix} {self.sql(expression, 'this')}" 5596 return self.func("XMLELEMENT", name, *expression.expressions) 5597 5598 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5599 this = self.sql(expression, "this") 5600 expr = self.sql(expression, "expression") 5601 expr = f"({expr})" if expr else "" 5602 return f"{this}{expr}" 5603 5604 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5605 partitions = self.expressions(expression, "partition_expressions") 5606 create = self.expressions(expression, "create_expressions") 5607 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5608 5609 def partitionbyrangepropertydynamic_sql( 5610 self, expression: exp.PartitionByRangePropertyDynamic 5611 ) -> str: 5612 start = self.sql(expression, "start") 5613 end = self.sql(expression, "end") 5614 5615 every = expression.args["every"] 5616 if isinstance(every, exp.Interval) and every.this.is_string: 5617 every.this.replace(exp.Literal.number(every.name)) 5618 5619 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5620 5621 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5622 name = self.sql(expression, "this") 5623 values = self.expressions(expression, flat=True) 5624 5625 return f"NAME {name} VALUE {values}" 5626 5627 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5628 kind = self.sql(expression, "kind") 5629 sample = self.sql(expression, "sample") 5630 return f"SAMPLE {sample} {kind}" 5631 5632 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5633 kind = self.sql(expression, "kind") 5634 option = self.sql(expression, "option") 5635 option = f" {option}" if option else "" 5636 this = self.sql(expression, "this") 5637 this = f" {this}" if this else "" 5638 columns = self.expressions(expression) 5639 columns = f" {columns}" if columns else "" 5640 return f"{kind}{option} STATISTICS{this}{columns}" 5641 5642 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5643 this = self.sql(expression, "this") 5644 columns = self.expressions(expression) 5645 inner_expression = self.sql(expression, "expression") 5646 inner_expression = f" {inner_expression}" if inner_expression else "" 5647 update_options = self.sql(expression, "update_options") 5648 update_options = f" {update_options} UPDATE" if update_options else "" 5649 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5650 5651 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5652 kind = self.sql(expression, "kind") 5653 kind = f" {kind}" if kind else "" 5654 return f"DELETE{kind} STATISTICS" 5655 5656 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5657 inner_expression = self.sql(expression, "expression") 5658 return f"LIST CHAINED ROWS{inner_expression}" 5659 5660 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5661 kind = self.sql(expression, "kind") 5662 this = self.sql(expression, "this") 5663 this = f" {this}" if this else "" 5664 inner_expression = self.sql(expression, "expression") 5665 return f"VALIDATE {kind}{this}{inner_expression}" 5666 5667 def analyze_sql(self, expression: exp.Analyze) -> str: 5668 options = self.expressions(expression, key="options", sep=" ") 5669 options = f" {options}" if options else "" 5670 kind = self.sql(expression, "kind") 5671 kind = f" {kind}" if kind else "" 5672 this = self.sql(expression, "this") 5673 this = f" {this}" if this else "" 5674 mode = self.sql(expression, "mode") 5675 mode = f" {mode}" if mode else "" 5676 properties = self.sql(expression, "properties") 5677 properties = f" {properties}" if properties else "" 5678 partition = self.sql(expression, "partition") 5679 partition = f" {partition}" if partition else "" 5680 inner_expression = self.sql(expression, "expression") 5681 inner_expression = f" {inner_expression}" if inner_expression else "" 5682 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5683 5684 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5685 this = self.sql(expression, "this") 5686 namespaces = self.expressions(expression, key="namespaces") 5687 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5688 passing = self.expressions(expression, key="passing") 5689 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5690 columns = self.expressions(expression, key="columns") 5691 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5692 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5693 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5694 5695 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5696 this = self.sql(expression, "this") 5697 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5698 5699 def export_sql(self, expression: exp.Export) -> str: 5700 this = self.sql(expression, "this") 5701 connection = self.sql(expression, "connection") 5702 connection = f"WITH CONNECTION {connection} " if connection else "" 5703 options = self.sql(expression, "options") 5704 return f"EXPORT DATA {connection}{options} AS {this}" 5705 5706 def declare_sql(self, expression: exp.Declare) -> str: 5707 replace = "OR REPLACE " if expression.args.get("replace") else "" 5708 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 5709 5710 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5711 variables = self.expressions(expression, "this") 5712 default = self.sql(expression, "default") 5713 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5714 5715 kind = self.sql(expression, "kind") 5716 if isinstance(expression.args.get("kind"), exp.Schema): 5717 kind = f"TABLE {kind}" 5718 5719 kind = f" {kind}" if kind else "" 5720 5721 return f"{variables}{kind}{default}" 5722 5723 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5724 kind = self.sql(expression, "kind") 5725 this = self.sql(expression, "this") 5726 set = self.sql(expression, "expression") 5727 using = self.sql(expression, "using") 5728 using = f" USING {using}" if using else "" 5729 5730 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5731 5732 return f"{kind_sql} {this} SET {set}{using}" 5733 5734 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5735 params = self.expressions(expression, key="params", flat=True) 5736 return self.func(expression.name, *expression.expressions) + f"({params})" 5737 5738 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5739 return self.func(expression.name, *expression.expressions) 5740 5741 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5742 return self.anonymousaggfunc_sql(expression) 5743 5744 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5745 return self.parameterizedagg_sql(expression) 5746 5747 def show_sql(self, expression: exp.Show) -> str: 5748 self.unsupported("Unsupported SHOW statement") 5749 return "" 5750 5751 def install_sql(self, expression: exp.Install) -> str: 5752 self.unsupported("Unsupported INSTALL statement") 5753 return "" 5754 5755 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5756 # Snowflake GET/PUT statements: 5757 # PUT <file> <internalStage> <properties> 5758 # GET <internalStage> <file> <properties> 5759 props = expression.args.get("properties") 5760 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5761 this = self.sql(expression, "this") 5762 target = self.sql(expression, "target") 5763 5764 if isinstance(expression, exp.Put): 5765 return f"PUT {this} {target}{props_sql}" 5766 else: 5767 return f"GET {target} {this}{props_sql}" 5768 5769 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5770 this = self.sql(expression, "this") 5771 expr = self.sql(expression, "expression") 5772 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5773 return f"TRANSLATE({this} USING {expr}{with_error})" 5774 5775 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5776 if self.SUPPORTS_DECODE_CASE: 5777 return self.func("DECODE", *expression.expressions) 5778 5779 decode_expr, *expressions = expression.expressions 5780 5781 ifs = [] 5782 for search, result in zip(expressions[::2], expressions[1::2]): 5783 if isinstance(search, exp.Literal): 5784 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 5785 elif isinstance(search, exp.Null): 5786 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 5787 else: 5788 if isinstance(search, exp.Binary): 5789 search = exp.paren(search) 5790 5791 cond = exp.or_( 5792 decode_expr.eq(search), 5793 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5794 copy=False, 5795 ) 5796 ifs.append(exp.If(this=cond, true=result)) 5797 5798 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5799 return self.sql(case) 5800 5801 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5802 this = self.sql(expression, "this") 5803 this = self.seg(this, sep="") 5804 dimensions = self.expressions( 5805 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5806 ) 5807 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5808 metrics = self.expressions( 5809 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5810 ) 5811 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5812 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5813 facts = self.seg(f"FACTS {facts}") if facts else "" 5814 where = self.sql(expression, "where") 5815 where = self.seg(f"WHERE {where}") if where else "" 5816 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5817 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5818 5819 def getextract_sql(self, expression: exp.GetExtract) -> str: 5820 this = expression.this 5821 expr = expression.expression 5822 5823 if not this.type or not expression.type: 5824 import sqlglot.optimizer.annotate_types 5825 5826 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 5827 5828 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 5829 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5830 5831 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5832 5833 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5834 return self.sql( 5835 exp.DateAdd( 5836 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5837 expression=expression.this, 5838 unit=exp.var("DAY"), 5839 ) 5840 ) 5841 5842 def space_sql(self: Generator, expression: exp.Space) -> str: 5843 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5844 5845 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5846 return f"BUILD {self.sql(expression, 'this')}" 5847 5848 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5849 method = self.sql(expression, "method") 5850 kind = expression.args.get("kind") 5851 if not kind: 5852 return f"REFRESH {method}" 5853 5854 every = self.sql(expression, "every") 5855 unit = self.sql(expression, "unit") 5856 every = f" EVERY {every} {unit}" if every else "" 5857 starts = self.sql(expression, "starts") 5858 starts = f" STARTS {starts}" if starts else "" 5859 5860 return f"REFRESH {method} ON {kind}{every}{starts}" 5861 5862 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5863 self.unsupported("The model!attribute syntax is not supported") 5864 return "" 5865 5866 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5867 return self.func("DIRECTORY", expression.this) 5868 5869 def uuid_sql(self, expression: exp.Uuid) -> str: 5870 is_string = expression.args.get("is_string", False) 5871 uuid_func_sql = self.func("UUID") 5872 5873 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5874 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 5875 5876 return uuid_func_sql 5877 5878 def initcap_sql(self, expression: exp.Initcap) -> str: 5879 delimiters = expression.expression 5880 5881 if delimiters: 5882 # do not generate delimiters arg if we are round-tripping from default delimiters 5883 if ( 5884 delimiters.is_string 5885 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5886 ): 5887 delimiters = None 5888 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5889 self.unsupported("INITCAP does not support custom delimiters") 5890 delimiters = None 5891 5892 return self.func("INITCAP", expression.this, delimiters) 5893 5894 def localtime_sql(self, expression: exp.Localtime) -> str: 5895 this = expression.this 5896 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5897 5898 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 5899 this = expression.this 5900 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5901 5902 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5903 this = expression.this.name.upper() 5904 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5905 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5906 return "WEEK" 5907 5908 return self.func("WEEK", expression.this) 5909 5910 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5911 this = self.expressions(expression) 5912 charset = self.sql(expression, "charset") 5913 using = f" USING {charset}" if charset else "" 5914 return self.func(name, this + using) 5915 5916 def block_sql(self, expression: exp.Block) -> str: 5917 expressions = self.expressions(expression, sep="; ", flat=True) 5918 return f"{expressions}" if expressions else "" 5919 5920 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 5921 self.unsupported("Unsupported Stored Procedure syntax") 5922 return "" 5923 5924 def ifblock_sql(self, expression: exp.IfBlock) -> str: 5925 self.unsupported("Unsupported If block syntax") 5926 return "" 5927 5928 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 5929 self.unsupported("Unsupported While block syntax") 5930 return "" 5931 5932 def execute_sql(self, expression: exp.Execute) -> str: 5933 self.unsupported("Unsupported Execute syntax") 5934 return "" 5935 5936 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 5937 self.unsupported("Unsupported Execute syntax") 5938 return "" 5939 5940 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 5941 props = self.expressions(expression, sep=" ") 5942 return f"MODIFY {props}" 5943 5944 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 5945 kind = expression.args.get("kind") 5946 return f"USING {kind} {self.sql(expression, 'this')}" 5947 5948 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 5949 this = self.sql(expression, "this") 5950 to = self.sql(expression, "to") 5951 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)
845 def __init__( 846 self, 847 pretty: bool | int | None = None, 848 identify: str | bool = False, 849 normalize: bool = False, 850 pad: int = 2, 851 indent: int = 2, 852 normalize_functions: str | bool | None = None, 853 unsupported_level: ErrorLevel = ErrorLevel.WARN, 854 max_unsupported: int = 3, 855 leading_comma: bool = False, 856 max_text_width: int = 80, 857 comments: bool = True, 858 dialect: DialectType = None, 859 ): 860 import sqlglot 861 import sqlglot.dialects.dialect 862 863 self.pretty = pretty if pretty is not None else sqlglot.pretty 864 self.identify = identify 865 self.normalize = normalize 866 self.pad = pad 867 self._indent = indent 868 self.unsupported_level = unsupported_level 869 self.max_unsupported = max_unsupported 870 self.leading_comma = leading_comma 871 self.max_text_width = max_text_width 872 self.comments = comments 873 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 874 875 # This is both a Dialect property and a Generator argument, so we prioritize the latter 876 self.normalize_functions = ( 877 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 878 ) 879 880 self.unsupported_messages: list[str] = [] 881 self._escaped_quote_end: str = ( 882 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 883 ) 884 self._escaped_byte_quote_end: str = ( 885 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 886 if self.dialect.BYTE_END 887 else "" 888 ) 889 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 890 891 self._next_name = name_sequence("_t") 892 893 self._identifier_start = self.dialect.IDENTIFIER_START 894 self._identifier_end = self.dialect.IDENTIFIER_END 895 896 self._quote_json_path_key_using_brackets = True 897 898 cls = type(self) 899 dispatch = _DISPATCH_CACHE.get(cls) 900 if dispatch is None: 901 dispatch = _build_dispatch(cls) 902 _DISPATCH_CACHE[cls] = dispatch 903 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.JSONPathRecursive'>, <class 'sqlglot.expressions.query.JSONPathFilter'>, <class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathWildcard'>, <class 'sqlglot.expressions.query.JSONPathUnion'>, <class 'sqlglot.expressions.query.JSONPathSubscript'>, <class 'sqlglot.expressions.query.JSONPathSelector'>, <class 'sqlglot.expressions.query.JSONPathSlice'>, <class 'sqlglot.expressions.query.JSONPathScript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>}
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'}
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.CHAR: 'CHAR'>, <DType.NCHAR: 'NCHAR'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
()
905 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 906 """ 907 Generates the SQL string corresponding to the given syntax tree. 908 909 Args: 910 expression: The syntax tree. 911 copy: Whether to copy the expression. The generator performs mutations so 912 it is safer to copy. 913 914 Returns: 915 The SQL string corresponding to `expression`. 916 """ 917 if copy: 918 expression = expression.copy() 919 920 expression = self.preprocess(expression) 921 922 self.unsupported_messages = [] 923 sql = self.sql(expression).strip() 924 925 if self.pretty: 926 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 927 928 if self.unsupported_level == ErrorLevel.IGNORE: 929 return sql 930 931 if self.unsupported_level == ErrorLevel.WARN: 932 for msg in self.unsupported_messages: 933 logger.warning(msg) 934 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 935 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 936 937 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.
939 def preprocess(self, expression: exp.Expr) -> exp.Expr: 940 """Apply generic preprocessing transformations to a given expression.""" 941 expression = self._move_ctes_to_top_level(expression) 942 943 if self.ENSURE_BOOLS: 944 import sqlglot.transforms 945 946 expression = sqlglot.transforms.ensure_bools(expression) 947 948 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
972 def sanitize_comment(self, comment: str) -> str: 973 comment = " " + comment if comment[0].strip() else comment 974 comment = comment + " " if comment[-1].strip() else comment 975 976 # Escape block comment markers to prevent premature closure or unintended nesting. 977 # This is necessary because single-line comments (--) are converted to block comments 978 # (/* */) on output, and any */ in the original text would close the comment early. 979 comment = comment.replace("*/", "* /").replace("/*", "/ *") 980 981 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
983 def maybe_comment( 984 self, 985 sql: str, 986 expression: exp.Expr | None = None, 987 comments: list[str] | None = None, 988 separated: bool = False, 989 ) -> str: 990 comments = ( 991 ((expression and expression.comments) if comments is None else comments) # type: ignore 992 if self.comments 993 else None 994 ) 995 996 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 997 return sql 998 999 comments_sql = " ".join( 1000 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 1001 ) 1002 1003 if not comments_sql: 1004 return sql 1005 1006 comments_sql = self._replace_line_breaks(comments_sql) 1007 1008 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1009 return ( 1010 f"{self.sep()}{comments_sql}{sql}" 1011 if not sql or sql[0].isspace() 1012 else f"{comments_sql}{self.sep()}{sql}" 1013 ) 1014 1015 return f"{sql} {comments_sql}"
1017 def wrap(self, expression: exp.Expr | str) -> str: 1018 this_sql = ( 1019 self.sql(expression) 1020 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1021 else self.sql(expression, "this") 1022 ) 1023 if not this_sql: 1024 return "()" 1025 1026 this_sql = self.indent(this_sql, level=1, pad=0) 1027 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:
1043 def indent( 1044 self, 1045 sql: str, 1046 level: int = 0, 1047 pad: int | None = None, 1048 skip_first: bool = False, 1049 skip_last: bool = False, 1050 ) -> str: 1051 if not self.pretty or not sql: 1052 return sql 1053 1054 pad = self.pad if pad is None else pad 1055 lines = sql.split("\n") 1056 1057 return "\n".join( 1058 ( 1059 line 1060 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1061 else f"{' ' * (level * self._indent + pad)}{line}" 1062 ) 1063 for i, line in enumerate(lines) 1064 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1066 def sql( 1067 self, 1068 expression: str | exp.Expr | None, 1069 key: str | None = None, 1070 comment: bool = True, 1071 ) -> str: 1072 if not expression: 1073 return "" 1074 1075 if isinstance(expression, str): 1076 return expression 1077 1078 if key: 1079 value = expression.args.get(key) 1080 if value: 1081 return self.sql(value) 1082 return "" 1083 1084 handler = self._dispatch.get(expression.__class__) 1085 1086 if handler: 1087 sql = handler(self, expression) 1088 elif isinstance(expression, exp.Func): 1089 sql = self.function_fallback_sql(expression) 1090 elif isinstance(expression, exp.Property): 1091 sql = self.property_sql(expression) 1092 else: 1093 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1094 1095 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1102 def cache_sql(self, expression: exp.Cache) -> str: 1103 lazy = " LAZY" if expression.args.get("lazy") else "" 1104 table = self.sql(expression, "this") 1105 options = expression.args.get("options") 1106 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1107 sql = self.sql(expression, "expression") 1108 sql = f" AS{self.sep()}{sql}" if sql else "" 1109 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1110 return self.prepend_ctes(expression, sql)
1128 def column_sql(self, expression: exp.Column) -> str: 1129 join_mark = " (+)" if expression.args.get("join_mark") else "" 1130 1131 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1132 join_mark = "" 1133 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1134 1135 return f"{self.column_parts(expression)}{join_mark}"
1146 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1147 column = self.sql(expression, "this") 1148 kind = self.sql(expression, "kind") 1149 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1150 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1151 kind = f"{sep}{kind}" if kind else "" 1152 constraints = f" {constraints}" if constraints else "" 1153 position = self.sql(expression, "position") 1154 position = f" {position}" if position else "" 1155 1156 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1157 kind = "" 1158 1159 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:
1166 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1167 this = self.sql(expression, "this") 1168 if expression.args.get("not_null"): 1169 persisted = " PERSISTED NOT NULL" 1170 elif expression.args.get("persisted"): 1171 persisted = " PERSISTED" 1172 else: 1173 persisted = "" 1174 1175 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:
1188 def generatedasidentitycolumnconstraint_sql( 1189 self, expression: exp.GeneratedAsIdentityColumnConstraint 1190 ) -> str: 1191 this = "" 1192 if expression.this is not None: 1193 on_null = " ON NULL" if expression.args.get("on_null") else "" 1194 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1195 1196 start = expression.args.get("start") 1197 start = f"START WITH {start}" if start else "" 1198 increment = expression.args.get("increment") 1199 increment = f" INCREMENT BY {increment}" if increment else "" 1200 minvalue = expression.args.get("minvalue") 1201 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1202 maxvalue = expression.args.get("maxvalue") 1203 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1204 cycle = expression.args.get("cycle") 1205 cycle_sql = "" 1206 1207 if cycle is not None: 1208 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1209 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1210 1211 sequence_opts = "" 1212 if start or increment or cycle_sql: 1213 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1214 sequence_opts = f" ({sequence_opts.strip()})" 1215 1216 expr = self.sql(expression, "expression") 1217 expr = f"({expr})" if expr else "IDENTITY" 1218 1219 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1221 def generatedasrowcolumnconstraint_sql( 1222 self, expression: exp.GeneratedAsRowColumnConstraint 1223 ) -> str: 1224 start = "START" if expression.args.get("start") else "END" 1225 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1226 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:
1236 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1237 desc = expression.args.get("desc") 1238 if desc is not None: 1239 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1240 options = self.expressions(expression, key="options", flat=True, sep=" ") 1241 options = f" {options}" if options else "" 1242 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1244 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1245 this = self.sql(expression, "this") 1246 this = f" {this}" if this else "" 1247 index_type = expression.args.get("index_type") 1248 index_type = f" USING {index_type}" if index_type else "" 1249 on_conflict = self.sql(expression, "on_conflict") 1250 on_conflict = f" {on_conflict}" if on_conflict else "" 1251 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1252 options = self.expressions(expression, key="options", flat=True, sep=" ") 1253 options = f" {options}" if options else "" 1254 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1256 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1257 input_ = expression.args.get("input_") 1258 output = expression.args.get("output") 1259 variadic = expression.args.get("variadic") 1260 1261 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1262 if variadic: 1263 return "VARIADIC" 1264 1265 if input_ and output: 1266 return f"IN{self.INOUT_SEPARATOR}OUT" 1267 if input_: 1268 return "IN" 1269 if output: 1270 return "OUT" 1271 1272 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1277 def create_sql(self, expression: exp.Create) -> str: 1278 kind = self.sql(expression, "kind") 1279 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1280 1281 properties = expression.args.get("properties") 1282 1283 if ( 1284 kind == "TRIGGER" 1285 and properties 1286 and properties.expressions 1287 and isinstance(properties.expressions[0], exp.TriggerProperties) 1288 and properties.expressions[0].args.get("constraint") 1289 ): 1290 kind = f"CONSTRAINT {kind}" 1291 1292 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1293 1294 this = self.createable_sql(expression, properties_locs) 1295 1296 properties_sql = "" 1297 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1298 exp.Properties.Location.POST_WITH 1299 ): 1300 props_ast = exp.Properties( 1301 expressions=[ 1302 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1303 *properties_locs[exp.Properties.Location.POST_WITH], 1304 ] 1305 ) 1306 props_ast.parent = expression 1307 properties_sql = self.sql(props_ast) 1308 1309 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1310 properties_sql = self.sep() + properties_sql 1311 elif not self.pretty: 1312 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1313 properties_sql = f" {properties_sql}" 1314 1315 begin = " BEGIN" if expression.args.get("begin") else "" 1316 1317 expression_sql = self.sql(expression, "expression") 1318 if expression_sql: 1319 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1320 1321 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1322 postalias_props_sql = "" 1323 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1324 postalias_props_sql = self.properties( 1325 exp.Properties( 1326 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1327 ), 1328 wrapped=False, 1329 ) 1330 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1331 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1332 1333 postindex_props_sql = "" 1334 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1335 postindex_props_sql = self.properties( 1336 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1337 wrapped=False, 1338 prefix=" ", 1339 ) 1340 1341 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1342 indexes = f" {indexes}" if indexes else "" 1343 index_sql = indexes + postindex_props_sql 1344 1345 replace = " OR REPLACE" if expression.args.get("replace") else "" 1346 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1347 unique = " UNIQUE" if expression.args.get("unique") else "" 1348 1349 clustered = expression.args.get("clustered") 1350 if clustered is None: 1351 clustered_sql = "" 1352 elif clustered: 1353 clustered_sql = " CLUSTERED COLUMNSTORE" 1354 else: 1355 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1356 1357 postcreate_props_sql = "" 1358 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1359 postcreate_props_sql = self.properties( 1360 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1361 sep=" ", 1362 prefix=" ", 1363 wrapped=False, 1364 ) 1365 1366 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1367 1368 postexpression_props_sql = "" 1369 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1370 postexpression_props_sql = self.properties( 1371 exp.Properties( 1372 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1373 ), 1374 sep=" ", 1375 prefix=" ", 1376 wrapped=False, 1377 ) 1378 1379 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1380 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1381 no_schema_binding = ( 1382 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1383 ) 1384 1385 clone = self.sql(expression, "clone") 1386 clone = f" {clone}" if clone else "" 1387 1388 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1389 properties_expression = f"{expression_sql}{properties_sql}" 1390 else: 1391 properties_expression = f"{properties_sql}{expression_sql}" 1392 1393 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1394 return self.prepend_ctes(expression, expression_sql)
1396 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1397 start = self.sql(expression, "start") 1398 start = f"START WITH {start}" if start else "" 1399 increment = self.sql(expression, "increment") 1400 increment = f" INCREMENT BY {increment}" if increment else "" 1401 minvalue = self.sql(expression, "minvalue") 1402 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1403 maxvalue = self.sql(expression, "maxvalue") 1404 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1405 owned = self.sql(expression, "owned") 1406 owned = f" OWNED BY {owned}" if owned else "" 1407 1408 cache = expression.args.get("cache") 1409 if cache is None: 1410 cache_str = "" 1411 elif cache is True: 1412 cache_str = " CACHE" 1413 else: 1414 cache_str = f" CACHE {cache}" 1415 1416 options = self.expressions(expression, key="options", flat=True, sep=" ") 1417 options = f" {options}" if options else "" 1418 1419 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1421 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1422 timing = expression.args.get("timing", "") 1423 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1424 timing_events = f"{timing} {events}".strip() if timing or events else "" 1425 1426 parts = [timing_events, "ON", self.sql(expression, "table")] 1427 1428 if referenced_table := expression.args.get("referenced_table"): 1429 parts.extend(["FROM", self.sql(referenced_table)]) 1430 1431 if deferrable := expression.args.get("deferrable"): 1432 parts.append(deferrable) 1433 1434 if initially := expression.args.get("initially"): 1435 parts.append(f"INITIALLY {initially}") 1436 1437 if referencing := expression.args.get("referencing"): 1438 parts.append(self.sql(referencing)) 1439 1440 if for_each := expression.args.get("for_each"): 1441 parts.append(f"FOR EACH {for_each}") 1442 1443 if when := expression.args.get("when"): 1444 parts.append(f"WHEN ({self.sql(when)})") 1445 1446 parts.append(self.sql(expression, "execute")) 1447 1448 return self.sep().join(parts)
1450 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1451 parts = [] 1452 1453 if old_alias := expression.args.get("old"): 1454 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1455 1456 if new_alias := expression.args.get("new"): 1457 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1458 1459 return f"REFERENCING {' '.join(parts)}"
1468 def clone_sql(self, expression: exp.Clone) -> str: 1469 this = self.sql(expression, "this") 1470 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1471 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1472 return f"{shallow}{keyword} {this}"
1474 def describe_sql(self, expression: exp.Describe) -> str: 1475 style = expression.args.get("style") 1476 style = f" {style}" if style else "" 1477 partition = self.sql(expression, "partition") 1478 partition = f" {partition}" if partition else "" 1479 format = self.sql(expression, "format") 1480 format = f" {format}" if format else "" 1481 as_json = " AS JSON" if expression.args.get("as_json") else "" 1482 1483 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1495 def with_sql(self, expression: exp.With) -> str: 1496 sql = self.expressions(expression, flat=True) 1497 recursive = ( 1498 "RECURSIVE " 1499 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1500 else "" 1501 ) 1502 search = self.sql(expression, "search") 1503 search = f" {search}" if search else "" 1504 1505 return f"WITH {recursive}{sql}{search}"
1507 def cte_sql(self, expression: exp.CTE) -> str: 1508 alias = expression.args.get("alias") 1509 if alias: 1510 alias.add_comments(expression.pop_comments()) 1511 1512 alias_sql = self.sql(expression, "alias") 1513 1514 materialized = expression.args.get("materialized") 1515 if materialized is False: 1516 materialized = "NOT MATERIALIZED " 1517 elif materialized: 1518 materialized = "MATERIALIZED " 1519 1520 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1521 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1522 1523 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1525 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1526 alias = self.sql(expression, "this") 1527 columns = self.expressions(expression, key="columns", flat=True) 1528 columns = f"({columns})" if columns else "" 1529 1530 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1531 columns = "" 1532 self.unsupported("Named columns are not supported in table alias.") 1533 1534 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1535 alias = self._next_name() 1536 1537 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1545 def hexstring_sql( 1546 self, expression: exp.HexString, binary_function_repr: str | None = None 1547 ) -> str: 1548 this = self.sql(expression, "this") 1549 is_integer_type = expression.args.get("is_integer") 1550 1551 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1552 not self.dialect.HEX_START and not binary_function_repr 1553 ): 1554 # Integer representation will be returned if: 1555 # - The read dialect treats the hex value as integer literal but not the write 1556 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1557 return f"{int(this, 16)}" 1558 1559 if not is_integer_type: 1560 # Read dialect treats the hex value as BINARY/BLOB 1561 if binary_function_repr: 1562 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1563 return self.func(binary_function_repr, exp.Literal.string(this)) 1564 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1565 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1566 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1567 1568 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1570 def bytestring_sql(self, expression: exp.ByteString) -> str: 1571 this = self.sql(expression, "this") 1572 if self.dialect.BYTE_START: 1573 escaped_byte_string = self.escape_str( 1574 this, 1575 escape_backslash=False, 1576 delimiter=self.dialect.BYTE_END, 1577 escaped_delimiter=self._escaped_byte_quote_end, 1578 is_byte_string=True, 1579 ) 1580 is_bytes = expression.args.get("is_bytes", False) 1581 delimited_byte_string = ( 1582 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1583 ) 1584 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1585 return self.sql( 1586 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1587 ) 1588 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1589 return self.sql( 1590 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1591 ) 1592 1593 return delimited_byte_string 1594 return this
1596 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1597 this = self.sql(expression, "this") 1598 escape = expression.args.get("escape") 1599 1600 if self.dialect.UNICODE_START: 1601 escape_substitute = r"\\\1" 1602 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1603 else: 1604 escape_substitute = r"\\u\1" 1605 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1606 1607 if escape: 1608 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1609 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1610 else: 1611 escape_pattern = ESCAPED_UNICODE_RE 1612 escape_sql = "" 1613 1614 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1615 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1616 1617 return f"{left_quote}{this}{right_quote}{escape_sql}"
1619 def rawstring_sql(self, expression: exp.RawString) -> str: 1620 string = expression.this 1621 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1622 string = string.replace("\\", "\\\\") 1623 1624 string = self.escape_str(string, escape_backslash=False) 1625 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1633 def datatype_sql(self, expression: exp.DataType) -> str: 1634 nested = "" 1635 values = "" 1636 1637 expr_nested = expression.args.get("nested") 1638 interior = ( 1639 self.expressions( 1640 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1641 ) 1642 if expr_nested and self.pretty 1643 else self.expressions(expression, flat=True) 1644 ) 1645 1646 type_value = expression.this 1647 if type_value in self.UNSUPPORTED_TYPES: 1648 self.unsupported( 1649 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1650 ) 1651 1652 type_sql: t.Any = "" 1653 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1654 type_sql = self.sql(expression, "kind") 1655 elif type_value == exp.DType.CHARACTER_SET: 1656 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1657 else: 1658 type_sql = ( 1659 self.TYPE_MAPPING.get(type_value, type_value.value) 1660 if isinstance(type_value, exp.DType) 1661 else type_value 1662 ) 1663 1664 if interior: 1665 if expr_nested: 1666 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1667 if expression.args.get("values") is not None: 1668 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1669 values = self.expressions(expression, key="values", flat=True) 1670 values = f"{delimiters[0]}{values}{delimiters[1]}" 1671 elif type_value == exp.DType.INTERVAL: 1672 nested = f" {interior}" 1673 else: 1674 nested = f"({interior})" 1675 1676 type_sql = f"{type_sql}{nested}{values}" 1677 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1678 exp.DType.TIMETZ, 1679 exp.DType.TIMESTAMPTZ, 1680 ): 1681 type_sql = f"{type_sql} WITH TIME ZONE" 1682 1683 return type_sql
1685 def directory_sql(self, expression: exp.Directory) -> str: 1686 local = "LOCAL " if expression.args.get("local") else "" 1687 row_format = self.sql(expression, "row_format") 1688 row_format = f" {row_format}" if row_format else "" 1689 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1691 def delete_sql(self, expression: exp.Delete) -> str: 1692 hint = self.sql(expression, "hint") 1693 this = self.sql(expression, "this") 1694 this = f" FROM {this}" if this else "" 1695 using = self.expressions(expression, key="using") 1696 using = f" USING {using}" if using else "" 1697 cluster = self.sql(expression, "cluster") 1698 cluster = f" {cluster}" if cluster else "" 1699 where = self.sql(expression, "where") 1700 returning = self.sql(expression, "returning") 1701 order = self.sql(expression, "order") 1702 limit = self.sql(expression, "limit") 1703 tables = self.expressions(expression, key="tables") 1704 tables = f" {tables}" if tables else "" 1705 if self.RETURNING_END: 1706 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1707 else: 1708 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1709 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1711 def drop_sql(self, expression: exp.Drop) -> str: 1712 this = self.sql(expression, "this") 1713 expressions = self.expressions(expression, flat=True) 1714 expressions = f" ({expressions})" if expressions else "" 1715 kind = expression.args["kind"] 1716 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1717 iceberg = ( 1718 " ICEBERG" 1719 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1720 else "" 1721 ) 1722 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1723 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1724 on_cluster = self.sql(expression, "cluster") 1725 on_cluster = f" {on_cluster}" if on_cluster else "" 1726 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1727 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1728 cascade = " CASCADE" if expression.args.get("cascade") else "" 1729 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1730 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1731 purge = " PURGE" if expression.args.get("purge") else "" 1732 sync = " SYNC" if expression.args.get("sync") else "" 1733 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1735 def set_operation(self, expression: exp.SetOperation) -> str: 1736 op_type = type(expression) 1737 op_name = op_type.key.upper() 1738 1739 distinct = expression.args.get("distinct") 1740 if ( 1741 distinct is False 1742 and op_type in (exp.Except, exp.Intersect) 1743 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1744 ): 1745 self.unsupported(f"{op_name} ALL is not supported") 1746 1747 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1748 1749 if distinct is None: 1750 distinct = default_distinct 1751 if distinct is None: 1752 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1753 1754 if distinct is default_distinct: 1755 distinct_or_all = "" 1756 else: 1757 distinct_or_all = " DISTINCT" if distinct else " ALL" 1758 1759 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1760 side_kind = f"{side_kind} " if side_kind else "" 1761 1762 by_name = " BY NAME" if expression.args.get("by_name") else "" 1763 on = self.expressions(expression, key="on", flat=True) 1764 on = f" ON ({on})" if on else "" 1765 1766 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1768 def set_operations(self, expression: exp.SetOperation) -> str: 1769 if not self.SET_OP_MODIFIERS: 1770 limit = expression.args.get("limit") 1771 order = expression.args.get("order") 1772 1773 if limit or order: 1774 select = self._move_ctes_to_top_level( 1775 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1776 ) 1777 1778 if limit: 1779 select = select.limit(limit.pop(), copy=False) 1780 if order: 1781 select = select.order_by(order.pop(), copy=False) 1782 return self.sql(select) 1783 1784 sqls: list[str] = [] 1785 stack: list[str | exp.Expr] = [expression] 1786 1787 while stack: 1788 node = stack.pop() 1789 1790 if isinstance(node, exp.SetOperation): 1791 stack.append(node.expression) 1792 stack.append( 1793 self.maybe_comment( 1794 self.set_operation(node), comments=node.comments, separated=True 1795 ) 1796 ) 1797 stack.append(node.this) 1798 else: 1799 sqls.append(self.sql(node)) 1800 1801 this = self.sep().join(sqls) 1802 this = self.query_modifiers(expression, this) 1803 return self.prepend_ctes(expression, this)
1805 def fetch_sql(self, expression: exp.Fetch) -> str: 1806 direction = expression.args.get("direction") 1807 direction = f" {direction}" if direction else "" 1808 count = self.sql(expression, "count") 1809 count = f" {count}" if count else "" 1810 limit_options = self.sql(expression, "limit_options") 1811 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1812 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1814 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1815 percent = " PERCENT" if expression.args.get("percent") else "" 1816 rows = " ROWS" if expression.args.get("rows") else "" 1817 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1818 if not with_ties and rows: 1819 with_ties = " ONLY" 1820 return f"{percent}{rows}{with_ties}"
1822 def filter_sql(self, expression: exp.Filter) -> str: 1823 if self.AGGREGATE_FILTER_SUPPORTED: 1824 this = self.sql(expression, "this") 1825 where = self.sql(expression, "expression").strip() 1826 return f"{this} FILTER({where})" 1827 1828 agg = expression.this 1829 agg_arg = agg.this 1830 cond = expression.expression.this 1831 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1832 return self.sql(agg)
1841 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1842 using = self.sql(expression, "using") 1843 using = f" USING {using}" if using else "" 1844 columns = self.expressions(expression, key="columns", flat=True) 1845 columns = f"({columns})" if columns else "" 1846 partition_by = self.expressions(expression, key="partition_by", flat=True) 1847 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1848 where = self.sql(expression, "where") 1849 include = self.expressions(expression, key="include", flat=True) 1850 if include: 1851 include = f" INCLUDE ({include})" 1852 with_storage = self.expressions(expression, key="with_storage", flat=True) 1853 with_storage = f" WITH ({with_storage})" if with_storage else "" 1854 tablespace = self.sql(expression, "tablespace") 1855 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1856 on = self.sql(expression, "on") 1857 on = f" ON {on}" if on else "" 1858 1859 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1861 def index_sql(self, expression: exp.Index) -> str: 1862 unique = "UNIQUE " if expression.args.get("unique") else "" 1863 primary = "PRIMARY " if expression.args.get("primary") else "" 1864 amp = "AMP " if expression.args.get("amp") else "" 1865 name = self.sql(expression, "this") 1866 name = f"{name} " if name else "" 1867 table = self.sql(expression, "table") 1868 table = f"{self.INDEX_ON} {table}" if table else "" 1869 1870 index = "INDEX " if not table else "" 1871 1872 params = self.sql(expression, "params") 1873 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1875 def identifier_sql(self, expression: exp.Identifier) -> str: 1876 text = expression.name 1877 lower = text.lower() 1878 quoted = expression.quoted 1879 text = lower if self.normalize and not quoted else text 1880 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1881 if ( 1882 quoted 1883 or self.dialect.can_quote(expression, self.identify) 1884 or lower in self.RESERVED_KEYWORDS 1885 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1886 ): 1887 text = ( 1888 f"{self._identifier_start}{self._replace_line_breaks(text)}{self._identifier_end}" 1889 ) 1890 return text
1905 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1906 input_format = self.sql(expression, "input_format") 1907 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1908 output_format = self.sql(expression, "output_format") 1909 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1910 return self.sep().join((input_format, output_format))
1920 def properties_sql(self, expression: exp.Properties) -> str: 1921 root_properties = [] 1922 with_properties = [] 1923 1924 for p in expression.expressions: 1925 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1926 if p_loc == exp.Properties.Location.POST_WITH: 1927 with_properties.append(p) 1928 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1929 root_properties.append(p) 1930 1931 root_props_ast = exp.Properties(expressions=root_properties) 1932 root_props_ast.parent = expression.parent 1933 1934 with_props_ast = exp.Properties(expressions=with_properties) 1935 with_props_ast.parent = expression.parent 1936 1937 root_props = self.root_properties(root_props_ast) 1938 with_props = self.with_properties(with_props_ast) 1939 1940 if root_props and with_props and not self.pretty: 1941 with_props = " " + with_props 1942 1943 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1950 def properties( 1951 self, 1952 properties: exp.Properties, 1953 prefix: str = "", 1954 sep: str = ", ", 1955 suffix: str = "", 1956 wrapped: bool = True, 1957 ) -> str: 1958 if properties.expressions: 1959 expressions = self.expressions(properties, sep=sep, indent=False) 1960 if expressions: 1961 expressions = self.wrap(expressions) if wrapped else expressions 1962 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1963 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
1968 def locate_properties(self, properties: exp.Properties) -> defaultdict: 1969 properties_locs = defaultdict(list) 1970 for p in properties.expressions: 1971 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1972 if p_loc != exp.Properties.Location.UNSUPPORTED: 1973 properties_locs[p_loc].append(p) 1974 else: 1975 self.unsupported(f"Unsupported property {p.key}") 1976 1977 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
1984 def property_sql(self, expression: exp.Property) -> str: 1985 property_cls = expression.__class__ 1986 if property_cls == exp.Property: 1987 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1988 1989 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1990 if not property_name: 1991 self.unsupported(f"Unsupported property {expression.key}") 1992 1993 return f"{property_name}={self.sql(expression, 'this')}"
1998 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1999 if self.SUPPORTS_CREATE_TABLE_LIKE: 2000 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 2001 options = f" {options}" if options else "" 2002 2003 like = f"LIKE {self.sql(expression, 'this')}{options}" 2004 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2005 like = f"({like})" 2006 2007 return like 2008 2009 if expression.expressions: 2010 self.unsupported("Transpilation of LIKE property options is unsupported") 2011 2012 select = exp.select("*").from_(expression.this).limit(0) 2013 return f"AS {self.sql(select)}"
2020 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2021 no = "NO " if expression.args.get("no") else "" 2022 local = expression.args.get("local") 2023 local = f"{local} " if local else "" 2024 dual = "DUAL " if expression.args.get("dual") else "" 2025 before = "BEFORE " if expression.args.get("before") else "" 2026 after = "AFTER " if expression.args.get("after") else "" 2027 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:
2043 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2044 if expression.args.get("no"): 2045 return "NO MERGEBLOCKRATIO" 2046 if expression.args.get("default"): 2047 return "DEFAULT MERGEBLOCKRATIO" 2048 2049 percent = " PERCENT" if expression.args.get("percent") else "" 2050 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2057 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2058 default = expression.args.get("default") 2059 minimum = expression.args.get("minimum") 2060 maximum = expression.args.get("maximum") 2061 if default or minimum or maximum: 2062 if default: 2063 prop = "DEFAULT" 2064 elif minimum: 2065 prop = "MINIMUM" 2066 else: 2067 prop = "MAXIMUM" 2068 return f"{prop} DATABLOCKSIZE" 2069 units = expression.args.get("units") 2070 units = f" {units}" if units else "" 2071 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2073 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2074 autotemp = expression.args.get("autotemp") 2075 always = expression.args.get("always") 2076 default = expression.args.get("default") 2077 manual = expression.args.get("manual") 2078 never = expression.args.get("never") 2079 2080 if autotemp is not None: 2081 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2082 elif always: 2083 prop = "ALWAYS" 2084 elif default: 2085 prop = "DEFAULT" 2086 elif manual: 2087 prop = "MANUAL" 2088 elif never: 2089 prop = "NEVER" 2090 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2092 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2093 no = expression.args.get("no") 2094 no = " NO" if no else "" 2095 concurrent = expression.args.get("concurrent") 2096 concurrent = " CONCURRENT" if concurrent else "" 2097 target = self.sql(expression, "target") 2098 target = f" {target}" if target else "" 2099 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2101 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2102 if isinstance(expression.this, list): 2103 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2104 if expression.this: 2105 modulus = self.sql(expression, "this") 2106 remainder = self.sql(expression, "expression") 2107 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2108 2109 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2110 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2111 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2113 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2114 this = self.sql(expression, "this") 2115 2116 for_values_or_default = expression.expression 2117 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2118 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2119 else: 2120 for_values_or_default = " DEFAULT" 2121 2122 return f"PARTITION OF {this}{for_values_or_default}"
2124 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2125 kind = expression.args.get("kind") 2126 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2127 for_or_in = expression.args.get("for_or_in") 2128 for_or_in = f" {for_or_in}" if for_or_in else "" 2129 lock_type = expression.args.get("lock_type") 2130 override = " OVERRIDE" if expression.args.get("override") else "" 2131 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2133 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2134 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2135 statistics = expression.args.get("statistics") 2136 statistics_sql = "" 2137 if statistics is not None: 2138 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2139 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2141 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2142 this = self.sql(expression, "this") 2143 this = f"HISTORY_TABLE={this}" if this else "" 2144 data_consistency: str | None = self.sql(expression, "data_consistency") 2145 data_consistency = ( 2146 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2147 ) 2148 retention_period: str | None = self.sql(expression, "retention_period") 2149 retention_period = ( 2150 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2151 ) 2152 2153 if this: 2154 on_sql = self.func("ON", this, data_consistency, retention_period) 2155 else: 2156 on_sql = "ON" if expression.args.get("on") else "OFF" 2157 2158 sql = f"SYSTEM_VERSIONING={on_sql}" 2159 2160 return f"WITH({sql})" if expression.args.get("with_") else sql
2162 def insert_sql(self, expression: exp.Insert) -> str: 2163 hint = self.sql(expression, "hint") 2164 overwrite = expression.args.get("overwrite") 2165 2166 if isinstance(expression.this, exp.Directory): 2167 this = " OVERWRITE" if overwrite else " INTO" 2168 else: 2169 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2170 2171 stored = self.sql(expression, "stored") 2172 stored = f" {stored}" if stored else "" 2173 alternative = expression.args.get("alternative") 2174 alternative = f" OR {alternative}" if alternative else "" 2175 ignore = " IGNORE" if expression.args.get("ignore") else "" 2176 is_function = expression.args.get("is_function") 2177 if is_function: 2178 this = f"{this} FUNCTION" 2179 this = f"{this} {self.sql(expression, 'this')}" 2180 2181 exists = " IF EXISTS" if expression.args.get("exists") else "" 2182 where = self.sql(expression, "where") 2183 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2184 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2185 on_conflict = self.sql(expression, "conflict") 2186 on_conflict = f" {on_conflict}" if on_conflict else "" 2187 by_name = " BY NAME" if expression.args.get("by_name") else "" 2188 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2189 returning = self.sql(expression, "returning") 2190 2191 if self.RETURNING_END: 2192 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2193 else: 2194 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2195 2196 partition_by = self.sql(expression, "partition") 2197 partition_by = f" {partition_by}" if partition_by else "" 2198 settings = self.sql(expression, "settings") 2199 settings = f" {settings}" if settings else "" 2200 2201 source = self.sql(expression, "source") 2202 source = f"TABLE {source}" if source else "" 2203 2204 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2205 return self.prepend_ctes(expression, sql)
2223 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2224 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2225 2226 constraint = self.sql(expression, "constraint") 2227 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2228 2229 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2230 if conflict_keys: 2231 conflict_keys = f"({conflict_keys})" 2232 2233 index_predicate = self.sql(expression, "index_predicate") 2234 conflict_keys = f"{conflict_keys}{index_predicate} " 2235 2236 action = self.sql(expression, "action") 2237 2238 expressions = self.expressions(expression, flat=True) 2239 if expressions: 2240 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2241 expressions = f" {set_keyword}{expressions}" 2242 2243 where = self.sql(expression, "where") 2244 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2249 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2250 fields = self.sql(expression, "fields") 2251 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2252 escaped = self.sql(expression, "escaped") 2253 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2254 items = self.sql(expression, "collection_items") 2255 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2256 keys = self.sql(expression, "map_keys") 2257 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2258 lines = self.sql(expression, "lines") 2259 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2260 null = self.sql(expression, "null") 2261 null = f" NULL DEFINED AS {null}" if null else "" 2262 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2290 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2291 table = self.table_parts(expression) 2292 only = "ONLY " if expression.args.get("only") else "" 2293 partition = self.sql(expression, "partition") 2294 partition = f" {partition}" if partition else "" 2295 version = self.sql(expression, "version") 2296 version = f" {version}" if version else "" 2297 alias = self.sql(expression, "alias") 2298 alias = f"{sep}{alias}" if alias else "" 2299 2300 sample = self.sql(expression, "sample") 2301 post_alias = "" 2302 pre_alias = "" 2303 2304 if self.dialect.ALIAS_POST_TABLESAMPLE: 2305 pre_alias = sample 2306 else: 2307 post_alias = sample 2308 2309 if self.dialect.ALIAS_POST_VERSION: 2310 pre_alias = f"{pre_alias}{version}" 2311 else: 2312 post_alias = f"{post_alias}{version}" 2313 2314 hints = self.expressions(expression, key="hints", sep=" ") 2315 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2316 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2317 joins = self.indent( 2318 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2319 ) 2320 laterals = self.expressions(expression, key="laterals", sep="") 2321 2322 file_format = self.sql(expression, "format") 2323 if file_format: 2324 pattern = self.sql(expression, "pattern") 2325 pattern = f", PATTERN => {pattern}" if pattern else "" 2326 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2327 2328 ordinality = expression.args.get("ordinality") or "" 2329 if ordinality: 2330 ordinality = f" WITH ORDINALITY{alias}" 2331 alias = "" 2332 2333 when = self.sql(expression, "when") 2334 if when: 2335 table = f"{table} {when}" 2336 2337 changes = self.sql(expression, "changes") 2338 changes = f" {changes}" if changes else "" 2339 2340 rows_from = self.expressions(expression, key="rows_from") 2341 if rows_from: 2342 table = f"ROWS FROM {self.wrap(rows_from)}" 2343 2344 indexed = expression.args.get("indexed") 2345 if indexed is not None: 2346 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2347 else: 2348 indexed = "" 2349 2350 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2352 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2353 table = self.func("TABLE", expression.this) 2354 alias = self.sql(expression, "alias") 2355 alias = f" AS {alias}" if alias else "" 2356 sample = self.sql(expression, "sample") 2357 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2358 joins = self.indent( 2359 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2360 ) 2361 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2363 def tablesample_sql( 2364 self, 2365 expression: exp.TableSample, 2366 tablesample_keyword: str | None = None, 2367 ) -> str: 2368 method = self.sql(expression, "method") 2369 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2370 numerator = self.sql(expression, "bucket_numerator") 2371 denominator = self.sql(expression, "bucket_denominator") 2372 field = self.sql(expression, "bucket_field") 2373 field = f" ON {field}" if field else "" 2374 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2375 seed = self.sql(expression, "seed") 2376 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2377 2378 size = self.sql(expression, "size") 2379 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2380 size = f"{size} ROWS" 2381 2382 percent = self.sql(expression, "percent") 2383 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2384 percent = f"{percent} PERCENT" 2385 2386 expr = f"{bucket}{percent}{size}" 2387 if self.TABLESAMPLE_REQUIRES_PARENS: 2388 expr = f"({expr})" 2389 2390 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2392 def pivot_sql(self, expression: exp.Pivot) -> str: 2393 expressions = self.expressions(expression, flat=True) 2394 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2395 2396 group = self.sql(expression, "group") 2397 2398 if expression.this: 2399 this = self.sql(expression, "this") 2400 if not expressions: 2401 sql = f"UNPIVOT {this}" 2402 else: 2403 on = f"{self.seg('ON')} {expressions}" 2404 into = self.sql(expression, "into") 2405 into = f"{self.seg('INTO')} {into}" if into else "" 2406 using = self.expressions(expression, key="using", flat=True) 2407 using = f"{self.seg('USING')} {using}" if using else "" 2408 sql = f"{direction} {this}{on}{into}{using}{group}" 2409 return self.prepend_ctes(expression, sql) 2410 2411 alias = self.sql(expression, "alias") 2412 alias = f" AS {alias}" if alias else "" 2413 2414 fields = self.expressions( 2415 expression, 2416 "fields", 2417 sep=" ", 2418 dynamic=True, 2419 new_line=True, 2420 skip_first=True, 2421 skip_last=True, 2422 ) 2423 2424 include_nulls = expression.args.get("include_nulls") 2425 if include_nulls is not None: 2426 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2427 else: 2428 nulls = "" 2429 2430 default_on_null = self.sql(expression, "default_on_null") 2431 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2432 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2433 return self.prepend_ctes(expression, sql)
2476 def update_sql(self, expression: exp.Update) -> str: 2477 hint = self.sql(expression, "hint") 2478 this = self.sql(expression, "this") 2479 join_sql, from_sql = self._update_from_joins_sql(expression) 2480 set_sql = self.expressions(expression, flat=True) 2481 where_sql = self.sql(expression, "where") 2482 returning = self.sql(expression, "returning") 2483 order = self.sql(expression, "order") 2484 limit = self.sql(expression, "limit") 2485 if self.RETURNING_END: 2486 expression_sql = f"{from_sql}{where_sql}{returning}" 2487 else: 2488 expression_sql = f"{returning}{from_sql}{where_sql}" 2489 options = self.expressions(expression, key="options") 2490 options = f" OPTION({options})" if options else "" 2491 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2492 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2494 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2495 values_as_table = values_as_table and self.VALUES_AS_TABLE 2496 2497 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2498 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2499 args = self.expressions(expression) 2500 alias = self.sql(expression, "alias") 2501 values = f"VALUES{self.seg('')}{args}" 2502 values = ( 2503 f"({values})" 2504 if self.WRAP_DERIVED_VALUES 2505 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2506 else values 2507 ) 2508 values = self.query_modifiers(expression, values) 2509 return f"{values} AS {alias}" if alias else values 2510 2511 # Converts `VALUES...` expression into a series of select unions. 2512 alias_node = expression.args.get("alias") 2513 column_names = alias_node and alias_node.columns 2514 2515 selects: list[exp.Query] = [] 2516 2517 for i, tup in enumerate(expression.expressions): 2518 row = tup.expressions 2519 2520 if i == 0 and column_names: 2521 row = [ 2522 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2523 ] 2524 2525 selects.append(exp.Select(expressions=row)) 2526 2527 if self.pretty: 2528 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2529 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2530 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2531 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2532 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2533 2534 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2535 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2536 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2541 @unsupported_args("expressions") 2542 def into_sql(self, expression: exp.Into) -> str: 2543 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2544 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2545 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2558 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2559 this = self.sql(expression, "this") 2560 2561 columns = self.expressions(expression, flat=True) 2562 2563 from_sql = self.sql(expression, "from_index") 2564 from_sql = f" FROM {from_sql}" if from_sql else "" 2565 2566 properties = expression.args.get("properties") 2567 properties_sql = ( 2568 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2569 ) 2570 2571 return f"{this}({columns}){from_sql}{properties_sql}"
2580 def group_sql(self, expression: exp.Group) -> str: 2581 group_by_all = expression.args.get("all") 2582 if group_by_all is True: 2583 modifier = " ALL" 2584 elif group_by_all is False: 2585 modifier = " DISTINCT" 2586 else: 2587 modifier = "" 2588 2589 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2590 2591 grouping_sets = self.expressions(expression, key="grouping_sets") 2592 cube = self.expressions(expression, key="cube") 2593 rollup = self.expressions(expression, key="rollup") 2594 2595 groupings = csv( 2596 self.seg(grouping_sets) if grouping_sets else "", 2597 self.seg(cube) if cube else "", 2598 self.seg(rollup) if rollup else "", 2599 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2600 sep=self.GROUPINGS_SEP, 2601 ) 2602 2603 if ( 2604 expression.expressions 2605 and groupings 2606 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2607 ): 2608 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2609 2610 return f"{group_by}{groupings}"
2616 def connect_sql(self, expression: exp.Connect) -> str: 2617 start = self.sql(expression, "start") 2618 start = self.seg(f"START WITH {start}") if start else "" 2619 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2620 connect = self.sql(expression, "connect") 2621 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2622 return start + connect
2627 def join_sql(self, expression: exp.Join) -> str: 2628 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2629 side = None 2630 else: 2631 side = expression.side 2632 2633 op_sql = " ".join( 2634 op 2635 for op in ( 2636 expression.method, 2637 "GLOBAL" if expression.args.get("global_") else None, 2638 side, 2639 expression.kind, 2640 expression.hint if self.JOIN_HINTS else None, 2641 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2642 ) 2643 if op 2644 ) 2645 match_cond = self.sql(expression, "match_condition") 2646 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2647 on_sql = self.sql(expression, "on") 2648 using = expression.args.get("using") 2649 2650 if not on_sql and using: 2651 on_sql = csv(*(self.sql(column) for column in using)) 2652 2653 this = expression.this 2654 this_sql = self.sql(this) 2655 2656 exprs = self.expressions(expression) 2657 if exprs: 2658 this_sql = f"{this_sql},{self.seg(exprs)}" 2659 2660 if on_sql: 2661 on_sql = self.indent(on_sql, skip_first=True) 2662 space = self.seg(" " * self.pad) if self.pretty else " " 2663 if using: 2664 on_sql = f"{space}USING ({on_sql})" 2665 else: 2666 on_sql = f"{space}ON {on_sql}" 2667 elif not op_sql: 2668 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2669 return f" {this_sql}" 2670 2671 return f", {this_sql}" 2672 2673 if op_sql != "STRAIGHT_JOIN": 2674 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2675 2676 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2677 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:
2684 def lateral_op(self, expression: exp.Lateral) -> str: 2685 cross_apply = expression.args.get("cross_apply") 2686 2687 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2688 if cross_apply is True: 2689 op = "INNER JOIN " 2690 elif cross_apply is False: 2691 op = "LEFT JOIN " 2692 else: 2693 op = "" 2694 2695 return f"{op}LATERAL"
2697 def lateral_sql(self, expression: exp.Lateral) -> str: 2698 this = self.sql(expression, "this") 2699 2700 if expression.args.get("view"): 2701 alias = expression.args["alias"] 2702 columns = self.expressions(alias, key="columns", flat=True) 2703 table = f" {alias.name}" if alias.name else "" 2704 columns = f" AS {columns}" if columns else "" 2705 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2706 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2707 2708 alias = self.sql(expression, "alias") 2709 alias = f" AS {alias}" if alias else "" 2710 2711 ordinality = expression.args.get("ordinality") or "" 2712 if ordinality: 2713 ordinality = f" WITH ORDINALITY{alias}" 2714 alias = "" 2715 2716 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2718 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2719 this = self.sql(expression, "this") 2720 2721 args = [ 2722 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2723 for e in (expression.args.get(k) for k in ("offset", "expression")) 2724 if e 2725 ] 2726 2727 args_sql = ", ".join(self.sql(e) for e in args) 2728 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2729 expressions = self.expressions(expression, flat=True) 2730 limit_options = self.sql(expression, "limit_options") 2731 expressions = f" BY {expressions}" if expressions else "" 2732 2733 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2735 def offset_sql(self, expression: exp.Offset) -> str: 2736 this = self.sql(expression, "this") 2737 value = expression.expression 2738 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2739 expressions = self.expressions(expression, flat=True) 2740 expressions = f" BY {expressions}" if expressions else "" 2741 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2743 def setitem_sql(self, expression: exp.SetItem) -> str: 2744 kind = self.sql(expression, "kind") 2745 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2746 kind = "" 2747 else: 2748 kind = f"{kind} " if kind else "" 2749 this = self.sql(expression, "this") 2750 expressions = self.expressions(expression) 2751 collate = self.sql(expression, "collate") 2752 collate = f" COLLATE {collate}" if collate else "" 2753 global_ = "GLOBAL " if expression.args.get("global_") else "" 2754 return f"{global_}{kind}{this}{expressions}{collate}"
2761 def queryband_sql(self, expression: exp.QueryBand) -> str: 2762 this = self.sql(expression, "this") 2763 update = " UPDATE" if expression.args.get("update") else "" 2764 scope = self.sql(expression, "scope") 2765 scope = f" FOR {scope}" if scope else "" 2766 2767 return f"QUERY_BAND = {this}{update}{scope}"
2772 def lock_sql(self, expression: exp.Lock) -> str: 2773 if not self.LOCKING_READS_SUPPORTED: 2774 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2775 return "" 2776 2777 update = expression.args["update"] 2778 key = expression.args.get("key") 2779 if update: 2780 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2781 else: 2782 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2783 expressions = self.expressions(expression, flat=True) 2784 expressions = f" OF {expressions}" if expressions else "" 2785 wait = expression.args.get("wait") 2786 2787 if wait is not None: 2788 if isinstance(wait, exp.Literal): 2789 wait = f" WAIT {self.sql(wait)}" 2790 else: 2791 wait = " NOWAIT" if wait else " SKIP LOCKED" 2792 2793 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:
2801 def escape_str( 2802 self, 2803 text: str, 2804 escape_backslash: bool = True, 2805 delimiter: str | None = None, 2806 escaped_delimiter: str | None = None, 2807 is_byte_string: bool = False, 2808 ) -> str: 2809 if is_byte_string: 2810 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2811 else: 2812 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2813 2814 if supports_escape_sequences: 2815 text = "".join( 2816 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2817 for ch in text 2818 ) 2819 2820 delimiter = delimiter or self.dialect.QUOTE_END 2821 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2822 2823 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2825 def loaddata_sql(self, expression: exp.LoadData) -> str: 2826 is_overwrite = expression.args.get("overwrite") 2827 overwrite = " OVERWRITE" if is_overwrite else "" 2828 this = self.sql(expression, "this") 2829 2830 files = expression.args.get("files") 2831 if files: 2832 files_sql = self.expressions(files, flat=True) 2833 files_sql = f"FILES{self.wrap(files_sql)}" 2834 this = f" {this}" if is_overwrite else f" INTO TABLE {this}" 2835 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2836 2837 local = " LOCAL" if expression.args.get("local") else "" 2838 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2839 this = f" INTO TABLE {this}" 2840 partition = self.sql(expression, "partition") 2841 partition = f" {partition}" if partition else "" 2842 input_format = self.sql(expression, "input_format") 2843 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2844 serde = self.sql(expression, "serde") 2845 serde = f" SERDE {serde}" if serde else "" 2846 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2860 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2861 this = self.sql(expression, "this") 2862 this = f"{this} " if this else this 2863 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2864 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
2866 def withfill_sql(self, expression: exp.WithFill) -> str: 2867 from_sql = self.sql(expression, "from_") 2868 from_sql = f" FROM {from_sql}" if from_sql else "" 2869 to_sql = self.sql(expression, "to") 2870 to_sql = f" TO {to_sql}" if to_sql else "" 2871 step_sql = self.sql(expression, "step") 2872 step_sql = f" STEP {step_sql}" if step_sql else "" 2873 interpolated_values = [ 2874 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2875 if isinstance(e, exp.Alias) 2876 else self.sql(e, "this") 2877 for e in expression.args.get("interpolate") or [] 2878 ] 2879 interpolate = ( 2880 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2881 ) 2882 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2893 def ordered_sql(self, expression: exp.Ordered) -> str: 2894 desc = expression.args.get("desc") 2895 asc = not desc 2896 2897 nulls_first = expression.args.get("nulls_first") 2898 nulls_last = not nulls_first 2899 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2900 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2901 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2902 2903 this = self.sql(expression, "this") 2904 2905 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2906 nulls_sort_change = "" 2907 if nulls_first and ( 2908 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2909 ): 2910 nulls_sort_change = " NULLS FIRST" 2911 elif ( 2912 nulls_last 2913 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2914 and not nulls_are_last 2915 ): 2916 nulls_sort_change = " NULLS LAST" 2917 2918 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2919 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2920 window = expression.find_ancestor(exp.Window, exp.Select) 2921 2922 if isinstance(window, exp.Window): 2923 window_this = window.this 2924 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 2925 window_this = window_this.this 2926 spec = window.args.get("spec") 2927 else: 2928 window_this = None 2929 spec = None 2930 2931 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 2932 # without a spec or with a ROWS spec, but not with RANGE 2933 if not ( 2934 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 2935 and (not spec or spec.text("kind").upper() == "ROWS") 2936 ): 2937 if window_this and spec: 2938 self.unsupported( 2939 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 2940 ) 2941 nulls_sort_change = "" 2942 elif self.NULL_ORDERING_SUPPORTED is False and ( 2943 (asc and nulls_sort_change == " NULLS LAST") 2944 or (desc and nulls_sort_change == " NULLS FIRST") 2945 ): 2946 # BigQuery does not allow these ordering/nulls combinations when used under 2947 # an aggregation func or under a window containing one 2948 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2949 2950 if isinstance(ancestor, exp.Window): 2951 ancestor = ancestor.this 2952 if isinstance(ancestor, exp.AggFunc): 2953 self.unsupported( 2954 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 2955 ) 2956 nulls_sort_change = "" 2957 elif self.NULL_ORDERING_SUPPORTED is None: 2958 if expression.this.is_int: 2959 self.unsupported( 2960 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2961 ) 2962 elif not isinstance(expression.this, exp.Rand): 2963 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2964 this = ( 2965 f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2966 ) 2967 nulls_sort_change = "" 2968 2969 with_fill = self.sql(expression, "with_fill") 2970 with_fill = f" {with_fill}" if with_fill else "" 2971 2972 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
2982 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2983 partition = self.partition_by_sql(expression) 2984 order = self.sql(expression, "order") 2985 measures = self.expressions(expression, key="measures") 2986 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2987 rows = self.sql(expression, "rows") 2988 rows = self.seg(rows) if rows else "" 2989 after = self.sql(expression, "after") 2990 after = self.seg(after) if after else "" 2991 pattern = self.sql(expression, "pattern") 2992 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2993 definition_sqls = [ 2994 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2995 for definition in expression.args.get("define", []) 2996 ] 2997 definitions = self.expressions(sqls=definition_sqls) 2998 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2999 body = "".join( 3000 ( 3001 partition, 3002 order, 3003 measures, 3004 rows, 3005 after, 3006 pattern, 3007 define, 3008 ) 3009 ) 3010 alias = self.sql(expression, "alias") 3011 alias = f" {alias}" if alias else "" 3012 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3014 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3015 limit = expression.args.get("limit") 3016 3017 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3018 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3019 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3020 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3021 3022 return csv( 3023 *sqls, 3024 *[self.sql(join) for join in expression.args.get("joins") or []], 3025 self.sql(expression, "match"), 3026 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3027 self.sql(expression, "prewhere"), 3028 self.sql(expression, "where"), 3029 self.sql(expression, "connect"), 3030 self.sql(expression, "group"), 3031 self.sql(expression, "having"), 3032 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3033 self.sql(expression, "order"), 3034 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3035 *self.after_limit_modifiers(expression), 3036 self.options_modifier(expression), 3037 self.for_modifiers(expression), 3038 sep="", 3039 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.core.Expr, fetch: bool, limit: sqlglot.expressions.query.Fetch | sqlglot.expressions.query.Limit | None) -> list[str]:
3066 def select_sql(self, expression: exp.Select) -> str: 3067 into = expression.args.get("into") 3068 if not self.SUPPORTS_SELECT_INTO and into: 3069 into.pop() 3070 3071 hint = self.sql(expression, "hint") 3072 distinct = self.sql(expression, "distinct") 3073 distinct = f" {distinct}" if distinct else "" 3074 kind = self.sql(expression, "kind") 3075 3076 limit = expression.args.get("limit") 3077 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3078 top = self.limit_sql(limit, top=True) 3079 limit.pop() 3080 else: 3081 top = "" 3082 3083 expressions = self.expressions(expression) 3084 3085 if kind: 3086 if kind in self.SELECT_KINDS: 3087 kind = f" AS {kind}" 3088 else: 3089 if kind == "STRUCT": 3090 expressions = self.expressions( 3091 sqls=[ 3092 self.sql( 3093 exp.Struct( 3094 expressions=[ 3095 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3096 if isinstance(e, exp.Alias) 3097 else e 3098 for e in expression.expressions 3099 ] 3100 ) 3101 ) 3102 ] 3103 ) 3104 kind = "" 3105 3106 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3107 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3108 3109 exclude = expression.args.get("exclude") 3110 3111 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3112 exclude_sql = self.expressions(sqls=exclude, flat=True) 3113 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3114 3115 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3116 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3117 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3118 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3119 sql = self.query_modifiers( 3120 expression, 3121 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3122 self.sql(expression, "into", comment=False), 3123 self.sql(expression, "from_", comment=False), 3124 ) 3125 3126 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3127 if expression.args.get("with_"): 3128 sql = self.maybe_comment(sql, expression) 3129 expression.pop_comments() 3130 3131 sql = self.prepend_ctes(expression, sql) 3132 3133 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3134 expression.set("exclude", None) 3135 subquery = expression.subquery(copy=False) 3136 star = exp.Star(except_=exclude) 3137 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3138 3139 if not self.SUPPORTS_SELECT_INTO and into: 3140 if into.args.get("temporary"): 3141 table_kind = " TEMPORARY" 3142 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3143 table_kind = " UNLOGGED" 3144 else: 3145 table_kind = "" 3146 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3147 3148 return sql
3160 def star_sql(self, expression: exp.Star) -> str: 3161 except_ = self.expressions(expression, key="except_", flat=True) 3162 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3163 replace = self.expressions(expression, key="replace", flat=True) 3164 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3165 rename = self.expressions(expression, key="rename", flat=True) 3166 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3167 return f"*{except_}{replace}{rename}"
3183 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3184 alias = self.sql(expression, "alias") 3185 alias = f"{sep}{alias}" if alias else "" 3186 sample = self.sql(expression, "sample") 3187 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3188 alias = f"{sample}{alias}" 3189 3190 # Set to None so it's not generated again by self.query_modifiers() 3191 expression.set("sample", None) 3192 3193 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3194 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3195 return self.prepend_ctes(expression, sql)
3201 def unnest_sql(self, expression: exp.Unnest) -> str: 3202 args = self.expressions(expression, flat=True) 3203 3204 alias = expression.args.get("alias") 3205 offset = expression.args.get("offset") 3206 3207 if self.UNNEST_WITH_ORDINALITY: 3208 if alias and isinstance(offset, exp.Expr): 3209 alias.append("columns", offset) 3210 expression.set("offset", None) 3211 3212 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3213 columns = alias.columns 3214 alias = self.sql(columns[0]) if columns else "" 3215 else: 3216 alias = self.sql(alias) 3217 3218 alias = f" AS {alias}" if alias else alias 3219 if self.UNNEST_WITH_ORDINALITY: 3220 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3221 else: 3222 if isinstance(offset, exp.Expr): 3223 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3224 elif offset: 3225 suffix = f"{alias} WITH OFFSET" 3226 else: 3227 suffix = alias 3228 3229 return f"UNNEST({args}){suffix}"
3238 def window_sql(self, expression: exp.Window) -> str: 3239 this = self.sql(expression, "this") 3240 partition = self.partition_by_sql(expression) 3241 order = expression.args.get("order") 3242 order = self.order_sql(order, flat=True) if order else "" 3243 spec = self.sql(expression, "spec") 3244 alias = self.sql(expression, "alias") 3245 over = self.sql(expression, "over") or "OVER" 3246 3247 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3248 3249 first = expression.args.get("first") 3250 if first is None: 3251 first = "" 3252 else: 3253 first = "FIRST" if first else "LAST" 3254 3255 if not partition and not order and not spec and alias: 3256 return f"{this} {alias}" 3257 3258 args = self.format_args( 3259 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3260 ) 3261 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3267 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3268 kind = self.sql(expression, "kind") 3269 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3270 end = ( 3271 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3272 or "CURRENT ROW" 3273 ) 3274 3275 window_spec = f"{kind} BETWEEN {start} AND {end}" 3276 3277 exclude = self.sql(expression, "exclude") 3278 if exclude: 3279 if self.SUPPORTS_WINDOW_EXCLUDE: 3280 window_spec += f" EXCLUDE {exclude}" 3281 else: 3282 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3283 3284 return window_spec
3291 def between_sql(self, expression: exp.Between) -> str: 3292 this = self.sql(expression, "this") 3293 low = self.sql(expression, "low") 3294 high = self.sql(expression, "high") 3295 symmetric = expression.args.get("symmetric") 3296 3297 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3298 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3299 3300 flag = ( 3301 " SYMMETRIC" 3302 if symmetric 3303 else " ASYMMETRIC" 3304 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3305 else "" # silently drop ASYMMETRIC – semantics identical 3306 ) 3307 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]:
3309 def bracket_offset_expressions( 3310 self, expression: exp.Bracket, index_offset: int | None = None 3311 ) -> list[exp.Expr]: 3312 if expression.args.get("json_access"): 3313 return expression.expressions 3314 3315 return apply_index_offset( 3316 expression.this, 3317 expression.expressions, 3318 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3319 dialect=self.dialect, 3320 )
3333 def any_sql(self, expression: exp.Any) -> str: 3334 this = self.sql(expression, "this") 3335 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3336 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3337 this = self.wrap(this) 3338 return f"ANY{this}" 3339 return f"ANY {this}"
3344 def case_sql(self, expression: exp.Case) -> str: 3345 this = self.sql(expression, "this") 3346 statements = [f"CASE {this}" if this else "CASE"] 3347 3348 for e in expression.args["ifs"]: 3349 statements.append(f"WHEN {self.sql(e, 'this')}") 3350 statements.append(f"THEN {self.sql(e, 'true')}") 3351 3352 default = self.sql(expression, "default") 3353 3354 if default: 3355 statements.append(f"ELSE {default}") 3356 3357 statements.append("END") 3358 3359 if self.pretty and self.too_wide(statements): 3360 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3361 3362 return " ".join(statements)
3374 def extract_sql(self, expression: exp.Extract) -> str: 3375 import sqlglot.dialects.dialect 3376 3377 this = ( 3378 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3379 if self.NORMALIZE_EXTRACT_DATE_PARTS 3380 else expression.this 3381 ) 3382 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3383 expression_sql = self.sql(expression, "expression") 3384 3385 return f"EXTRACT({this_sql} FROM {expression_sql})"
3387 def trim_sql(self, expression: exp.Trim) -> str: 3388 trim_type = self.sql(expression, "position") 3389 3390 if trim_type == "LEADING": 3391 func_name = "LTRIM" 3392 elif trim_type == "TRAILING": 3393 func_name = "RTRIM" 3394 else: 3395 func_name = "TRIM" 3396 3397 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3399 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3400 args = expression.expressions 3401 if isinstance(expression, exp.ConcatWs): 3402 args = args[1:] # Skip the delimiter 3403 3404 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3405 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3406 3407 concat_coalesce = ( 3408 self.dialect.CONCAT_WS_COALESCE 3409 if isinstance(expression, exp.ConcatWs) 3410 else self.dialect.CONCAT_COALESCE 3411 ) 3412 3413 if not concat_coalesce and expression.args.get("coalesce"): 3414 3415 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3416 if not e.type: 3417 import sqlglot.optimizer.annotate_types 3418 3419 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3420 3421 if e.is_string or e.is_type(exp.DType.ARRAY): 3422 return e 3423 3424 return exp.func("coalesce", e, exp.Literal.string("")) 3425 3426 args = [_wrap_with_coalesce(e) for e in args] 3427 3428 return args
3430 def concat_sql(self, expression: exp.Concat) -> str: 3431 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3432 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3433 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3434 # instead of coalescing them to empty string. 3435 import sqlglot.dialects.dialect 3436 3437 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3438 3439 expressions = self.convert_concat_args(expression) 3440 3441 # Some dialects don't allow a single-argument CONCAT call 3442 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3443 return self.sql(expressions[0]) 3444 3445 return self.func("CONCAT", *expressions)
3447 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3448 if self.dialect.CONCAT_WS_COALESCE and not expression.args.get("coalesce"): 3449 # Dialect's CONCAT_WS function skips NULL args, but the expression does not. 3450 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3451 all_args = expression.expressions 3452 expression.set("coalesce", True) 3453 return self.sql( 3454 exp.case() 3455 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3456 .else_(expression) 3457 ) 3458 3459 return self.func( 3460 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3461 )
3467 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3468 expressions = self.expressions(expression, flat=True) 3469 expressions = f" ({expressions})" if expressions else "" 3470 reference = self.sql(expression, "reference") 3471 reference = f" {reference}" if reference else "" 3472 delete = self.sql(expression, "delete") 3473 delete = f" ON DELETE {delete}" if delete else "" 3474 update = self.sql(expression, "update") 3475 update = f" ON UPDATE {update}" if update else "" 3476 options = self.expressions(expression, key="options", flat=True, sep=" ") 3477 options = f" {options}" if options else "" 3478 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3480 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3481 this = self.sql(expression, "this") 3482 this = f" {this}" if this else "" 3483 expressions = self.expressions(expression, flat=True) 3484 include = self.sql(expression, "include") 3485 options = self.expressions(expression, key="options", flat=True, sep=" ") 3486 options = f" {options}" if options else "" 3487 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3492 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3493 if self.MATCH_AGAINST_TABLE_PREFIX: 3494 expressions = [] 3495 for expr in expression.expressions: 3496 if isinstance(expr, exp.Table): 3497 expressions.append(f"TABLE {self.sql(expr)}") 3498 else: 3499 expressions.append(expr) 3500 else: 3501 expressions = expression.expressions 3502 3503 modifier = expression.args.get("modifier") 3504 modifier = f" {modifier}" if modifier else "" 3505 return ( 3506 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3507 )
3512 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3513 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3514 3515 if expression.args.get("escape"): 3516 path = self.escape_str(path) 3517 3518 if self.QUOTE_JSON_PATH: 3519 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3520 3521 return path
3523 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3524 if isinstance(expression, exp.JSONPathPart): 3525 transform = self.TRANSFORMS.get(expression.__class__) 3526 if not callable(transform): 3527 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3528 return "" 3529 3530 return transform(self, expression) 3531 3532 if isinstance(expression, int): 3533 return str(expression) 3534 3535 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3536 escaped = expression.replace("'", "\\'") 3537 escaped = f"\\'{expression}\\'" 3538 else: 3539 escaped = expression.replace('"', '\\"') 3540 escaped = f'"{escaped}"' 3541 3542 return escaped
3547 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3548 # Output the Teradata column FORMAT override. 3549 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3550 this = self.sql(expression, "this") 3551 fmt = self.sql(expression, "format") 3552 return f"{this} (FORMAT {fmt})"
3580 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3581 null_handling = expression.args.get("null_handling") 3582 null_handling = f" {null_handling}" if null_handling else "" 3583 return_type = self.sql(expression, "return_type") 3584 return_type = f" RETURNING {return_type}" if return_type else "" 3585 strict = " STRICT" if expression.args.get("strict") else "" 3586 return self.func( 3587 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3588 )
3590 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3591 this = self.sql(expression, "this") 3592 order = self.sql(expression, "order") 3593 null_handling = expression.args.get("null_handling") 3594 null_handling = f" {null_handling}" if null_handling else "" 3595 return_type = self.sql(expression, "return_type") 3596 return_type = f" RETURNING {return_type}" if return_type else "" 3597 strict = " STRICT" if expression.args.get("strict") else "" 3598 return self.func( 3599 "JSON_ARRAYAGG", 3600 this, 3601 suffix=f"{order}{null_handling}{return_type}{strict})", 3602 )
3604 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3605 path = self.sql(expression, "path") 3606 path = f" PATH {path}" if path else "" 3607 nested_schema = self.sql(expression, "nested_schema") 3608 3609 if nested_schema: 3610 return f"NESTED{path} {nested_schema}" 3611 3612 this = self.sql(expression, "this") 3613 kind = self.sql(expression, "kind") 3614 kind = f" {kind}" if kind else "" 3615 format_json = " FORMAT JSON" if expression.args.get("format_json") else "" 3616 3617 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3618 return f"{this}{kind}{format_json}{path}{ordinality}"
3623 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3624 this = self.sql(expression, "this") 3625 path = self.sql(expression, "path") 3626 path = f", {path}" if path else "" 3627 error_handling = expression.args.get("error_handling") 3628 error_handling = f" {error_handling}" if error_handling else "" 3629 empty_handling = expression.args.get("empty_handling") 3630 empty_handling = f" {empty_handling}" if empty_handling else "" 3631 schema = self.sql(expression, "schema") 3632 return self.func( 3633 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3634 )
3636 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3637 this = self.sql(expression, "this") 3638 kind = self.sql(expression, "kind") 3639 path = self.sql(expression, "path") 3640 path = f" {path}" if path else "" 3641 as_json = " AS JSON" if expression.args.get("as_json") else "" 3642 return f"{this} {kind}{path}{as_json}"
3644 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3645 this = self.sql(expression, "this") 3646 path = self.sql(expression, "path") 3647 path = f", {path}" if path else "" 3648 expressions = self.expressions(expression) 3649 with_ = ( 3650 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3651 if expressions 3652 else "" 3653 ) 3654 return f"OPENJSON({this}{path}){with_}"
3656 def in_sql(self, expression: exp.In) -> str: 3657 query = expression.args.get("query") 3658 unnest = expression.args.get("unnest") 3659 field = expression.args.get("field") 3660 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3661 3662 if query: 3663 in_sql = self.sql(query) 3664 elif unnest: 3665 in_sql = self.in_unnest_op(unnest) 3666 elif field: 3667 in_sql = self.sql(field) 3668 else: 3669 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3670 3671 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3676 def interval_sql(self, expression: exp.Interval) -> str: 3677 unit_expression = expression.args.get("unit") 3678 unit = self.sql(unit_expression) if unit_expression else "" 3679 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3680 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3681 unit = f" {unit}" if unit else "" 3682 3683 if self.SINGLE_STRING_INTERVAL: 3684 this = expression.this.name if expression.this else "" 3685 if this: 3686 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3687 return f"INTERVAL '{this}'{unit}" 3688 return f"INTERVAL '{this}{unit}'" 3689 return f"INTERVAL{unit}" 3690 3691 this = self.sql(expression, "this") 3692 if this: 3693 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3694 this = f" {this}" if unwrapped else f" ({this})" 3695 3696 return f"INTERVAL{this}{unit}"
3701 def reference_sql(self, expression: exp.Reference) -> str: 3702 this = self.sql(expression, "this") 3703 expressions = self.expressions(expression, flat=True) 3704 expressions = f"({expressions})" if expressions else "" 3705 options = self.expressions(expression, key="options", flat=True, sep=" ") 3706 options = f" {options}" if options else "" 3707 return f"REFERENCES {this}{expressions}{options}"
3709 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3710 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3711 parent = expression.parent 3712 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3713 3714 return self.func( 3715 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3716 )
3736 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3737 alias = expression.args["alias"] 3738 3739 parent = expression.parent 3740 pivot = parent and parent.parent 3741 3742 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3743 identifier_alias = isinstance(alias, exp.Identifier) 3744 literal_alias = isinstance(alias, exp.Literal) 3745 3746 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3747 alias.replace(exp.Literal.string(alias.output_name)) 3748 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3749 alias.replace(exp.to_identifier(alias.output_name)) 3750 3751 return self.alias_sql(expression)
def
and_sql( self, expression: sqlglot.expressions.core.And, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str: