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 the LikeProperty needs to be specified inside of the schema clause 468 LIKE_PROPERTY_INSIDE_SCHEMA = False 469 470 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 471 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 472 MULTI_ARG_DISTINCT = True 473 474 # Whether the JSON extraction operators expect a value of type JSON 475 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 476 477 # Whether bracketed keys like ["foo"] are supported in JSON paths 478 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 479 480 # Whether to escape keys using single quotes in JSON paths 481 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 482 483 # The JSONPathPart expressions supported by this dialect 484 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 485 486 # Whether any(f(x) for x in array) can be implemented by this dialect 487 CAN_IMPLEMENT_ARRAY_ANY = False 488 489 # Whether the function TO_NUMBER is supported 490 SUPPORTS_TO_NUMBER = True 491 492 # Whether EXCLUDE in window specification is supported 493 SUPPORTS_WINDOW_EXCLUDE = False 494 495 # Whether or not set op modifiers apply to the outer set op or select. 496 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 497 # True means limit 1 happens after the set op, False means it it happens on y. 498 SET_OP_MODIFIERS = True 499 500 # Whether parameters from COPY statement are wrapped in parentheses 501 COPY_PARAMS_ARE_WRAPPED = True 502 503 # Whether values of params are set with "=" token or empty space 504 COPY_PARAMS_EQ_REQUIRED = False 505 506 # Whether COPY statement has INTO keyword 507 COPY_HAS_INTO_KEYWORD = True 508 509 # Whether the conditional TRY(expression) function is supported 510 TRY_SUPPORTED = True 511 512 # Whether the UESCAPE syntax in unicode strings is supported 513 SUPPORTS_UESCAPE = True 514 515 # Function used to replace escaped unicode codes in unicode strings 516 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 517 518 # The keyword to use when generating a star projection with excluded columns 519 STAR_EXCEPT = "EXCEPT" 520 521 # The HEX function name 522 HEX_FUNC = "HEX" 523 524 # The keywords to use when prefixing & separating WITH based properties 525 WITH_PROPERTIES_PREFIX = "WITH" 526 527 # Whether to quote the generated expression of exp.JsonPath 528 QUOTE_JSON_PATH = True 529 530 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 531 PAD_FILL_PATTERN_IS_REQUIRED = False 532 533 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 534 SUPPORTS_EXPLODING_PROJECTIONS = True 535 536 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 537 ARRAY_CONCAT_IS_VAR_LEN = True 538 539 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 540 SUPPORTS_CONVERT_TIMEZONE = False 541 542 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 543 SUPPORTS_MEDIAN = True 544 545 # Whether UNIX_SECONDS(timestamp) is supported 546 SUPPORTS_UNIX_SECONDS = False 547 548 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 549 ALTER_SET_WRAPPED = False 550 551 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 552 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 553 # TODO: The normalization should be done by default once we've tested it across all dialects. 554 NORMALIZE_EXTRACT_DATE_PARTS = False 555 556 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 557 PARSE_JSON_NAME: str | None = "PARSE_JSON" 558 559 # The function name of the exp.ArraySize expression 560 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 561 562 # The syntax to use when altering the type of a column 563 ALTER_SET_TYPE = "SET DATA TYPE" 564 565 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 566 # None -> Doesn't support it at all 567 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 568 # True (Postgres) -> Explicitly requires it 569 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 570 571 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 572 SUPPORTS_DECODE_CASE = True 573 574 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 575 SUPPORTS_BETWEEN_FLAGS = False 576 577 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 578 SUPPORTS_LIKE_QUANTIFIERS = True 579 580 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 581 MATCH_AGAINST_TABLE_PREFIX: str | None = None 582 583 # Whether to include the VARIABLE keyword for SET assignments 584 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 585 586 # The keyword to use for default value assignment in DECLARE statements 587 DECLARE_DEFAULT_ASSIGNMENT = "=" 588 589 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 590 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 591 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 592 UPDATE_STATEMENT_SUPPORTS_FROM = True 593 594 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 595 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 596 597 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 598 # - Snowflake: DROP ICEBERG TABLE a.b; 599 # - DuckDB: DROP TABLE a.b; 600 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 601 602 TYPE_MAPPING: t.ClassVar = { 603 exp.DType.DATETIME2: "TIMESTAMP", 604 exp.DType.NCHAR: "CHAR", 605 exp.DType.NVARCHAR: "VARCHAR", 606 exp.DType.MEDIUMTEXT: "TEXT", 607 exp.DType.LONGTEXT: "TEXT", 608 exp.DType.TINYTEXT: "TEXT", 609 exp.DType.BLOB: "VARBINARY", 610 exp.DType.MEDIUMBLOB: "BLOB", 611 exp.DType.LONGBLOB: "BLOB", 612 exp.DType.TINYBLOB: "BLOB", 613 exp.DType.INET: "INET", 614 exp.DType.ROWVERSION: "VARBINARY", 615 exp.DType.SMALLDATETIME: "TIMESTAMP", 616 } 617 618 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 619 620 TIME_PART_SINGULARS: t.ClassVar = { 621 "MICROSECONDS": "MICROSECOND", 622 "SECONDS": "SECOND", 623 "MINUTES": "MINUTE", 624 "HOURS": "HOUR", 625 "DAYS": "DAY", 626 "WEEKS": "WEEK", 627 "MONTHS": "MONTH", 628 "QUARTERS": "QUARTER", 629 "YEARS": "YEAR", 630 } 631 632 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 633 "cluster": lambda self, e: self.sql(e, "cluster"), 634 "distribute": lambda self, e: self.sql(e, "distribute"), 635 "sort": lambda self, e: self.sql(e, "sort"), 636 **AFTER_HAVING_MODIFIER_TRANSFORMS, 637 } 638 639 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 640 641 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 642 643 PARAMETER_TOKEN = "@" 644 NAMED_PLACEHOLDER_TOKEN = ":" 645 646 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 647 648 PROPERTIES_LOCATION: t.ClassVar = { 649 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 650 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 651 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 652 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 653 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 654 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 655 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 656 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 657 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 658 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 659 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 660 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 661 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 662 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 663 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 664 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 665 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 666 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 667 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 668 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 669 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 671 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 672 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 673 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 674 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 675 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 676 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 677 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 678 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 679 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 681 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 682 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 683 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 684 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 685 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 686 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 687 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 688 exp.HeapProperty: exp.Properties.Location.POST_WITH, 689 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 690 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 691 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 692 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 693 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 694 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 695 exp.JournalProperty: exp.Properties.Location.POST_NAME, 696 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 697 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 698 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 699 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 700 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 701 exp.LogProperty: exp.Properties.Location.POST_NAME, 702 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 703 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 704 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 705 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 706 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 707 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 708 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 709 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 710 exp.Order: exp.Properties.Location.POST_SCHEMA, 711 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 712 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 713 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 714 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 715 exp.Property: exp.Properties.Location.POST_WITH, 716 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 717 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 718 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 719 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 720 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 721 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 722 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 723 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 725 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 727 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 728 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 729 exp.Set: exp.Properties.Location.POST_SCHEMA, 730 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 731 exp.SetProperty: exp.Properties.Location.POST_CREATE, 732 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 733 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 734 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 735 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 736 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 737 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 742 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 743 exp.Tags: exp.Properties.Location.POST_WITH, 744 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 745 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 746 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 747 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 748 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 749 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 750 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 751 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 752 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 754 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 755 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 756 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 757 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 758 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 759 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 760 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 761 } 762 763 # Keywords that can't be used as unquoted identifier names 764 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 765 766 # Exprs whose comments are separated from them for better formatting 767 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 768 exp.Command, 769 exp.Create, 770 exp.Describe, 771 exp.Delete, 772 exp.Drop, 773 exp.From, 774 exp.Insert, 775 exp.Join, 776 exp.MultitableInserts, 777 exp.Order, 778 exp.Group, 779 exp.Having, 780 exp.Select, 781 exp.SetOperation, 782 exp.Update, 783 exp.Where, 784 exp.With, 785 ) 786 787 # Exprs that should not have their comments generated in maybe_comment 788 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 789 exp.Binary, 790 exp.SetOperation, 791 ) 792 793 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 794 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 795 exp.Column, 796 exp.Literal, 797 exp.Neg, 798 exp.Paren, 799 ) 800 801 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 802 exp.DType.NVARCHAR, 803 exp.DType.VARCHAR, 804 exp.DType.CHAR, 805 exp.DType.NCHAR, 806 } 807 808 # Exprs that need to have all CTEs under them bubbled up to them 809 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 810 811 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 812 813 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 814 815 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 816 817 __slots__ = ( 818 "pretty", 819 "identify", 820 "normalize", 821 "pad", 822 "_indent", 823 "normalize_functions", 824 "unsupported_level", 825 "max_unsupported", 826 "leading_comma", 827 "max_text_width", 828 "comments", 829 "dialect", 830 "unsupported_messages", 831 "_escaped_quote_end", 832 "_escaped_byte_quote_end", 833 "_escaped_identifier_end", 834 "_next_name", 835 "_identifier_start", 836 "_identifier_end", 837 "_quote_json_path_key_using_brackets", 838 "_dispatch", 839 ) 840 841 def __init__( 842 self, 843 pretty: bool | int | None = None, 844 identify: str | bool = False, 845 normalize: bool = False, 846 pad: int = 2, 847 indent: int = 2, 848 normalize_functions: str | bool | None = None, 849 unsupported_level: ErrorLevel = ErrorLevel.WARN, 850 max_unsupported: int = 3, 851 leading_comma: bool = False, 852 max_text_width: int = 80, 853 comments: bool = True, 854 dialect: DialectType = None, 855 ): 856 import sqlglot 857 import sqlglot.dialects.dialect 858 859 self.pretty = pretty if pretty is not None else sqlglot.pretty 860 self.identify = identify 861 self.normalize = normalize 862 self.pad = pad 863 self._indent = indent 864 self.unsupported_level = unsupported_level 865 self.max_unsupported = max_unsupported 866 self.leading_comma = leading_comma 867 self.max_text_width = max_text_width 868 self.comments = comments 869 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 870 871 # This is both a Dialect property and a Generator argument, so we prioritize the latter 872 self.normalize_functions = ( 873 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 874 ) 875 876 self.unsupported_messages: list[str] = [] 877 self._escaped_quote_end: str = ( 878 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 879 ) 880 self._escaped_byte_quote_end: str = ( 881 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 882 if self.dialect.BYTE_END 883 else "" 884 ) 885 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 886 887 self._next_name = name_sequence("_t") 888 889 self._identifier_start = self.dialect.IDENTIFIER_START 890 self._identifier_end = self.dialect.IDENTIFIER_END 891 892 self._quote_json_path_key_using_brackets = True 893 894 cls = type(self) 895 dispatch = _DISPATCH_CACHE.get(cls) 896 if dispatch is None: 897 dispatch = _build_dispatch(cls) 898 _DISPATCH_CACHE[cls] = dispatch 899 self._dispatch = dispatch 900 901 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 902 """ 903 Generates the SQL string corresponding to the given syntax tree. 904 905 Args: 906 expression: The syntax tree. 907 copy: Whether to copy the expression. The generator performs mutations so 908 it is safer to copy. 909 910 Returns: 911 The SQL string corresponding to `expression`. 912 """ 913 if copy: 914 expression = expression.copy() 915 916 expression = self.preprocess(expression) 917 918 self.unsupported_messages = [] 919 sql = self.sql(expression).strip() 920 921 if self.pretty: 922 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 923 924 if self.unsupported_level == ErrorLevel.IGNORE: 925 return sql 926 927 if self.unsupported_level == ErrorLevel.WARN: 928 for msg in self.unsupported_messages: 929 logger.warning(msg) 930 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 931 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 932 933 return sql 934 935 def preprocess(self, expression: exp.Expr) -> exp.Expr: 936 """Apply generic preprocessing transformations to a given expression.""" 937 expression = self._move_ctes_to_top_level(expression) 938 939 if self.ENSURE_BOOLS: 940 import sqlglot.transforms 941 942 expression = sqlglot.transforms.ensure_bools(expression) 943 944 return expression 945 946 def _move_ctes_to_top_level(self, expression: E) -> E: 947 if ( 948 not expression.parent 949 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 950 and any(node.parent is not expression for node in expression.find_all(exp.With)) 951 ): 952 import sqlglot.transforms 953 954 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 955 return expression 956 957 def unsupported(self, message: str) -> None: 958 if self.unsupported_level == ErrorLevel.IMMEDIATE: 959 raise UnsupportedError(message) 960 self.unsupported_messages.append(message) 961 962 def sep(self, sep: str = " ") -> str: 963 return f"{sep.strip()}\n" if self.pretty else sep 964 965 def seg(self, sql: str, sep: str = " ") -> str: 966 return f"{self.sep(sep)}{sql}" 967 968 def sanitize_comment(self, comment: str) -> str: 969 comment = " " + comment if comment[0].strip() else comment 970 comment = comment + " " if comment[-1].strip() else comment 971 972 # Escape block comment markers to prevent premature closure or unintended nesting. 973 # This is necessary because single-line comments (--) are converted to block comments 974 # (/* */) on output, and any */ in the original text would close the comment early. 975 comment = comment.replace("*/", "* /").replace("/*", "/ *") 976 977 return comment 978 979 def maybe_comment( 980 self, 981 sql: str, 982 expression: exp.Expr | None = None, 983 comments: list[str] | None = None, 984 separated: bool = False, 985 ) -> str: 986 comments = ( 987 ((expression and expression.comments) if comments is None else comments) # type: ignore 988 if self.comments 989 else None 990 ) 991 992 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 993 return sql 994 995 comments_sql = " ".join( 996 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 997 ) 998 999 if not comments_sql: 1000 return sql 1001 1002 comments_sql = self._replace_line_breaks(comments_sql) 1003 1004 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1005 return ( 1006 f"{self.sep()}{comments_sql}{sql}" 1007 if not sql or sql[0].isspace() 1008 else f"{comments_sql}{self.sep()}{sql}" 1009 ) 1010 1011 return f"{sql} {comments_sql}" 1012 1013 def wrap(self, expression: exp.Expr | str) -> str: 1014 this_sql = ( 1015 self.sql(expression) 1016 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1017 else self.sql(expression, "this") 1018 ) 1019 if not this_sql: 1020 return "()" 1021 1022 this_sql = self.indent(this_sql, level=1, pad=0) 1023 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1024 1025 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1026 original = self.identify 1027 self.identify = False 1028 result = func(*args, **kwargs) 1029 self.identify = original 1030 return result 1031 1032 def normalize_func(self, name: str) -> str: 1033 if self.normalize_functions == "upper" or self.normalize_functions is True: 1034 return name.upper() 1035 if self.normalize_functions == "lower": 1036 return name.lower() 1037 return name 1038 1039 def indent( 1040 self, 1041 sql: str, 1042 level: int = 0, 1043 pad: int | None = None, 1044 skip_first: bool = False, 1045 skip_last: bool = False, 1046 ) -> str: 1047 if not self.pretty or not sql: 1048 return sql 1049 1050 pad = self.pad if pad is None else pad 1051 lines = sql.split("\n") 1052 1053 return "\n".join( 1054 ( 1055 line 1056 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1057 else f"{' ' * (level * self._indent + pad)}{line}" 1058 ) 1059 for i, line in enumerate(lines) 1060 ) 1061 1062 def sql( 1063 self, 1064 expression: str | exp.Expr | None, 1065 key: str | None = None, 1066 comment: bool = True, 1067 ) -> str: 1068 if not expression: 1069 return "" 1070 1071 if isinstance(expression, str): 1072 return expression 1073 1074 if key: 1075 value = expression.args.get(key) 1076 if value: 1077 return self.sql(value) 1078 return "" 1079 1080 handler = self._dispatch.get(expression.__class__) 1081 1082 if handler: 1083 sql = handler(self, expression) 1084 elif isinstance(expression, exp.Func): 1085 sql = self.function_fallback_sql(expression) 1086 elif isinstance(expression, exp.Property): 1087 sql = self.property_sql(expression) 1088 else: 1089 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1090 1091 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1092 1093 def uncache_sql(self, expression: exp.Uncache) -> str: 1094 table = self.sql(expression, "this") 1095 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1096 return f"UNCACHE TABLE{exists_sql} {table}" 1097 1098 def cache_sql(self, expression: exp.Cache) -> str: 1099 lazy = " LAZY" if expression.args.get("lazy") else "" 1100 table = self.sql(expression, "this") 1101 options = expression.args.get("options") 1102 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1103 sql = self.sql(expression, "expression") 1104 sql = f" AS{self.sep()}{sql}" if sql else "" 1105 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1106 return self.prepend_ctes(expression, sql) 1107 1108 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1109 default = "DEFAULT " if expression.args.get("default") else "" 1110 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1111 1112 def column_parts(self, expression: exp.Column) -> str: 1113 return ".".join( 1114 self.sql(part) 1115 for part in ( 1116 expression.args.get("catalog"), 1117 expression.args.get("db"), 1118 expression.args.get("table"), 1119 expression.args.get("this"), 1120 ) 1121 if part 1122 ) 1123 1124 def column_sql(self, expression: exp.Column) -> str: 1125 join_mark = " (+)" if expression.args.get("join_mark") else "" 1126 1127 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1128 join_mark = "" 1129 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1130 1131 return f"{self.column_parts(expression)}{join_mark}" 1132 1133 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1134 return self.column_sql(expression) 1135 1136 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1137 this = self.sql(expression, "this") 1138 this = f" {this}" if this else "" 1139 position = self.sql(expression, "position") 1140 return f"{position}{this}" 1141 1142 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1143 column = self.sql(expression, "this") 1144 kind = self.sql(expression, "kind") 1145 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1146 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1147 kind = f"{sep}{kind}" if kind else "" 1148 constraints = f" {constraints}" if constraints else "" 1149 position = self.sql(expression, "position") 1150 position = f" {position}" if position else "" 1151 1152 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1153 kind = "" 1154 1155 return f"{exists}{column}{kind}{constraints}{position}" 1156 1157 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1158 this = self.sql(expression, "this") 1159 kind_sql = self.sql(expression, "kind").strip() 1160 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1161 1162 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1163 this = self.sql(expression, "this") 1164 if expression.args.get("not_null"): 1165 persisted = " PERSISTED NOT NULL" 1166 elif expression.args.get("persisted"): 1167 persisted = " PERSISTED" 1168 else: 1169 persisted = "" 1170 1171 return f"AS {this}{persisted}" 1172 1173 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1174 return self.token_sql(TokenType.AUTO_INCREMENT) 1175 1176 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1177 if isinstance(expression.this, list): 1178 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1179 else: 1180 this = self.sql(expression, "this") 1181 1182 return f"COMPRESS {this}" 1183 1184 def generatedasidentitycolumnconstraint_sql( 1185 self, expression: exp.GeneratedAsIdentityColumnConstraint 1186 ) -> str: 1187 this = "" 1188 if expression.this is not None: 1189 on_null = " ON NULL" if expression.args.get("on_null") else "" 1190 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1191 1192 start = expression.args.get("start") 1193 start = f"START WITH {start}" if start else "" 1194 increment = expression.args.get("increment") 1195 increment = f" INCREMENT BY {increment}" if increment else "" 1196 minvalue = expression.args.get("minvalue") 1197 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1198 maxvalue = expression.args.get("maxvalue") 1199 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1200 cycle = expression.args.get("cycle") 1201 cycle_sql = "" 1202 1203 if cycle is not None: 1204 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1205 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1206 1207 sequence_opts = "" 1208 if start or increment or cycle_sql: 1209 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1210 sequence_opts = f" ({sequence_opts.strip()})" 1211 1212 expr = self.sql(expression, "expression") 1213 expr = f"({expr})" if expr else "IDENTITY" 1214 1215 return f"GENERATED{this} AS {expr}{sequence_opts}" 1216 1217 def generatedasrowcolumnconstraint_sql( 1218 self, expression: exp.GeneratedAsRowColumnConstraint 1219 ) -> str: 1220 start = "START" if expression.args.get("start") else "END" 1221 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1222 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1223 1224 def periodforsystemtimeconstraint_sql( 1225 self, expression: exp.PeriodForSystemTimeConstraint 1226 ) -> str: 1227 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1228 1229 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1230 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1231 1232 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1233 desc = expression.args.get("desc") 1234 if desc is not None: 1235 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1236 options = self.expressions(expression, key="options", flat=True, sep=" ") 1237 options = f" {options}" if options else "" 1238 return f"PRIMARY KEY{options}" 1239 1240 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1241 this = self.sql(expression, "this") 1242 this = f" {this}" if this else "" 1243 index_type = expression.args.get("index_type") 1244 index_type = f" USING {index_type}" if index_type else "" 1245 on_conflict = self.sql(expression, "on_conflict") 1246 on_conflict = f" {on_conflict}" if on_conflict else "" 1247 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1248 options = self.expressions(expression, key="options", flat=True, sep=" ") 1249 options = f" {options}" if options else "" 1250 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1251 1252 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1253 input_ = expression.args.get("input_") 1254 output = expression.args.get("output") 1255 variadic = expression.args.get("variadic") 1256 1257 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1258 if variadic: 1259 return "VARIADIC" 1260 1261 if input_ and output: 1262 return f"IN{self.INOUT_SEPARATOR}OUT" 1263 if input_: 1264 return "IN" 1265 if output: 1266 return "OUT" 1267 1268 return "" 1269 1270 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1271 return self.sql(expression, "this") 1272 1273 def create_sql(self, expression: exp.Create) -> str: 1274 kind = self.sql(expression, "kind") 1275 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1276 1277 properties = expression.args.get("properties") 1278 1279 if ( 1280 kind == "TRIGGER" 1281 and properties 1282 and properties.expressions 1283 and isinstance(properties.expressions[0], exp.TriggerProperties) 1284 and properties.expressions[0].args.get("constraint") 1285 ): 1286 kind = f"CONSTRAINT {kind}" 1287 1288 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1289 1290 this = self.createable_sql(expression, properties_locs) 1291 1292 properties_sql = "" 1293 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1294 exp.Properties.Location.POST_WITH 1295 ): 1296 props_ast = exp.Properties( 1297 expressions=[ 1298 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1299 *properties_locs[exp.Properties.Location.POST_WITH], 1300 ] 1301 ) 1302 props_ast.parent = expression 1303 properties_sql = self.sql(props_ast) 1304 1305 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1306 properties_sql = self.sep() + properties_sql 1307 elif not self.pretty: 1308 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1309 properties_sql = f" {properties_sql}" 1310 1311 begin = " BEGIN" if expression.args.get("begin") else "" 1312 1313 expression_sql = self.sql(expression, "expression") 1314 if expression_sql: 1315 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1316 1317 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1318 postalias_props_sql = "" 1319 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1320 postalias_props_sql = self.properties( 1321 exp.Properties( 1322 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1323 ), 1324 wrapped=False, 1325 ) 1326 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1327 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1328 1329 postindex_props_sql = "" 1330 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1331 postindex_props_sql = self.properties( 1332 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1333 wrapped=False, 1334 prefix=" ", 1335 ) 1336 1337 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1338 indexes = f" {indexes}" if indexes else "" 1339 index_sql = indexes + postindex_props_sql 1340 1341 replace = " OR REPLACE" if expression.args.get("replace") else "" 1342 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1343 unique = " UNIQUE" if expression.args.get("unique") else "" 1344 1345 clustered = expression.args.get("clustered") 1346 if clustered is None: 1347 clustered_sql = "" 1348 elif clustered: 1349 clustered_sql = " CLUSTERED COLUMNSTORE" 1350 else: 1351 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1352 1353 postcreate_props_sql = "" 1354 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1355 postcreate_props_sql = self.properties( 1356 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1357 sep=" ", 1358 prefix=" ", 1359 wrapped=False, 1360 ) 1361 1362 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1363 1364 postexpression_props_sql = "" 1365 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1366 postexpression_props_sql = self.properties( 1367 exp.Properties( 1368 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1369 ), 1370 sep=" ", 1371 prefix=" ", 1372 wrapped=False, 1373 ) 1374 1375 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1376 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1377 no_schema_binding = ( 1378 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1379 ) 1380 1381 clone = self.sql(expression, "clone") 1382 clone = f" {clone}" if clone else "" 1383 1384 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1385 properties_expression = f"{expression_sql}{properties_sql}" 1386 else: 1387 properties_expression = f"{properties_sql}{expression_sql}" 1388 1389 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1390 return self.prepend_ctes(expression, expression_sql) 1391 1392 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1393 start = self.sql(expression, "start") 1394 start = f"START WITH {start}" if start else "" 1395 increment = self.sql(expression, "increment") 1396 increment = f" INCREMENT BY {increment}" if increment else "" 1397 minvalue = self.sql(expression, "minvalue") 1398 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1399 maxvalue = self.sql(expression, "maxvalue") 1400 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1401 owned = self.sql(expression, "owned") 1402 owned = f" OWNED BY {owned}" if owned else "" 1403 1404 cache = expression.args.get("cache") 1405 if cache is None: 1406 cache_str = "" 1407 elif cache is True: 1408 cache_str = " CACHE" 1409 else: 1410 cache_str = f" CACHE {cache}" 1411 1412 options = self.expressions(expression, key="options", flat=True, sep=" ") 1413 options = f" {options}" if options else "" 1414 1415 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1416 1417 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1418 timing = expression.args.get("timing", "") 1419 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1420 timing_events = f"{timing} {events}".strip() if timing or events else "" 1421 1422 parts = [timing_events, "ON", self.sql(expression, "table")] 1423 1424 if referenced_table := expression.args.get("referenced_table"): 1425 parts.extend(["FROM", self.sql(referenced_table)]) 1426 1427 if deferrable := expression.args.get("deferrable"): 1428 parts.append(deferrable) 1429 1430 if initially := expression.args.get("initially"): 1431 parts.append(f"INITIALLY {initially}") 1432 1433 if referencing := expression.args.get("referencing"): 1434 parts.append(self.sql(referencing)) 1435 1436 if for_each := expression.args.get("for_each"): 1437 parts.append(f"FOR EACH {for_each}") 1438 1439 if when := expression.args.get("when"): 1440 parts.append(f"WHEN ({self.sql(when)})") 1441 1442 parts.append(self.sql(expression, "execute")) 1443 1444 return self.sep().join(parts) 1445 1446 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1447 parts = [] 1448 1449 if old_alias := expression.args.get("old"): 1450 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1451 1452 if new_alias := expression.args.get("new"): 1453 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1454 1455 return f"REFERENCING {' '.join(parts)}" 1456 1457 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1458 columns = expression.args.get("columns") 1459 if columns: 1460 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1461 1462 return self.sql(expression, "this") 1463 1464 def clone_sql(self, expression: exp.Clone) -> str: 1465 this = self.sql(expression, "this") 1466 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1467 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1468 return f"{shallow}{keyword} {this}" 1469 1470 def describe_sql(self, expression: exp.Describe) -> str: 1471 style = expression.args.get("style") 1472 style = f" {style}" if style else "" 1473 partition = self.sql(expression, "partition") 1474 partition = f" {partition}" if partition else "" 1475 format = self.sql(expression, "format") 1476 format = f" {format}" if format else "" 1477 as_json = " AS JSON" if expression.args.get("as_json") else "" 1478 1479 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1480 1481 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1482 tag = self.sql(expression, "tag") 1483 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1484 1485 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1486 with_ = self.sql(expression, "with_") 1487 if with_: 1488 sql = f"{with_}{self.sep()}{sql}" 1489 return sql 1490 1491 def with_sql(self, expression: exp.With) -> str: 1492 sql = self.expressions(expression, flat=True) 1493 recursive = ( 1494 "RECURSIVE " 1495 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1496 else "" 1497 ) 1498 search = self.sql(expression, "search") 1499 search = f" {search}" if search else "" 1500 1501 return f"WITH {recursive}{sql}{search}" 1502 1503 def cte_sql(self, expression: exp.CTE) -> str: 1504 alias = expression.args.get("alias") 1505 if alias: 1506 alias.add_comments(expression.pop_comments()) 1507 1508 alias_sql = self.sql(expression, "alias") 1509 1510 materialized = expression.args.get("materialized") 1511 if materialized is False: 1512 materialized = "NOT MATERIALIZED " 1513 elif materialized: 1514 materialized = "MATERIALIZED " 1515 1516 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1517 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1518 1519 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1520 1521 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1522 alias = self.sql(expression, "this") 1523 columns = self.expressions(expression, key="columns", flat=True) 1524 columns = f"({columns})" if columns else "" 1525 1526 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1527 columns = "" 1528 self.unsupported("Named columns are not supported in table alias.") 1529 1530 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1531 alias = self._next_name() 1532 1533 return f"{alias}{columns}" 1534 1535 def bitstring_sql(self, expression: exp.BitString) -> str: 1536 this = self.sql(expression, "this") 1537 if self.dialect.BIT_START: 1538 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1539 return f"{int(this, 2)}" 1540 1541 def hexstring_sql( 1542 self, expression: exp.HexString, binary_function_repr: str | None = None 1543 ) -> str: 1544 this = self.sql(expression, "this") 1545 is_integer_type = expression.args.get("is_integer") 1546 1547 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1548 not self.dialect.HEX_START and not binary_function_repr 1549 ): 1550 # Integer representation will be returned if: 1551 # - The read dialect treats the hex value as integer literal but not the write 1552 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1553 return f"{int(this, 16)}" 1554 1555 if not is_integer_type: 1556 # Read dialect treats the hex value as BINARY/BLOB 1557 if binary_function_repr: 1558 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1559 return self.func(binary_function_repr, exp.Literal.string(this)) 1560 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1561 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1562 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1563 1564 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1565 1566 def bytestring_sql(self, expression: exp.ByteString) -> str: 1567 this = self.sql(expression, "this") 1568 if self.dialect.BYTE_START: 1569 escaped_byte_string = self.escape_str( 1570 this, 1571 escape_backslash=False, 1572 delimiter=self.dialect.BYTE_END, 1573 escaped_delimiter=self._escaped_byte_quote_end, 1574 is_byte_string=True, 1575 ) 1576 is_bytes = expression.args.get("is_bytes", False) 1577 delimited_byte_string = ( 1578 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1579 ) 1580 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1581 return self.sql( 1582 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1583 ) 1584 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1585 return self.sql( 1586 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1587 ) 1588 1589 return delimited_byte_string 1590 return this 1591 1592 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1593 this = self.sql(expression, "this") 1594 escape = expression.args.get("escape") 1595 1596 if self.dialect.UNICODE_START: 1597 escape_substitute = r"\\\1" 1598 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1599 else: 1600 escape_substitute = r"\\u\1" 1601 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1602 1603 if escape: 1604 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1605 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1606 else: 1607 escape_pattern = ESCAPED_UNICODE_RE 1608 escape_sql = "" 1609 1610 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1611 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1612 1613 return f"{left_quote}{this}{right_quote}{escape_sql}" 1614 1615 def rawstring_sql(self, expression: exp.RawString) -> str: 1616 string = expression.this 1617 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1618 string = string.replace("\\", "\\\\") 1619 1620 string = self.escape_str(string, escape_backslash=False) 1621 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1622 1623 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1624 this = self.sql(expression, "this") 1625 specifier = self.sql(expression, "expression") 1626 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1627 return f"{this}{specifier}" 1628 1629 def datatype_sql(self, expression: exp.DataType) -> str: 1630 nested = "" 1631 values = "" 1632 1633 expr_nested = expression.args.get("nested") 1634 interior = ( 1635 self.expressions( 1636 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1637 ) 1638 if expr_nested and self.pretty 1639 else self.expressions(expression, flat=True) 1640 ) 1641 1642 type_value = expression.this 1643 if type_value in self.UNSUPPORTED_TYPES: 1644 self.unsupported( 1645 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1646 ) 1647 1648 type_sql: t.Any = "" 1649 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1650 type_sql = self.sql(expression, "kind") 1651 elif type_value == exp.DType.CHARACTER_SET: 1652 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1653 else: 1654 type_sql = ( 1655 self.TYPE_MAPPING.get(type_value, type_value.value) 1656 if isinstance(type_value, exp.DType) 1657 else type_value 1658 ) 1659 1660 if interior: 1661 if expr_nested: 1662 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1663 if expression.args.get("values") is not None: 1664 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1665 values = self.expressions(expression, key="values", flat=True) 1666 values = f"{delimiters[0]}{values}{delimiters[1]}" 1667 elif type_value == exp.DType.INTERVAL: 1668 nested = f" {interior}" 1669 else: 1670 nested = f"({interior})" 1671 1672 type_sql = f"{type_sql}{nested}{values}" 1673 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1674 exp.DType.TIMETZ, 1675 exp.DType.TIMESTAMPTZ, 1676 ): 1677 type_sql = f"{type_sql} WITH TIME ZONE" 1678 1679 return type_sql 1680 1681 def directory_sql(self, expression: exp.Directory) -> str: 1682 local = "LOCAL " if expression.args.get("local") else "" 1683 row_format = self.sql(expression, "row_format") 1684 row_format = f" {row_format}" if row_format else "" 1685 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1686 1687 def delete_sql(self, expression: exp.Delete) -> str: 1688 hint = self.sql(expression, "hint") 1689 this = self.sql(expression, "this") 1690 this = f" FROM {this}" if this else "" 1691 using = self.expressions(expression, key="using") 1692 using = f" USING {using}" if using else "" 1693 cluster = self.sql(expression, "cluster") 1694 cluster = f" {cluster}" if cluster else "" 1695 where = self.sql(expression, "where") 1696 returning = self.sql(expression, "returning") 1697 order = self.sql(expression, "order") 1698 limit = self.sql(expression, "limit") 1699 tables = self.expressions(expression, key="tables") 1700 tables = f" {tables}" if tables else "" 1701 if self.RETURNING_END: 1702 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1703 else: 1704 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1705 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1706 1707 def drop_sql(self, expression: exp.Drop) -> str: 1708 this = self.sql(expression, "this") 1709 expressions = self.expressions(expression, flat=True) 1710 expressions = f" ({expressions})" if expressions else "" 1711 kind = expression.args["kind"] 1712 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1713 iceberg = ( 1714 " ICEBERG" 1715 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1716 else "" 1717 ) 1718 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1719 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1720 on_cluster = self.sql(expression, "cluster") 1721 on_cluster = f" {on_cluster}" if on_cluster else "" 1722 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1723 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1724 cascade = " CASCADE" if expression.args.get("cascade") else "" 1725 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1726 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1727 purge = " PURGE" if expression.args.get("purge") else "" 1728 sync = " SYNC" if expression.args.get("sync") else "" 1729 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}" 1730 1731 def set_operation(self, expression: exp.SetOperation) -> str: 1732 op_type = type(expression) 1733 op_name = op_type.key.upper() 1734 1735 distinct = expression.args.get("distinct") 1736 if ( 1737 distinct is False 1738 and op_type in (exp.Except, exp.Intersect) 1739 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1740 ): 1741 self.unsupported(f"{op_name} ALL is not supported") 1742 1743 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1744 1745 if distinct is None: 1746 distinct = default_distinct 1747 if distinct is None: 1748 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1749 1750 if distinct is default_distinct: 1751 distinct_or_all = "" 1752 else: 1753 distinct_or_all = " DISTINCT" if distinct else " ALL" 1754 1755 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1756 side_kind = f"{side_kind} " if side_kind else "" 1757 1758 by_name = " BY NAME" if expression.args.get("by_name") else "" 1759 on = self.expressions(expression, key="on", flat=True) 1760 on = f" ON ({on})" if on else "" 1761 1762 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1763 1764 def set_operations(self, expression: exp.SetOperation) -> str: 1765 if not self.SET_OP_MODIFIERS: 1766 limit = expression.args.get("limit") 1767 order = expression.args.get("order") 1768 1769 if limit or order: 1770 select = self._move_ctes_to_top_level( 1771 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1772 ) 1773 1774 if limit: 1775 select = select.limit(limit.pop(), copy=False) 1776 if order: 1777 select = select.order_by(order.pop(), copy=False) 1778 return self.sql(select) 1779 1780 sqls: list[str] = [] 1781 stack: list[str | exp.Expr] = [expression] 1782 1783 while stack: 1784 node = stack.pop() 1785 1786 if isinstance(node, exp.SetOperation): 1787 stack.append(node.expression) 1788 stack.append( 1789 self.maybe_comment( 1790 self.set_operation(node), comments=node.comments, separated=True 1791 ) 1792 ) 1793 stack.append(node.this) 1794 else: 1795 sqls.append(self.sql(node)) 1796 1797 this = self.sep().join(sqls) 1798 this = self.query_modifiers(expression, this) 1799 return self.prepend_ctes(expression, this) 1800 1801 def fetch_sql(self, expression: exp.Fetch) -> str: 1802 direction = expression.args.get("direction") 1803 direction = f" {direction}" if direction else "" 1804 count = self.sql(expression, "count") 1805 count = f" {count}" if count else "" 1806 limit_options = self.sql(expression, "limit_options") 1807 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1808 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1809 1810 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1811 percent = " PERCENT" if expression.args.get("percent") else "" 1812 rows = " ROWS" if expression.args.get("rows") else "" 1813 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1814 if not with_ties and rows: 1815 with_ties = " ONLY" 1816 return f"{percent}{rows}{with_ties}" 1817 1818 def filter_sql(self, expression: exp.Filter) -> str: 1819 if self.AGGREGATE_FILTER_SUPPORTED: 1820 this = self.sql(expression, "this") 1821 where = self.sql(expression, "expression").strip() 1822 return f"{this} FILTER({where})" 1823 1824 agg = expression.this 1825 agg_arg = agg.this 1826 cond = expression.expression.this 1827 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1828 return self.sql(agg) 1829 1830 def hint_sql(self, expression: exp.Hint) -> str: 1831 if not self.QUERY_HINTS: 1832 self.unsupported("Hints are not supported") 1833 return "" 1834 1835 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1836 1837 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1838 using = self.sql(expression, "using") 1839 using = f" USING {using}" if using else "" 1840 columns = self.expressions(expression, key="columns", flat=True) 1841 columns = f"({columns})" if columns else "" 1842 partition_by = self.expressions(expression, key="partition_by", flat=True) 1843 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1844 where = self.sql(expression, "where") 1845 include = self.expressions(expression, key="include", flat=True) 1846 if include: 1847 include = f" INCLUDE ({include})" 1848 with_storage = self.expressions(expression, key="with_storage", flat=True) 1849 with_storage = f" WITH ({with_storage})" if with_storage else "" 1850 tablespace = self.sql(expression, "tablespace") 1851 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1852 on = self.sql(expression, "on") 1853 on = f" ON {on}" if on else "" 1854 1855 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1856 1857 def index_sql(self, expression: exp.Index) -> str: 1858 unique = "UNIQUE " if expression.args.get("unique") else "" 1859 primary = "PRIMARY " if expression.args.get("primary") else "" 1860 amp = "AMP " if expression.args.get("amp") else "" 1861 name = self.sql(expression, "this") 1862 name = f"{name} " if name else "" 1863 table = self.sql(expression, "table") 1864 table = f"{self.INDEX_ON} {table}" if table else "" 1865 1866 index = "INDEX " if not table else "" 1867 1868 params = self.sql(expression, "params") 1869 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1870 1871 def identifier_sql(self, expression: exp.Identifier) -> str: 1872 text = expression.name 1873 lower = text.lower() 1874 quoted = expression.quoted 1875 text = lower if self.normalize and not quoted else text 1876 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1877 if ( 1878 quoted 1879 or self.dialect.can_quote(expression, self.identify) 1880 or lower in self.RESERVED_KEYWORDS 1881 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1882 ): 1883 text = f"{self._identifier_start}{text}{self._identifier_end}" 1884 return text 1885 1886 def hex_sql(self, expression: exp.Hex) -> str: 1887 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1888 if self.dialect.HEX_LOWERCASE: 1889 text = self.func("LOWER", text) 1890 1891 return text 1892 1893 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1894 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1895 if not self.dialect.HEX_LOWERCASE: 1896 text = self.func("LOWER", text) 1897 return text 1898 1899 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1900 input_format = self.sql(expression, "input_format") 1901 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1902 output_format = self.sql(expression, "output_format") 1903 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1904 return self.sep().join((input_format, output_format)) 1905 1906 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1907 string = self.sql(exp.Literal.string(expression.name)) 1908 return f"{prefix}{string}" 1909 1910 def partition_sql(self, expression: exp.Partition) -> str: 1911 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1912 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1913 1914 def properties_sql(self, expression: exp.Properties) -> str: 1915 root_properties = [] 1916 with_properties = [] 1917 1918 for p in expression.expressions: 1919 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1920 if p_loc == exp.Properties.Location.POST_WITH: 1921 with_properties.append(p) 1922 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1923 root_properties.append(p) 1924 1925 root_props_ast = exp.Properties(expressions=root_properties) 1926 root_props_ast.parent = expression.parent 1927 1928 with_props_ast = exp.Properties(expressions=with_properties) 1929 with_props_ast.parent = expression.parent 1930 1931 root_props = self.root_properties(root_props_ast) 1932 with_props = self.with_properties(with_props_ast) 1933 1934 if root_props and with_props and not self.pretty: 1935 with_props = " " + with_props 1936 1937 return root_props + with_props 1938 1939 def root_properties(self, properties: exp.Properties) -> str: 1940 if properties.expressions: 1941 return self.expressions(properties, indent=False, sep=" ") 1942 return "" 1943 1944 def properties( 1945 self, 1946 properties: exp.Properties, 1947 prefix: str = "", 1948 sep: str = ", ", 1949 suffix: str = "", 1950 wrapped: bool = True, 1951 ) -> str: 1952 if properties.expressions: 1953 expressions = self.expressions(properties, sep=sep, indent=False) 1954 if expressions: 1955 expressions = self.wrap(expressions) if wrapped else expressions 1956 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1957 return "" 1958 1959 def with_properties(self, properties: exp.Properties) -> str: 1960 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1961 1962 def locate_properties(self, properties: exp.Properties) -> defaultdict: 1963 properties_locs = defaultdict(list) 1964 for p in properties.expressions: 1965 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1966 if p_loc != exp.Properties.Location.UNSUPPORTED: 1967 properties_locs[p_loc].append(p) 1968 else: 1969 self.unsupported(f"Unsupported property {p.key}") 1970 1971 return properties_locs 1972 1973 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1974 if isinstance(expression.this, exp.Dot): 1975 return self.sql(expression, "this") 1976 return f"'{expression.name}'" if string_key else expression.name 1977 1978 def property_sql(self, expression: exp.Property) -> str: 1979 property_cls = expression.__class__ 1980 if property_cls == exp.Property: 1981 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1982 1983 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1984 if not property_name: 1985 self.unsupported(f"Unsupported property {expression.key}") 1986 1987 return f"{property_name}={self.sql(expression, 'this')}" 1988 1989 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 1990 return f"UUID {self.sql(expression, 'this')}" 1991 1992 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1993 if self.SUPPORTS_CREATE_TABLE_LIKE: 1994 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1995 options = f" {options}" if options else "" 1996 1997 like = f"LIKE {self.sql(expression, 'this')}{options}" 1998 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1999 like = f"({like})" 2000 2001 return like 2002 2003 if expression.expressions: 2004 self.unsupported("Transpilation of LIKE property options is unsupported") 2005 2006 select = exp.select("*").from_(expression.this).limit(0) 2007 return f"AS {self.sql(select)}" 2008 2009 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2010 no = "NO " if expression.args.get("no") else "" 2011 protection = " PROTECTION" if expression.args.get("protection") else "" 2012 return f"{no}FALLBACK{protection}" 2013 2014 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2015 no = "NO " if expression.args.get("no") else "" 2016 local = expression.args.get("local") 2017 local = f"{local} " if local else "" 2018 dual = "DUAL " if expression.args.get("dual") else "" 2019 before = "BEFORE " if expression.args.get("before") else "" 2020 after = "AFTER " if expression.args.get("after") else "" 2021 return f"{no}{local}{dual}{before}{after}JOURNAL" 2022 2023 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2024 freespace = self.sql(expression, "this") 2025 percent = " PERCENT" if expression.args.get("percent") else "" 2026 return f"FREESPACE={freespace}{percent}" 2027 2028 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2029 if expression.args.get("default"): 2030 property = "DEFAULT" 2031 elif expression.args.get("on"): 2032 property = "ON" 2033 else: 2034 property = "OFF" 2035 return f"CHECKSUM={property}" 2036 2037 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2038 if expression.args.get("no"): 2039 return "NO MERGEBLOCKRATIO" 2040 if expression.args.get("default"): 2041 return "DEFAULT MERGEBLOCKRATIO" 2042 2043 percent = " PERCENT" if expression.args.get("percent") else "" 2044 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2045 2046 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2047 expressions = self.expressions(expression, flat=True) 2048 expressions = f"({expressions})" if expressions else "" 2049 return f"USING {self.sql(expression, 'this')}{expressions}" 2050 2051 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2052 default = expression.args.get("default") 2053 minimum = expression.args.get("minimum") 2054 maximum = expression.args.get("maximum") 2055 if default or minimum or maximum: 2056 if default: 2057 prop = "DEFAULT" 2058 elif minimum: 2059 prop = "MINIMUM" 2060 else: 2061 prop = "MAXIMUM" 2062 return f"{prop} DATABLOCKSIZE" 2063 units = expression.args.get("units") 2064 units = f" {units}" if units else "" 2065 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2066 2067 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2068 autotemp = expression.args.get("autotemp") 2069 always = expression.args.get("always") 2070 default = expression.args.get("default") 2071 manual = expression.args.get("manual") 2072 never = expression.args.get("never") 2073 2074 if autotemp is not None: 2075 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2076 elif always: 2077 prop = "ALWAYS" 2078 elif default: 2079 prop = "DEFAULT" 2080 elif manual: 2081 prop = "MANUAL" 2082 elif never: 2083 prop = "NEVER" 2084 return f"BLOCKCOMPRESSION={prop}" 2085 2086 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2087 no = expression.args.get("no") 2088 no = " NO" if no else "" 2089 concurrent = expression.args.get("concurrent") 2090 concurrent = " CONCURRENT" if concurrent else "" 2091 target = self.sql(expression, "target") 2092 target = f" {target}" if target else "" 2093 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2094 2095 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2096 if isinstance(expression.this, list): 2097 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2098 if expression.this: 2099 modulus = self.sql(expression, "this") 2100 remainder = self.sql(expression, "expression") 2101 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2102 2103 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2104 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2105 return f"FROM ({from_expressions}) TO ({to_expressions})" 2106 2107 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2108 this = self.sql(expression, "this") 2109 2110 for_values_or_default = expression.expression 2111 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2112 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2113 else: 2114 for_values_or_default = " DEFAULT" 2115 2116 return f"PARTITION OF {this}{for_values_or_default}" 2117 2118 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2119 kind = expression.args.get("kind") 2120 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2121 for_or_in = expression.args.get("for_or_in") 2122 for_or_in = f" {for_or_in}" if for_or_in else "" 2123 lock_type = expression.args.get("lock_type") 2124 override = " OVERRIDE" if expression.args.get("override") else "" 2125 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2126 2127 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2128 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2129 statistics = expression.args.get("statistics") 2130 statistics_sql = "" 2131 if statistics is not None: 2132 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2133 return f"{data_sql}{statistics_sql}" 2134 2135 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2136 this = self.sql(expression, "this") 2137 this = f"HISTORY_TABLE={this}" if this else "" 2138 data_consistency: str | None = self.sql(expression, "data_consistency") 2139 data_consistency = ( 2140 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2141 ) 2142 retention_period: str | None = self.sql(expression, "retention_period") 2143 retention_period = ( 2144 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2145 ) 2146 2147 if this: 2148 on_sql = self.func("ON", this, data_consistency, retention_period) 2149 else: 2150 on_sql = "ON" if expression.args.get("on") else "OFF" 2151 2152 sql = f"SYSTEM_VERSIONING={on_sql}" 2153 2154 return f"WITH({sql})" if expression.args.get("with_") else sql 2155 2156 def insert_sql(self, expression: exp.Insert) -> str: 2157 hint = self.sql(expression, "hint") 2158 overwrite = expression.args.get("overwrite") 2159 2160 if isinstance(expression.this, exp.Directory): 2161 this = " OVERWRITE" if overwrite else " INTO" 2162 else: 2163 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2164 2165 stored = self.sql(expression, "stored") 2166 stored = f" {stored}" if stored else "" 2167 alternative = expression.args.get("alternative") 2168 alternative = f" OR {alternative}" if alternative else "" 2169 ignore = " IGNORE" if expression.args.get("ignore") else "" 2170 is_function = expression.args.get("is_function") 2171 if is_function: 2172 this = f"{this} FUNCTION" 2173 this = f"{this} {self.sql(expression, 'this')}" 2174 2175 exists = " IF EXISTS" if expression.args.get("exists") else "" 2176 where = self.sql(expression, "where") 2177 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2178 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2179 on_conflict = self.sql(expression, "conflict") 2180 on_conflict = f" {on_conflict}" if on_conflict else "" 2181 by_name = " BY NAME" if expression.args.get("by_name") else "" 2182 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2183 returning = self.sql(expression, "returning") 2184 2185 if self.RETURNING_END: 2186 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2187 else: 2188 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2189 2190 partition_by = self.sql(expression, "partition") 2191 partition_by = f" {partition_by}" if partition_by else "" 2192 settings = self.sql(expression, "settings") 2193 settings = f" {settings}" if settings else "" 2194 2195 source = self.sql(expression, "source") 2196 source = f"TABLE {source}" if source else "" 2197 2198 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2199 return self.prepend_ctes(expression, sql) 2200 2201 def introducer_sql(self, expression: exp.Introducer) -> str: 2202 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2203 2204 def kill_sql(self, expression: exp.Kill) -> str: 2205 kind = self.sql(expression, "kind") 2206 kind = f" {kind}" if kind else "" 2207 this = self.sql(expression, "this") 2208 this = f" {this}" if this else "" 2209 return f"KILL{kind}{this}" 2210 2211 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2212 return expression.name 2213 2214 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2215 return expression.name 2216 2217 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2218 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2219 2220 constraint = self.sql(expression, "constraint") 2221 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2222 2223 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2224 if conflict_keys: 2225 conflict_keys = f"({conflict_keys})" 2226 2227 index_predicate = self.sql(expression, "index_predicate") 2228 conflict_keys = f"{conflict_keys}{index_predicate} " 2229 2230 action = self.sql(expression, "action") 2231 2232 expressions = self.expressions(expression, flat=True) 2233 if expressions: 2234 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2235 expressions = f" {set_keyword}{expressions}" 2236 2237 where = self.sql(expression, "where") 2238 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2239 2240 def returning_sql(self, expression: exp.Returning) -> str: 2241 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2242 2243 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2244 fields = self.sql(expression, "fields") 2245 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2246 escaped = self.sql(expression, "escaped") 2247 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2248 items = self.sql(expression, "collection_items") 2249 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2250 keys = self.sql(expression, "map_keys") 2251 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2252 lines = self.sql(expression, "lines") 2253 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2254 null = self.sql(expression, "null") 2255 null = f" NULL DEFINED AS {null}" if null else "" 2256 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2257 2258 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2259 return f"WITH ({self.expressions(expression, flat=True)})" 2260 2261 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2262 this = f"{self.sql(expression, 'this')} INDEX" 2263 target = self.sql(expression, "target") 2264 target = f" FOR {target}" if target else "" 2265 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2266 2267 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2268 this = self.sql(expression, "this") 2269 kind = self.sql(expression, "kind") 2270 expr = self.sql(expression, "expression") 2271 return f"{this} ({kind} => {expr})" 2272 2273 def table_parts(self, expression: exp.Table) -> str: 2274 return ".".join( 2275 self.sql(part) 2276 for part in ( 2277 expression.args.get("catalog"), 2278 expression.args.get("db"), 2279 expression.args.get("this"), 2280 ) 2281 if part is not None 2282 ) 2283 2284 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2285 table = self.table_parts(expression) 2286 only = "ONLY " if expression.args.get("only") else "" 2287 partition = self.sql(expression, "partition") 2288 partition = f" {partition}" if partition else "" 2289 version = self.sql(expression, "version") 2290 version = f" {version}" if version else "" 2291 alias = self.sql(expression, "alias") 2292 alias = f"{sep}{alias}" if alias else "" 2293 2294 sample = self.sql(expression, "sample") 2295 post_alias = "" 2296 pre_alias = "" 2297 2298 if self.dialect.ALIAS_POST_TABLESAMPLE: 2299 pre_alias = sample 2300 else: 2301 post_alias = sample 2302 2303 if self.dialect.ALIAS_POST_VERSION: 2304 pre_alias = f"{pre_alias}{version}" 2305 else: 2306 post_alias = f"{post_alias}{version}" 2307 2308 hints = self.expressions(expression, key="hints", sep=" ") 2309 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2310 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2311 joins = self.indent( 2312 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2313 ) 2314 laterals = self.expressions(expression, key="laterals", sep="") 2315 2316 file_format = self.sql(expression, "format") 2317 if file_format: 2318 pattern = self.sql(expression, "pattern") 2319 pattern = f", PATTERN => {pattern}" if pattern else "" 2320 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2321 2322 ordinality = expression.args.get("ordinality") or "" 2323 if ordinality: 2324 ordinality = f" WITH ORDINALITY{alias}" 2325 alias = "" 2326 2327 when = self.sql(expression, "when") 2328 if when: 2329 table = f"{table} {when}" 2330 2331 changes = self.sql(expression, "changes") 2332 changes = f" {changes}" if changes else "" 2333 2334 rows_from = self.expressions(expression, key="rows_from") 2335 if rows_from: 2336 table = f"ROWS FROM {self.wrap(rows_from)}" 2337 2338 indexed = expression.args.get("indexed") 2339 if indexed is not None: 2340 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2341 else: 2342 indexed = "" 2343 2344 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2345 2346 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2347 table = self.func("TABLE", expression.this) 2348 alias = self.sql(expression, "alias") 2349 alias = f" AS {alias}" if alias else "" 2350 sample = self.sql(expression, "sample") 2351 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2352 joins = self.indent( 2353 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2354 ) 2355 return f"{table}{alias}{pivots}{sample}{joins}" 2356 2357 def tablesample_sql( 2358 self, 2359 expression: exp.TableSample, 2360 tablesample_keyword: str | None = None, 2361 ) -> str: 2362 method = self.sql(expression, "method") 2363 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2364 numerator = self.sql(expression, "bucket_numerator") 2365 denominator = self.sql(expression, "bucket_denominator") 2366 field = self.sql(expression, "bucket_field") 2367 field = f" ON {field}" if field else "" 2368 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2369 seed = self.sql(expression, "seed") 2370 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2371 2372 size = self.sql(expression, "size") 2373 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2374 size = f"{size} ROWS" 2375 2376 percent = self.sql(expression, "percent") 2377 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2378 percent = f"{percent} PERCENT" 2379 2380 expr = f"{bucket}{percent}{size}" 2381 if self.TABLESAMPLE_REQUIRES_PARENS: 2382 expr = f"({expr})" 2383 2384 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2385 2386 def pivot_sql(self, expression: exp.Pivot) -> str: 2387 expressions = self.expressions(expression, flat=True) 2388 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2389 2390 group = self.sql(expression, "group") 2391 2392 if expression.this: 2393 this = self.sql(expression, "this") 2394 if not expressions: 2395 sql = f"UNPIVOT {this}" 2396 else: 2397 on = f"{self.seg('ON')} {expressions}" 2398 into = self.sql(expression, "into") 2399 into = f"{self.seg('INTO')} {into}" if into else "" 2400 using = self.expressions(expression, key="using", flat=True) 2401 using = f"{self.seg('USING')} {using}" if using else "" 2402 sql = f"{direction} {this}{on}{into}{using}{group}" 2403 return self.prepend_ctes(expression, sql) 2404 2405 alias = self.sql(expression, "alias") 2406 alias = f" AS {alias}" if alias else "" 2407 2408 fields = self.expressions( 2409 expression, 2410 "fields", 2411 sep=" ", 2412 dynamic=True, 2413 new_line=True, 2414 skip_first=True, 2415 skip_last=True, 2416 ) 2417 2418 include_nulls = expression.args.get("include_nulls") 2419 if include_nulls is not None: 2420 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2421 else: 2422 nulls = "" 2423 2424 default_on_null = self.sql(expression, "default_on_null") 2425 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2426 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2427 return self.prepend_ctes(expression, sql) 2428 2429 def version_sql(self, expression: exp.Version) -> str: 2430 this = f"FOR {expression.name}" 2431 kind = expression.text("kind") 2432 expr = self.sql(expression, "expression") 2433 return f"{this} {kind} {expr}" 2434 2435 def tuple_sql(self, expression: exp.Tuple) -> str: 2436 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2437 2438 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2439 """ 2440 Returns (join_sql, from_sql) for UPDATE statements. 2441 - join_sql: placed after UPDATE table, before SET 2442 - from_sql: placed after SET clause (standard position) 2443 Dialects like MySQL need to convert FROM to JOIN syntax. 2444 """ 2445 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2446 return ("", self.sql(expression, "from_")) 2447 2448 # Qualify unqualified columns in SET clause with the target table 2449 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2450 target_table = expression.this 2451 if isinstance(target_table, exp.Table): 2452 target_name = exp.to_identifier(target_table.alias_or_name) 2453 for eq in expression.expressions: 2454 col = eq.this 2455 if isinstance(col, exp.Column) and not col.table: 2456 col.set("table", target_name) 2457 2458 table = from_expr.this 2459 if nested_joins := table.args.get("joins", []): 2460 table.set("joins", None) 2461 2462 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2463 for nested in nested_joins: 2464 if not nested.args.get("on") and not nested.args.get("using"): 2465 nested.set("on", exp.true()) 2466 join_sql += self.sql(nested) 2467 2468 return (join_sql, "") 2469 2470 def update_sql(self, expression: exp.Update) -> str: 2471 hint = self.sql(expression, "hint") 2472 this = self.sql(expression, "this") 2473 join_sql, from_sql = self._update_from_joins_sql(expression) 2474 set_sql = self.expressions(expression, flat=True) 2475 where_sql = self.sql(expression, "where") 2476 returning = self.sql(expression, "returning") 2477 order = self.sql(expression, "order") 2478 limit = self.sql(expression, "limit") 2479 if self.RETURNING_END: 2480 expression_sql = f"{from_sql}{where_sql}{returning}" 2481 else: 2482 expression_sql = f"{returning}{from_sql}{where_sql}" 2483 options = self.expressions(expression, key="options") 2484 options = f" OPTION({options})" if options else "" 2485 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2486 return self.prepend_ctes(expression, sql) 2487 2488 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2489 values_as_table = values_as_table and self.VALUES_AS_TABLE 2490 2491 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2492 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2493 args = self.expressions(expression) 2494 alias = self.sql(expression, "alias") 2495 values = f"VALUES{self.seg('')}{args}" 2496 values = ( 2497 f"({values})" 2498 if self.WRAP_DERIVED_VALUES 2499 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2500 else values 2501 ) 2502 values = self.query_modifiers(expression, values) 2503 return f"{values} AS {alias}" if alias else values 2504 2505 # Converts `VALUES...` expression into a series of select unions. 2506 alias_node = expression.args.get("alias") 2507 column_names = alias_node and alias_node.columns 2508 2509 selects: list[exp.Query] = [] 2510 2511 for i, tup in enumerate(expression.expressions): 2512 row = tup.expressions 2513 2514 if i == 0 and column_names: 2515 row = [ 2516 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2517 ] 2518 2519 selects.append(exp.Select(expressions=row)) 2520 2521 if self.pretty: 2522 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2523 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2524 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2525 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2526 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2527 2528 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2529 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2530 return f"({unions}){alias}" 2531 2532 def var_sql(self, expression: exp.Var) -> str: 2533 return self.sql(expression, "this") 2534 2535 @unsupported_args("expressions") 2536 def into_sql(self, expression: exp.Into) -> str: 2537 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2538 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2539 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2540 2541 def from_sql(self, expression: exp.From) -> str: 2542 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2543 2544 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2545 grouping_sets = self.expressions(expression, indent=False) 2546 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2547 2548 def rollup_sql(self, expression: exp.Rollup) -> str: 2549 expressions = self.expressions(expression, indent=False) 2550 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2551 2552 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2553 this = self.sql(expression, "this") 2554 2555 columns = self.expressions(expression, flat=True) 2556 2557 from_sql = self.sql(expression, "from_index") 2558 from_sql = f" FROM {from_sql}" if from_sql else "" 2559 2560 properties = expression.args.get("properties") 2561 properties_sql = ( 2562 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2563 ) 2564 2565 return f"{this}({columns}){from_sql}{properties_sql}" 2566 2567 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2568 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2569 2570 def cube_sql(self, expression: exp.Cube) -> str: 2571 expressions = self.expressions(expression, indent=False) 2572 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2573 2574 def group_sql(self, expression: exp.Group) -> str: 2575 group_by_all = expression.args.get("all") 2576 if group_by_all is True: 2577 modifier = " ALL" 2578 elif group_by_all is False: 2579 modifier = " DISTINCT" 2580 else: 2581 modifier = "" 2582 2583 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2584 2585 grouping_sets = self.expressions(expression, key="grouping_sets") 2586 cube = self.expressions(expression, key="cube") 2587 rollup = self.expressions(expression, key="rollup") 2588 2589 groupings = csv( 2590 self.seg(grouping_sets) if grouping_sets else "", 2591 self.seg(cube) if cube else "", 2592 self.seg(rollup) if rollup else "", 2593 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2594 sep=self.GROUPINGS_SEP, 2595 ) 2596 2597 if ( 2598 expression.expressions 2599 and groupings 2600 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2601 ): 2602 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2603 2604 return f"{group_by}{groupings}" 2605 2606 def having_sql(self, expression: exp.Having) -> str: 2607 this = self.indent(self.sql(expression, "this")) 2608 return f"{self.seg('HAVING')}{self.sep()}{this}" 2609 2610 def connect_sql(self, expression: exp.Connect) -> str: 2611 start = self.sql(expression, "start") 2612 start = self.seg(f"START WITH {start}") if start else "" 2613 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2614 connect = self.sql(expression, "connect") 2615 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2616 return start + connect 2617 2618 def prior_sql(self, expression: exp.Prior) -> str: 2619 return f"PRIOR {self.sql(expression, 'this')}" 2620 2621 def join_sql(self, expression: exp.Join) -> str: 2622 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2623 side = None 2624 else: 2625 side = expression.side 2626 2627 op_sql = " ".join( 2628 op 2629 for op in ( 2630 expression.method, 2631 "GLOBAL" if expression.args.get("global_") else None, 2632 side, 2633 expression.kind, 2634 expression.hint if self.JOIN_HINTS else None, 2635 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2636 ) 2637 if op 2638 ) 2639 match_cond = self.sql(expression, "match_condition") 2640 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2641 on_sql = self.sql(expression, "on") 2642 using = expression.args.get("using") 2643 2644 if not on_sql and using: 2645 on_sql = csv(*(self.sql(column) for column in using)) 2646 2647 this = expression.this 2648 this_sql = self.sql(this) 2649 2650 exprs = self.expressions(expression) 2651 if exprs: 2652 this_sql = f"{this_sql},{self.seg(exprs)}" 2653 2654 if on_sql: 2655 on_sql = self.indent(on_sql, skip_first=True) 2656 space = self.seg(" " * self.pad) if self.pretty else " " 2657 if using: 2658 on_sql = f"{space}USING ({on_sql})" 2659 else: 2660 on_sql = f"{space}ON {on_sql}" 2661 elif not op_sql: 2662 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2663 return f" {this_sql}" 2664 2665 return f", {this_sql}" 2666 2667 if op_sql != "STRAIGHT_JOIN": 2668 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2669 2670 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2671 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2672 2673 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2674 args = self.expressions(expression, flat=True) 2675 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2676 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2677 2678 def lateral_op(self, expression: exp.Lateral) -> str: 2679 cross_apply = expression.args.get("cross_apply") 2680 2681 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2682 if cross_apply is True: 2683 op = "INNER JOIN " 2684 elif cross_apply is False: 2685 op = "LEFT JOIN " 2686 else: 2687 op = "" 2688 2689 return f"{op}LATERAL" 2690 2691 def lateral_sql(self, expression: exp.Lateral) -> str: 2692 this = self.sql(expression, "this") 2693 2694 if expression.args.get("view"): 2695 alias = expression.args["alias"] 2696 columns = self.expressions(alias, key="columns", flat=True) 2697 table = f" {alias.name}" if alias.name else "" 2698 columns = f" AS {columns}" if columns else "" 2699 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2700 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2701 2702 alias = self.sql(expression, "alias") 2703 alias = f" AS {alias}" if alias else "" 2704 2705 ordinality = expression.args.get("ordinality") or "" 2706 if ordinality: 2707 ordinality = f" WITH ORDINALITY{alias}" 2708 alias = "" 2709 2710 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2711 2712 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2713 this = self.sql(expression, "this") 2714 2715 args = [ 2716 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2717 for e in (expression.args.get(k) for k in ("offset", "expression")) 2718 if e 2719 ] 2720 2721 args_sql = ", ".join(self.sql(e) for e in args) 2722 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2723 expressions = self.expressions(expression, flat=True) 2724 limit_options = self.sql(expression, "limit_options") 2725 expressions = f" BY {expressions}" if expressions else "" 2726 2727 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2728 2729 def offset_sql(self, expression: exp.Offset) -> str: 2730 this = self.sql(expression, "this") 2731 value = expression.expression 2732 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2733 expressions = self.expressions(expression, flat=True) 2734 expressions = f" BY {expressions}" if expressions else "" 2735 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2736 2737 def setitem_sql(self, expression: exp.SetItem) -> str: 2738 kind = self.sql(expression, "kind") 2739 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2740 kind = "" 2741 else: 2742 kind = f"{kind} " if kind else "" 2743 this = self.sql(expression, "this") 2744 expressions = self.expressions(expression) 2745 collate = self.sql(expression, "collate") 2746 collate = f" COLLATE {collate}" if collate else "" 2747 global_ = "GLOBAL " if expression.args.get("global_") else "" 2748 return f"{global_}{kind}{this}{expressions}{collate}" 2749 2750 def set_sql(self, expression: exp.Set) -> str: 2751 expressions = f" {self.expressions(expression, flat=True)}" 2752 tag = " TAG" if expression.args.get("tag") else "" 2753 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2754 2755 def queryband_sql(self, expression: exp.QueryBand) -> str: 2756 this = self.sql(expression, "this") 2757 update = " UPDATE" if expression.args.get("update") else "" 2758 scope = self.sql(expression, "scope") 2759 scope = f" FOR {scope}" if scope else "" 2760 2761 return f"QUERY_BAND = {this}{update}{scope}" 2762 2763 def pragma_sql(self, expression: exp.Pragma) -> str: 2764 return f"PRAGMA {self.sql(expression, 'this')}" 2765 2766 def lock_sql(self, expression: exp.Lock) -> str: 2767 if not self.LOCKING_READS_SUPPORTED: 2768 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2769 return "" 2770 2771 update = expression.args["update"] 2772 key = expression.args.get("key") 2773 if update: 2774 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2775 else: 2776 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2777 expressions = self.expressions(expression, flat=True) 2778 expressions = f" OF {expressions}" if expressions else "" 2779 wait = expression.args.get("wait") 2780 2781 if wait is not None: 2782 if isinstance(wait, exp.Literal): 2783 wait = f" WAIT {self.sql(wait)}" 2784 else: 2785 wait = " NOWAIT" if wait else " SKIP LOCKED" 2786 2787 return f"{lock_type}{expressions}{wait or ''}" 2788 2789 def literal_sql(self, expression: exp.Literal) -> str: 2790 text = expression.this or "" 2791 if expression.is_string: 2792 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2793 return text 2794 2795 def escape_str( 2796 self, 2797 text: str, 2798 escape_backslash: bool = True, 2799 delimiter: str | None = None, 2800 escaped_delimiter: str | None = None, 2801 is_byte_string: bool = False, 2802 ) -> str: 2803 if is_byte_string: 2804 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2805 else: 2806 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2807 2808 if supports_escape_sequences: 2809 text = "".join( 2810 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2811 for ch in text 2812 ) 2813 2814 delimiter = delimiter or self.dialect.QUOTE_END 2815 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2816 2817 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2818 2819 def loaddata_sql(self, expression: exp.LoadData) -> str: 2820 is_overwrite = expression.args.get("overwrite") 2821 overwrite = " OVERWRITE" if is_overwrite else "" 2822 this = self.sql(expression, "this") 2823 2824 files = expression.args.get("files") 2825 if files: 2826 files_sql = self.expressions(files, flat=True) 2827 files_sql = f"FILES{self.wrap(files_sql)}" 2828 this = f" {this}" if is_overwrite else f" INTO TABLE {this}" 2829 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2830 2831 local = " LOCAL" if expression.args.get("local") else "" 2832 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2833 this = f" INTO TABLE {this}" 2834 partition = self.sql(expression, "partition") 2835 partition = f" {partition}" if partition else "" 2836 input_format = self.sql(expression, "input_format") 2837 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2838 serde = self.sql(expression, "serde") 2839 serde = f" SERDE {serde}" if serde else "" 2840 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2841 2842 def null_sql(self, *_) -> str: 2843 return "NULL" 2844 2845 def boolean_sql(self, expression: exp.Boolean) -> str: 2846 return "TRUE" if expression.this else "FALSE" 2847 2848 def booland_sql(self, expression: exp.Booland) -> str: 2849 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2850 2851 def boolor_sql(self, expression: exp.Boolor) -> str: 2852 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2853 2854 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2855 this = self.sql(expression, "this") 2856 this = f"{this} " if this else this 2857 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2858 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 2859 2860 def withfill_sql(self, expression: exp.WithFill) -> str: 2861 from_sql = self.sql(expression, "from_") 2862 from_sql = f" FROM {from_sql}" if from_sql else "" 2863 to_sql = self.sql(expression, "to") 2864 to_sql = f" TO {to_sql}" if to_sql else "" 2865 step_sql = self.sql(expression, "step") 2866 step_sql = f" STEP {step_sql}" if step_sql else "" 2867 interpolated_values = [ 2868 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2869 if isinstance(e, exp.Alias) 2870 else self.sql(e, "this") 2871 for e in expression.args.get("interpolate") or [] 2872 ] 2873 interpolate = ( 2874 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2875 ) 2876 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2877 2878 def cluster_sql(self, expression: exp.Cluster) -> str: 2879 return self.op_expressions("CLUSTER BY", expression) 2880 2881 def distribute_sql(self, expression: exp.Distribute) -> str: 2882 return self.op_expressions("DISTRIBUTE BY", expression) 2883 2884 def sort_sql(self, expression: exp.Sort) -> str: 2885 return self.op_expressions("SORT BY", expression) 2886 2887 def ordered_sql(self, expression: exp.Ordered) -> str: 2888 desc = expression.args.get("desc") 2889 asc = not desc 2890 2891 nulls_first = expression.args.get("nulls_first") 2892 nulls_last = not nulls_first 2893 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2894 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2895 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2896 2897 this = self.sql(expression, "this") 2898 2899 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2900 nulls_sort_change = "" 2901 if nulls_first and ( 2902 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2903 ): 2904 nulls_sort_change = " NULLS FIRST" 2905 elif ( 2906 nulls_last 2907 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2908 and not nulls_are_last 2909 ): 2910 nulls_sort_change = " NULLS LAST" 2911 2912 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2913 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2914 window = expression.find_ancestor(exp.Window, exp.Select) 2915 2916 if isinstance(window, exp.Window): 2917 window_this = window.this 2918 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 2919 window_this = window_this.this 2920 spec = window.args.get("spec") 2921 else: 2922 window_this = None 2923 spec = None 2924 2925 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 2926 # without a spec or with a ROWS spec, but not with RANGE 2927 if not ( 2928 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 2929 and (not spec or spec.text("kind").upper() == "ROWS") 2930 ): 2931 if window_this and spec: 2932 self.unsupported( 2933 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 2934 ) 2935 nulls_sort_change = "" 2936 elif self.NULL_ORDERING_SUPPORTED is False and ( 2937 (asc and nulls_sort_change == " NULLS LAST") 2938 or (desc and nulls_sort_change == " NULLS FIRST") 2939 ): 2940 # BigQuery does not allow these ordering/nulls combinations when used under 2941 # an aggregation func or under a window containing one 2942 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2943 2944 if isinstance(ancestor, exp.Window): 2945 ancestor = ancestor.this 2946 if isinstance(ancestor, exp.AggFunc): 2947 self.unsupported( 2948 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 2949 ) 2950 nulls_sort_change = "" 2951 elif self.NULL_ORDERING_SUPPORTED is None: 2952 if expression.this.is_int: 2953 self.unsupported( 2954 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2955 ) 2956 elif not isinstance(expression.this, exp.Rand): 2957 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2958 this = ( 2959 f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2960 ) 2961 nulls_sort_change = "" 2962 2963 with_fill = self.sql(expression, "with_fill") 2964 with_fill = f" {with_fill}" if with_fill else "" 2965 2966 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2967 2968 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2969 window_frame = self.sql(expression, "window_frame") 2970 window_frame = f"{window_frame} " if window_frame else "" 2971 2972 this = self.sql(expression, "this") 2973 2974 return f"{window_frame}{this}" 2975 2976 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2977 partition = self.partition_by_sql(expression) 2978 order = self.sql(expression, "order") 2979 measures = self.expressions(expression, key="measures") 2980 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2981 rows = self.sql(expression, "rows") 2982 rows = self.seg(rows) if rows else "" 2983 after = self.sql(expression, "after") 2984 after = self.seg(after) if after else "" 2985 pattern = self.sql(expression, "pattern") 2986 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2987 definition_sqls = [ 2988 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2989 for definition in expression.args.get("define", []) 2990 ] 2991 definitions = self.expressions(sqls=definition_sqls) 2992 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2993 body = "".join( 2994 ( 2995 partition, 2996 order, 2997 measures, 2998 rows, 2999 after, 3000 pattern, 3001 define, 3002 ) 3003 ) 3004 alias = self.sql(expression, "alias") 3005 alias = f" {alias}" if alias else "" 3006 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3007 3008 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3009 limit = expression.args.get("limit") 3010 3011 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3012 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3013 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3014 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3015 3016 return csv( 3017 *sqls, 3018 *[self.sql(join) for join in expression.args.get("joins") or []], 3019 self.sql(expression, "match"), 3020 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3021 self.sql(expression, "prewhere"), 3022 self.sql(expression, "where"), 3023 self.sql(expression, "connect"), 3024 self.sql(expression, "group"), 3025 self.sql(expression, "having"), 3026 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3027 self.sql(expression, "order"), 3028 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3029 *self.after_limit_modifiers(expression), 3030 self.options_modifier(expression), 3031 self.for_modifiers(expression), 3032 sep="", 3033 ) 3034 3035 def options_modifier(self, expression: exp.Expr) -> str: 3036 options = self.expressions(expression, key="options") 3037 return f" {options}" if options else "" 3038 3039 def for_modifiers(self, expression: exp.Expr) -> str: 3040 for_modifiers = self.expressions(expression, key="for_") 3041 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 3042 3043 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3044 self.unsupported("Unsupported query option.") 3045 return "" 3046 3047 def offset_limit_modifiers( 3048 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3049 ) -> list[str]: 3050 return [ 3051 self.sql(expression, "offset") if fetch else self.sql(limit), 3052 self.sql(limit) if fetch else self.sql(expression, "offset"), 3053 ] 3054 3055 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3056 locks = self.expressions(expression, key="locks", sep=" ") 3057 locks = f" {locks}" if locks else "" 3058 return [locks, self.sql(expression, "sample")] 3059 3060 def select_sql(self, expression: exp.Select) -> str: 3061 into = expression.args.get("into") 3062 if not self.SUPPORTS_SELECT_INTO and into: 3063 into.pop() 3064 3065 hint = self.sql(expression, "hint") 3066 distinct = self.sql(expression, "distinct") 3067 distinct = f" {distinct}" if distinct else "" 3068 kind = self.sql(expression, "kind") 3069 3070 limit = expression.args.get("limit") 3071 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3072 top = self.limit_sql(limit, top=True) 3073 limit.pop() 3074 else: 3075 top = "" 3076 3077 expressions = self.expressions(expression) 3078 3079 if kind: 3080 if kind in self.SELECT_KINDS: 3081 kind = f" AS {kind}" 3082 else: 3083 if kind == "STRUCT": 3084 expressions = self.expressions( 3085 sqls=[ 3086 self.sql( 3087 exp.Struct( 3088 expressions=[ 3089 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3090 if isinstance(e, exp.Alias) 3091 else e 3092 for e in expression.expressions 3093 ] 3094 ) 3095 ) 3096 ] 3097 ) 3098 kind = "" 3099 3100 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3101 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3102 3103 exclude = expression.args.get("exclude") 3104 3105 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3106 exclude_sql = self.expressions(sqls=exclude, flat=True) 3107 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3108 3109 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3110 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3111 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3112 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3113 sql = self.query_modifiers( 3114 expression, 3115 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3116 self.sql(expression, "into", comment=False), 3117 self.sql(expression, "from_", comment=False), 3118 ) 3119 3120 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3121 if expression.args.get("with_"): 3122 sql = self.maybe_comment(sql, expression) 3123 expression.pop_comments() 3124 3125 sql = self.prepend_ctes(expression, sql) 3126 3127 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3128 expression.set("exclude", None) 3129 subquery = expression.subquery(copy=False) 3130 star = exp.Star(except_=exclude) 3131 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3132 3133 if not self.SUPPORTS_SELECT_INTO and into: 3134 if into.args.get("temporary"): 3135 table_kind = " TEMPORARY" 3136 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3137 table_kind = " UNLOGGED" 3138 else: 3139 table_kind = "" 3140 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3141 3142 return sql 3143 3144 def schema_sql(self, expression: exp.Schema) -> str: 3145 this = self.sql(expression, "this") 3146 sql = self.schema_columns_sql(expression) 3147 return f"{this} {sql}" if this and sql else this or sql 3148 3149 def schema_columns_sql(self, expression: exp.Expr) -> str: 3150 if expression.expressions: 3151 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3152 return "" 3153 3154 def star_sql(self, expression: exp.Star) -> str: 3155 except_ = self.expressions(expression, key="except_", flat=True) 3156 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3157 replace = self.expressions(expression, key="replace", flat=True) 3158 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3159 rename = self.expressions(expression, key="rename", flat=True) 3160 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3161 return f"*{except_}{replace}{rename}" 3162 3163 def parameter_sql(self, expression: exp.Parameter) -> str: 3164 this = self.sql(expression, "this") 3165 return f"{self.PARAMETER_TOKEN}{this}" 3166 3167 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3168 this = self.sql(expression, "this") 3169 kind = expression.text("kind") 3170 if kind: 3171 kind = f"{kind}." 3172 return f"@@{kind}{this}" 3173 3174 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3175 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3176 3177 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3178 alias = self.sql(expression, "alias") 3179 alias = f"{sep}{alias}" if alias else "" 3180 sample = self.sql(expression, "sample") 3181 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3182 alias = f"{sample}{alias}" 3183 3184 # Set to None so it's not generated again by self.query_modifiers() 3185 expression.set("sample", None) 3186 3187 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3188 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3189 return self.prepend_ctes(expression, sql) 3190 3191 def qualify_sql(self, expression: exp.Qualify) -> str: 3192 this = self.indent(self.sql(expression, "this")) 3193 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3194 3195 def unnest_sql(self, expression: exp.Unnest) -> str: 3196 args = self.expressions(expression, flat=True) 3197 3198 alias = expression.args.get("alias") 3199 offset = expression.args.get("offset") 3200 3201 if self.UNNEST_WITH_ORDINALITY: 3202 if alias and isinstance(offset, exp.Expr): 3203 alias.append("columns", offset) 3204 3205 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3206 columns = alias.columns 3207 alias = self.sql(columns[0]) if columns else "" 3208 else: 3209 alias = self.sql(alias) 3210 3211 alias = f" AS {alias}" if alias else alias 3212 if self.UNNEST_WITH_ORDINALITY: 3213 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3214 else: 3215 if isinstance(offset, exp.Expr): 3216 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3217 elif offset: 3218 suffix = f"{alias} WITH OFFSET" 3219 else: 3220 suffix = alias 3221 3222 return f"UNNEST({args}){suffix}" 3223 3224 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3225 return "" 3226 3227 def where_sql(self, expression: exp.Where) -> str: 3228 this = self.indent(self.sql(expression, "this")) 3229 return f"{self.seg('WHERE')}{self.sep()}{this}" 3230 3231 def window_sql(self, expression: exp.Window) -> str: 3232 this = self.sql(expression, "this") 3233 partition = self.partition_by_sql(expression) 3234 order = expression.args.get("order") 3235 order = self.order_sql(order, flat=True) if order else "" 3236 spec = self.sql(expression, "spec") 3237 alias = self.sql(expression, "alias") 3238 over = self.sql(expression, "over") or "OVER" 3239 3240 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3241 3242 first = expression.args.get("first") 3243 if first is None: 3244 first = "" 3245 else: 3246 first = "FIRST" if first else "LAST" 3247 3248 if not partition and not order and not spec and alias: 3249 return f"{this} {alias}" 3250 3251 args = self.format_args( 3252 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3253 ) 3254 return f"{this} ({args})" 3255 3256 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3257 partition = self.expressions(expression, key="partition_by", flat=True) 3258 return f"PARTITION BY {partition}" if partition else "" 3259 3260 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3261 kind = self.sql(expression, "kind") 3262 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3263 end = ( 3264 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3265 or "CURRENT ROW" 3266 ) 3267 3268 window_spec = f"{kind} BETWEEN {start} AND {end}" 3269 3270 exclude = self.sql(expression, "exclude") 3271 if exclude: 3272 if self.SUPPORTS_WINDOW_EXCLUDE: 3273 window_spec += f" EXCLUDE {exclude}" 3274 else: 3275 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3276 3277 return window_spec 3278 3279 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3280 this = self.sql(expression, "this") 3281 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3282 return f"{this} WITHIN GROUP ({expression_sql})" 3283 3284 def between_sql(self, expression: exp.Between) -> str: 3285 this = self.sql(expression, "this") 3286 low = self.sql(expression, "low") 3287 high = self.sql(expression, "high") 3288 symmetric = expression.args.get("symmetric") 3289 3290 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3291 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3292 3293 flag = ( 3294 " SYMMETRIC" 3295 if symmetric 3296 else " ASYMMETRIC" 3297 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3298 else "" # silently drop ASYMMETRIC – semantics identical 3299 ) 3300 return f"{this} BETWEEN{flag} {low} AND {high}" 3301 3302 def bracket_offset_expressions( 3303 self, expression: exp.Bracket, index_offset: int | None = None 3304 ) -> list[exp.Expr]: 3305 if expression.args.get("json_access"): 3306 return expression.expressions 3307 3308 return apply_index_offset( 3309 expression.this, 3310 expression.expressions, 3311 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3312 dialect=self.dialect, 3313 ) 3314 3315 def bracket_sql(self, expression: exp.Bracket) -> str: 3316 expressions = self.bracket_offset_expressions(expression) 3317 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3318 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3319 3320 def all_sql(self, expression: exp.All) -> str: 3321 this = self.sql(expression, "this") 3322 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3323 this = self.wrap(this) 3324 return f"ALL {this}" 3325 3326 def any_sql(self, expression: exp.Any) -> str: 3327 this = self.sql(expression, "this") 3328 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3329 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3330 this = self.wrap(this) 3331 return f"ANY{this}" 3332 return f"ANY {this}" 3333 3334 def exists_sql(self, expression: exp.Exists) -> str: 3335 return f"EXISTS{self.wrap(expression)}" 3336 3337 def case_sql(self, expression: exp.Case) -> str: 3338 this = self.sql(expression, "this") 3339 statements = [f"CASE {this}" if this else "CASE"] 3340 3341 for e in expression.args["ifs"]: 3342 statements.append(f"WHEN {self.sql(e, 'this')}") 3343 statements.append(f"THEN {self.sql(e, 'true')}") 3344 3345 default = self.sql(expression, "default") 3346 3347 if default: 3348 statements.append(f"ELSE {default}") 3349 3350 statements.append("END") 3351 3352 if self.pretty and self.too_wide(statements): 3353 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3354 3355 return " ".join(statements) 3356 3357 def constraint_sql(self, expression: exp.Constraint) -> str: 3358 this = self.sql(expression, "this") 3359 expressions = self.expressions(expression, flat=True) 3360 return f"CONSTRAINT {this} {expressions}" 3361 3362 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3363 order = expression.args.get("order") 3364 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3365 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3366 3367 def extract_sql(self, expression: exp.Extract) -> str: 3368 import sqlglot.dialects.dialect 3369 3370 this = ( 3371 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3372 if self.NORMALIZE_EXTRACT_DATE_PARTS 3373 else expression.this 3374 ) 3375 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3376 expression_sql = self.sql(expression, "expression") 3377 3378 return f"EXTRACT({this_sql} FROM {expression_sql})" 3379 3380 def trim_sql(self, expression: exp.Trim) -> str: 3381 trim_type = self.sql(expression, "position") 3382 3383 if trim_type == "LEADING": 3384 func_name = "LTRIM" 3385 elif trim_type == "TRAILING": 3386 func_name = "RTRIM" 3387 else: 3388 func_name = "TRIM" 3389 3390 return self.func(func_name, expression.this, expression.expression) 3391 3392 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3393 args = expression.expressions 3394 if isinstance(expression, exp.ConcatWs): 3395 args = args[1:] # Skip the delimiter 3396 3397 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3398 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3399 3400 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3401 3402 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3403 if not e.type: 3404 import sqlglot.optimizer.annotate_types 3405 3406 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3407 3408 if e.is_string or e.is_type(exp.DType.ARRAY): 3409 return e 3410 3411 return exp.func("coalesce", e, exp.Literal.string("")) 3412 3413 args = [_wrap_with_coalesce(e) for e in args] 3414 3415 return args 3416 3417 def concat_sql(self, expression: exp.Concat) -> str: 3418 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3419 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3420 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3421 # instead of coalescing them to empty string. 3422 import sqlglot.dialects.dialect 3423 3424 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3425 3426 expressions = self.convert_concat_args(expression) 3427 3428 # Some dialects don't allow a single-argument CONCAT call 3429 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3430 return self.sql(expressions[0]) 3431 3432 return self.func("CONCAT", *expressions) 3433 3434 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3435 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3436 # Dialect's CONCAT_WS function coalesces NULLs to empty strings, but the expression does not. 3437 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3438 all_args = expression.expressions 3439 expression.set("coalesce", True) 3440 return self.sql( 3441 exp.case() 3442 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3443 .else_(expression) 3444 ) 3445 3446 return self.func( 3447 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3448 ) 3449 3450 def check_sql(self, expression: exp.Check) -> str: 3451 this = self.sql(expression, key="this") 3452 return f"CHECK ({this})" 3453 3454 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3455 expressions = self.expressions(expression, flat=True) 3456 expressions = f" ({expressions})" if expressions else "" 3457 reference = self.sql(expression, "reference") 3458 reference = f" {reference}" if reference else "" 3459 delete = self.sql(expression, "delete") 3460 delete = f" ON DELETE {delete}" if delete else "" 3461 update = self.sql(expression, "update") 3462 update = f" ON UPDATE {update}" if update else "" 3463 options = self.expressions(expression, key="options", flat=True, sep=" ") 3464 options = f" {options}" if options else "" 3465 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3466 3467 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3468 this = self.sql(expression, "this") 3469 this = f" {this}" if this else "" 3470 expressions = self.expressions(expression, flat=True) 3471 include = self.sql(expression, "include") 3472 options = self.expressions(expression, key="options", flat=True, sep=" ") 3473 options = f" {options}" if options else "" 3474 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3475 3476 def if_sql(self, expression: exp.If) -> str: 3477 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3478 3479 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3480 if self.MATCH_AGAINST_TABLE_PREFIX: 3481 expressions = [] 3482 for expr in expression.expressions: 3483 if isinstance(expr, exp.Table): 3484 expressions.append(f"TABLE {self.sql(expr)}") 3485 else: 3486 expressions.append(expr) 3487 else: 3488 expressions = expression.expressions 3489 3490 modifier = expression.args.get("modifier") 3491 modifier = f" {modifier}" if modifier else "" 3492 return ( 3493 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3494 ) 3495 3496 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3497 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3498 3499 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3500 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3501 3502 if expression.args.get("escape"): 3503 path = self.escape_str(path) 3504 3505 if self.QUOTE_JSON_PATH: 3506 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3507 3508 return path 3509 3510 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3511 if isinstance(expression, exp.JSONPathPart): 3512 transform = self.TRANSFORMS.get(expression.__class__) 3513 if not callable(transform): 3514 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3515 return "" 3516 3517 return transform(self, expression) 3518 3519 if isinstance(expression, int): 3520 return str(expression) 3521 3522 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3523 escaped = expression.replace("'", "\\'") 3524 escaped = f"\\'{expression}\\'" 3525 else: 3526 escaped = expression.replace('"', '\\"') 3527 escaped = f'"{escaped}"' 3528 3529 return escaped 3530 3531 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3532 return f"{self.sql(expression, 'this')} FORMAT JSON" 3533 3534 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3535 # Output the Teradata column FORMAT override. 3536 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3537 this = self.sql(expression, "this") 3538 fmt = self.sql(expression, "format") 3539 return f"{this} (FORMAT {fmt})" 3540 3541 def _jsonobject_sql( 3542 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3543 ) -> str: 3544 null_handling = expression.args.get("null_handling") 3545 null_handling = f" {null_handling}" if null_handling else "" 3546 3547 unique_keys = expression.args.get("unique_keys") 3548 if unique_keys is not None: 3549 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3550 else: 3551 unique_keys = "" 3552 3553 return_type = self.sql(expression, "return_type") 3554 return_type = f" RETURNING {return_type}" if return_type else "" 3555 encoding = self.sql(expression, "encoding") 3556 encoding = f" ENCODING {encoding}" if encoding else "" 3557 3558 if not name: 3559 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3560 3561 return self.func( 3562 name, 3563 *expression.expressions, 3564 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3565 ) 3566 3567 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3568 null_handling = expression.args.get("null_handling") 3569 null_handling = f" {null_handling}" if null_handling else "" 3570 return_type = self.sql(expression, "return_type") 3571 return_type = f" RETURNING {return_type}" if return_type else "" 3572 strict = " STRICT" if expression.args.get("strict") else "" 3573 return self.func( 3574 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3575 ) 3576 3577 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3578 this = self.sql(expression, "this") 3579 order = self.sql(expression, "order") 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_ARRAYAGG", 3587 this, 3588 suffix=f"{order}{null_handling}{return_type}{strict})", 3589 ) 3590 3591 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3592 path = self.sql(expression, "path") 3593 path = f" PATH {path}" if path else "" 3594 nested_schema = self.sql(expression, "nested_schema") 3595 3596 if nested_schema: 3597 return f"NESTED{path} {nested_schema}" 3598 3599 this = self.sql(expression, "this") 3600 kind = self.sql(expression, "kind") 3601 kind = f" {kind}" if kind else "" 3602 3603 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3604 return f"{this}{kind}{path}{ordinality}" 3605 3606 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3607 return self.func("COLUMNS", *expression.expressions) 3608 3609 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3610 this = self.sql(expression, "this") 3611 path = self.sql(expression, "path") 3612 path = f", {path}" if path else "" 3613 error_handling = expression.args.get("error_handling") 3614 error_handling = f" {error_handling}" if error_handling else "" 3615 empty_handling = expression.args.get("empty_handling") 3616 empty_handling = f" {empty_handling}" if empty_handling else "" 3617 schema = self.sql(expression, "schema") 3618 return self.func( 3619 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3620 ) 3621 3622 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3623 this = self.sql(expression, "this") 3624 kind = self.sql(expression, "kind") 3625 path = self.sql(expression, "path") 3626 path = f" {path}" if path else "" 3627 as_json = " AS JSON" if expression.args.get("as_json") else "" 3628 return f"{this} {kind}{path}{as_json}" 3629 3630 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3631 this = self.sql(expression, "this") 3632 path = self.sql(expression, "path") 3633 path = f", {path}" if path else "" 3634 expressions = self.expressions(expression) 3635 with_ = ( 3636 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3637 if expressions 3638 else "" 3639 ) 3640 return f"OPENJSON({this}{path}){with_}" 3641 3642 def in_sql(self, expression: exp.In) -> str: 3643 query = expression.args.get("query") 3644 unnest = expression.args.get("unnest") 3645 field = expression.args.get("field") 3646 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3647 3648 if query: 3649 in_sql = self.sql(query) 3650 elif unnest: 3651 in_sql = self.in_unnest_op(unnest) 3652 elif field: 3653 in_sql = self.sql(field) 3654 else: 3655 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3656 3657 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3658 3659 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3660 return f"(SELECT {self.sql(unnest)})" 3661 3662 def interval_sql(self, expression: exp.Interval) -> str: 3663 unit_expression = expression.args.get("unit") 3664 unit = self.sql(unit_expression) if unit_expression else "" 3665 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3666 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3667 unit = f" {unit}" if unit else "" 3668 3669 if self.SINGLE_STRING_INTERVAL: 3670 this = expression.this.name if expression.this else "" 3671 if this: 3672 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3673 return f"INTERVAL '{this}'{unit}" 3674 return f"INTERVAL '{this}{unit}'" 3675 return f"INTERVAL{unit}" 3676 3677 this = self.sql(expression, "this") 3678 if this: 3679 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3680 this = f" {this}" if unwrapped else f" ({this})" 3681 3682 return f"INTERVAL{this}{unit}" 3683 3684 def return_sql(self, expression: exp.Return) -> str: 3685 return f"RETURN {self.sql(expression, 'this')}" 3686 3687 def reference_sql(self, expression: exp.Reference) -> str: 3688 this = self.sql(expression, "this") 3689 expressions = self.expressions(expression, flat=True) 3690 expressions = f"({expressions})" if expressions else "" 3691 options = self.expressions(expression, key="options", flat=True, sep=" ") 3692 options = f" {options}" if options else "" 3693 return f"REFERENCES {this}{expressions}{options}" 3694 3695 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3696 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3697 parent = expression.parent 3698 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3699 3700 return self.func( 3701 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3702 ) 3703 3704 def paren_sql(self, expression: exp.Paren) -> str: 3705 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3706 return f"({sql}{self.seg(')', sep='')}" 3707 3708 def neg_sql(self, expression: exp.Neg) -> str: 3709 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3710 this_sql = self.sql(expression, "this") 3711 sep = " " if this_sql[0] == "-" else "" 3712 return f"-{sep}{this_sql}" 3713 3714 def not_sql(self, expression: exp.Not) -> str: 3715 return f"NOT {self.sql(expression, 'this')}" 3716 3717 def alias_sql(self, expression: exp.Alias) -> str: 3718 alias = self.sql(expression, "alias") 3719 alias = f" AS {alias}" if alias else "" 3720 return f"{self.sql(expression, 'this')}{alias}" 3721 3722 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3723 alias = expression.args["alias"] 3724 3725 parent = expression.parent 3726 pivot = parent and parent.parent 3727 3728 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3729 identifier_alias = isinstance(alias, exp.Identifier) 3730 literal_alias = isinstance(alias, exp.Literal) 3731 3732 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3733 alias.replace(exp.Literal.string(alias.output_name)) 3734 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3735 alias.replace(exp.to_identifier(alias.output_name)) 3736 3737 return self.alias_sql(expression) 3738 3739 def aliases_sql(self, expression: exp.Aliases) -> str: 3740 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3741 3742 def atindex_sql(self, expression: exp.AtIndex) -> str: 3743 this = self.sql(expression, "this") 3744 index = self.sql(expression, "expression") 3745 return f"{this} AT {index}" 3746 3747 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3748 this = self.sql(expression, "this") 3749 zone = self.sql(expression, "zone") 3750 return f"{this} AT TIME ZONE {zone}" 3751 3752 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3753 this = self.sql(expression, "this") 3754 zone = self.sql(expression, "zone") 3755 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3756 3757 def add_sql(self, expression: exp.Add) -> str: 3758 return self.binary(expression, "+") 3759 3760 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 3761 return self.connector_sql(expression, "AND", stack) 3762 3763 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 3764 return self.connector_sql(expression, "OR", stack) 3765 3766 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 3767 return self.connector_sql(expression, "XOR", stack) 3768 3769 def connector_sql( 3770 self, 3771 expression: exp.Connector, 3772 op: str, 3773 stack: list[str | exp.Expr] | None = None, 3774 ) -> str: 3775 if stack is not None: 3776 if expression.expressions: 3777 stack.append(self.expressions(expression, sep=f" {op} ")) 3778 else: 3779 stack.append(expression.right) 3780 if expression.comments and self.comments: 3781 for comment in expression.comments: 3782 if comment: 3783 op += f" /*{self.sanitize_comment(comment)}*/" 3784 stack.extend((op, expression.left)) 3785 return op 3786 3787 stack = [expression] 3788 sqls: list[str] = [] 3789 ops = set() 3790 3791 while stack: 3792 node = stack.pop() 3793 if isinstance(node, exp.Connector): 3794 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3795 else: 3796 sql = self.sql(node) 3797 if sqls and sqls[-1] in ops: 3798 sqls[-1] += f" {sql}" 3799 else: 3800 sqls.append(sql) 3801 3802 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3803 return sep.join(sqls) 3804 3805 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3806 return self.binary(expression, "&") 3807 3808 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3809 return self.binary(expression, "<<") 3810 3811 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3812 return f"~{self.sql(expression, 'this')}" 3813 3814 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3815 return self.binary(expression, "|") 3816 3817 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3818 return self.binary(expression, ">>") 3819 3820 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3821 return self.binary(expression, "^") 3822 3823 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 3824 format_sql = self.sql(expression, "format") 3825 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3826 to_sql = self.sql(expression, "to") 3827 to_sql = f" {to_sql}" if to_sql else "" 3828 action = self.sql(expression, "action") 3829 action = f" {action}" if action else "" 3830 default = self.sql(expression, "default") 3831 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3832 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3833 3834 # Base implementation that excludes safe, zone, and target_type metadata args 3835 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3836 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3837 3838 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3839 zone = self.sql(expression, "this") 3840 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3841 3842 def collate_sql(self, expression: exp.Collate) -> str: 3843 if self.COLLATE_IS_FUNC: 3844 return self.function_fallback_sql(expression) 3845 return self.binary(expression, "COLLATE") 3846 3847 def command_sql(self, expression: exp.Command) -> str: 3848 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3849 3850 def comment_sql(self, expression: exp.Comment) -> str: 3851 this = self.sql(expression, "this") 3852 kind = expression.args["kind"] 3853 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3854 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3855 expression_sql = self.sql(expression, "expression") 3856 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3857 3858 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3859 this = self.sql(expression, "this") 3860 delete = " DELETE" if expression.args.get("delete") else "" 3861 recompress = self.sql(expression, "recompress") 3862 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3863 to_disk = self.sql(expression, "to_disk") 3864 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3865 to_volume = self.sql(expression, "to_volume") 3866 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3867 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3868 3869 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3870 where = self.sql(expression, "where") 3871 group = self.sql(expression, "group") 3872 aggregates = self.expressions(expression, key="aggregates") 3873 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3874 3875 if not (where or group or aggregates) and len(expression.expressions) == 1: 3876 return f"TTL {self.expressions(expression, flat=True)}" 3877 3878 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3879 3880 def transaction_sql(self, expression: exp.Transaction) -> str: 3881 modes = self.expressions(expression, key="modes") 3882 modes = f" {modes}" if modes else "" 3883 return f"BEGIN{modes}" 3884 3885 def commit_sql(self, expression: exp.Commit) -> str: 3886 chain = expression.args.get("chain") 3887 if chain is not None: 3888 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3889 3890 return f"COMMIT{chain or ''}" 3891 3892 def rollback_sql(self, expression: exp.Rollback) -> str: 3893 savepoint = expression.args.get("savepoint") 3894 savepoint = f" TO {savepoint}" if savepoint else "" 3895 return f"ROLLBACK{savepoint}" 3896 3897 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3898 this = self.sql(expression, "this") 3899 3900 dtype = self.sql(expression, "dtype") 3901 if dtype: 3902 collate = self.sql(expression, "collate") 3903 collate = f" COLLATE {collate}" if collate else "" 3904 using = self.sql(expression, "using") 3905 using = f" USING {using}" if using else "" 3906 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3907 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3908 3909 default = self.sql(expression, "default") 3910 if default: 3911 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3912 3913 comment = self.sql(expression, "comment") 3914 if comment: 3915 return f"ALTER COLUMN {this} COMMENT {comment}" 3916 3917 visible = expression.args.get("visible") 3918 if visible: 3919 return f"ALTER COLUMN {this} SET {visible}" 3920 3921 allow_null = expression.args.get("allow_null") 3922 drop = expression.args.get("drop") 3923 3924 if not drop and not allow_null: 3925 self.unsupported("Unsupported ALTER COLUMN syntax") 3926 3927 if allow_null is not None: 3928 keyword = "DROP" if drop else "SET" 3929 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3930 3931 return f"ALTER COLUMN {this} DROP DEFAULT" 3932 3933 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3934 this = self.sql(expression, "this") 3935 3936 visible = expression.args.get("visible") 3937 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3938 3939 return f"ALTER INDEX {this} {visible_sql}" 3940 3941 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3942 this = self.sql(expression, "this") 3943 if not isinstance(expression.this, exp.Var): 3944 this = f"KEY DISTKEY {this}" 3945 return f"ALTER DISTSTYLE {this}" 3946 3947 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3948 compound = " COMPOUND" if expression.args.get("compound") else "" 3949 this = self.sql(expression, "this") 3950 expressions = self.expressions(expression, flat=True) 3951 expressions = f"({expressions})" if expressions else "" 3952 return f"ALTER{compound} SORTKEY {this or expressions}" 3953 3954 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3955 if not self.RENAME_TABLE_WITH_DB: 3956 # Remove db from tables 3957 expression = expression.transform( 3958 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3959 ).assert_is(exp.AlterRename) 3960 this = self.sql(expression, "this") 3961 to_kw = " TO" if include_to else "" 3962 return f"RENAME{to_kw} {this}" 3963 3964 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3965 exists = " IF EXISTS" if expression.args.get("exists") else "" 3966 old_column = self.sql(expression, "this") 3967 new_column = self.sql(expression, "to") 3968 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3969 3970 def alterset_sql(self, expression: exp.AlterSet) -> str: 3971 exprs = self.expressions(expression, flat=True) 3972 if self.ALTER_SET_WRAPPED: 3973 exprs = f"({exprs})" 3974 3975 return f"SET {exprs}" 3976 3977 def alter_sql(self, expression: exp.Alter) -> str: 3978 actions = expression.args["actions"] 3979 3980 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3981 actions[0], exp.ColumnDef 3982 ): 3983 actions_sql = self.expressions(expression, key="actions", flat=True) 3984 actions_sql = f"ADD {actions_sql}" 3985 else: 3986 actions_list = [] 3987 for action in actions: 3988 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3989 action_sql = self.add_column_sql(action) 3990 else: 3991 action_sql = self.sql(action) 3992 if isinstance(action, exp.Query): 3993 action_sql = f"AS {action_sql}" 3994 3995 actions_list.append(action_sql) 3996 3997 actions_sql = self.format_args(*actions_list).lstrip("\n") 3998 3999 iceberg = ( 4000 "ICEBERG " 4001 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4002 else "" 4003 ) 4004 exists = " IF EXISTS" if expression.args.get("exists") else "" 4005 on_cluster = self.sql(expression, "cluster") 4006 on_cluster = f" {on_cluster}" if on_cluster else "" 4007 only = " ONLY" if expression.args.get("only") else "" 4008 options = self.expressions(expression, key="options") 4009 options = f", {options}" if options else "" 4010 kind = self.sql(expression, "kind") 4011 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4012 check = " WITH CHECK" if expression.args.get("check") else "" 4013 cascade = ( 4014 " CASCADE" 4015 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4016 else "" 4017 ) 4018 this = self.sql(expression, "this") 4019 this = f" {this}" if this else "" 4020 4021 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4022 4023 def altersession_sql(self, expression: exp.AlterSession) -> str: 4024 items_sql = self.expressions(expression, flat=True) 4025 keyword = "UNSET" if expression.args.get("unset") else "SET" 4026 return f"{keyword} {items_sql}" 4027 4028 def add_column_sql(self, expression: exp.Expr) -> str: 4029 sql = self.sql(expression) 4030 if isinstance(expression, exp.Schema): 4031 column_text = " COLUMNS" 4032 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4033 column_text = " COLUMN" 4034 else: 4035 column_text = "" 4036 4037 return f"ADD{column_text} {sql}" 4038 4039 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4040 expressions = self.expressions(expression) 4041 exists = " IF EXISTS " if expression.args.get("exists") else " " 4042 return f"DROP{exists}{expressions}" 4043 4044 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4045 return f"ADD {self.expressions(expression, indent=False)}" 4046 4047 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4048 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4049 location = self.sql(expression, "location") 4050 location = f" {location}" if location else "" 4051 return f"ADD {exists}{self.sql(expression.this)}{location}" 4052 4053 def distinct_sql(self, expression: exp.Distinct) -> str: 4054 this = self.expressions(expression, flat=True) 4055 4056 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4057 case = exp.case() 4058 for arg in expression.expressions: 4059 case = case.when(arg.is_(exp.null()), exp.null()) 4060 this = self.sql(case.else_(f"({this})")) 4061 4062 this = f" {this}" if this else "" 4063 4064 on = self.sql(expression, "on") 4065 on = f" ON {on}" if on else "" 4066 return f"DISTINCT{this}{on}" 4067 4068 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4069 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4070 4071 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4072 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4073 4074 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4075 this_sql = self.sql(expression, "this") 4076 expression_sql = self.sql(expression, "expression") 4077 kind = "MAX" if expression.args.get("max") else "MIN" 4078 return f"{this_sql} HAVING {kind} {expression_sql}" 4079 4080 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4081 return self.sql( 4082 exp.Cast( 4083 this=exp.Div(this=expression.this, expression=expression.expression), 4084 to=exp.DataType(this=exp.DType.INT), 4085 ) 4086 ) 4087 4088 def dpipe_sql(self, expression: exp.DPipe) -> str: 4089 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4090 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4091 return self.binary(expression, "||") 4092 4093 def div_sql(self, expression: exp.Div) -> str: 4094 l, r = expression.left, expression.right 4095 4096 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4097 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4098 4099 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4100 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4101 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4102 4103 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4104 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4105 return self.sql( 4106 exp.cast( 4107 l / r, 4108 to=exp.DType.BIGINT, 4109 ) 4110 ) 4111 4112 return self.binary(expression, "/") 4113 4114 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4115 n = exp._wrap(expression.this, exp.Binary) 4116 d = exp._wrap(expression.expression, exp.Binary) 4117 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4118 4119 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4120 return self.binary(expression, "OVERLAPS") 4121 4122 def distance_sql(self, expression: exp.Distance) -> str: 4123 return self.binary(expression, "<->") 4124 4125 def dot_sql(self, expression: exp.Dot) -> str: 4126 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4127 4128 def eq_sql(self, expression: exp.EQ) -> str: 4129 return self.binary(expression, "=") 4130 4131 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4132 return self.binary(expression, ":=") 4133 4134 def escape_sql(self, expression: exp.Escape) -> str: 4135 this = expression.this 4136 if ( 4137 isinstance(this, (exp.Like, exp.ILike)) 4138 and isinstance(this.expression, (exp.All, exp.Any)) 4139 and not self.SUPPORTS_LIKE_QUANTIFIERS 4140 ): 4141 return self._like_sql(this, escape=expression) 4142 return self.binary(expression, "ESCAPE") 4143 4144 def glob_sql(self, expression: exp.Glob) -> str: 4145 return self.binary(expression, "GLOB") 4146 4147 def gt_sql(self, expression: exp.GT) -> str: 4148 return self.binary(expression, ">") 4149 4150 def gte_sql(self, expression: exp.GTE) -> str: 4151 return self.binary(expression, ">=") 4152 4153 def is_sql(self, expression: exp.Is) -> str: 4154 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4155 return self.sql( 4156 expression.this if expression.expression.this else exp.not_(expression.this) 4157 ) 4158 return self.binary(expression, "IS") 4159 4160 def _like_sql( 4161 self, 4162 expression: exp.Like | exp.ILike, 4163 escape: exp.Escape | None = None, 4164 ) -> str: 4165 this = expression.this 4166 rhs = expression.expression 4167 4168 if isinstance(expression, exp.Like): 4169 exp_class: type[exp.Like | exp.ILike] = exp.Like 4170 op = "LIKE" 4171 else: 4172 exp_class = exp.ILike 4173 op = "ILIKE" 4174 4175 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4176 exprs = rhs.this.unnest() 4177 4178 if isinstance(exprs, exp.Tuple): 4179 exprs = exprs.expressions 4180 else: 4181 exprs = [exprs] 4182 4183 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4184 4185 def _make_like(expr: exp.Expression) -> exp.Expression: 4186 like: exp.Expression = exp_class(this=this, expression=expr) 4187 if escape: 4188 like = exp.Escape(this=like, expression=escape.expression.copy()) 4189 return like 4190 4191 like_expr: exp.Expr = _make_like(exprs[0]) 4192 for expr in exprs[1:]: 4193 like_expr = connective(like_expr, _make_like(expr), copy=False) 4194 4195 parent = escape.parent if escape else expression.parent 4196 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4197 parent, exp.Condition 4198 ): 4199 like_expr = exp.paren(like_expr, copy=False) 4200 4201 return self.sql(like_expr) 4202 4203 return self.binary(expression, op) 4204 4205 def like_sql(self, expression: exp.Like) -> str: 4206 return self._like_sql(expression) 4207 4208 def ilike_sql(self, expression: exp.ILike) -> str: 4209 return self._like_sql(expression) 4210 4211 def match_sql(self, expression: exp.Match) -> str: 4212 return self.binary(expression, "MATCH") 4213 4214 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4215 return self.binary(expression, "SIMILAR TO") 4216 4217 def lt_sql(self, expression: exp.LT) -> str: 4218 return self.binary(expression, "<") 4219 4220 def lte_sql(self, expression: exp.LTE) -> str: 4221 return self.binary(expression, "<=") 4222 4223 def mod_sql(self, expression: exp.Mod) -> str: 4224 return self.binary(expression, "%") 4225 4226 def mul_sql(self, expression: exp.Mul) -> str: 4227 return self.binary(expression, "*") 4228 4229 def neq_sql(self, expression: exp.NEQ) -> str: 4230 return self.binary(expression, "<>") 4231 4232 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4233 return self.binary(expression, "IS NOT DISTINCT FROM") 4234 4235 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4236 return self.binary(expression, "IS DISTINCT FROM") 4237 4238 def sub_sql(self, expression: exp.Sub) -> str: 4239 return self.binary(expression, "-") 4240 4241 def trycast_sql(self, expression: exp.TryCast) -> str: 4242 return self.cast_sql(expression, safe_prefix="TRY_") 4243 4244 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4245 return self.cast_sql(expression) 4246 4247 def try_sql(self, expression: exp.Try) -> str: 4248 if not self.TRY_SUPPORTED: 4249 self.unsupported("Unsupported TRY function") 4250 return self.sql(expression, "this") 4251 4252 return self.func("TRY", expression.this) 4253 4254 def log_sql(self, expression: exp.Log) -> str: 4255 this = expression.this 4256 expr = expression.expression 4257 4258 if self.dialect.LOG_BASE_FIRST is False: 4259 this, expr = expr, this 4260 elif self.dialect.LOG_BASE_FIRST is None and expr: 4261 if this.name in ("2", "10"): 4262 return self.func(f"LOG{this.name}", expr) 4263 4264 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4265 4266 return self.func("LOG", this, expr) 4267 4268 def use_sql(self, expression: exp.Use) -> str: 4269 kind = self.sql(expression, "kind") 4270 kind = f" {kind}" if kind else "" 4271 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4272 this = f" {this}" if this else "" 4273 return f"USE{kind}{this}" 4274 4275 def binary(self, expression: exp.Binary, op: str) -> str: 4276 sqls: list[str] = [] 4277 stack: list[None | str | exp.Expr] = [expression] 4278 binary_type = type(expression) 4279 4280 while stack: 4281 node = stack.pop() 4282 4283 if type(node) is binary_type: 4284 op_func = node.args.get("operator") 4285 if op_func: 4286 op = f"OPERATOR({self.sql(op_func)})" 4287 4288 stack.append(node.args.get("expression")) 4289 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4290 stack.append(node.args.get("this")) 4291 else: 4292 sqls.append(self.sql(node)) 4293 4294 return "".join(sqls) 4295 4296 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4297 to_clause = self.sql(expression, "to") 4298 if to_clause: 4299 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4300 4301 return self.function_fallback_sql(expression) 4302 4303 def function_fallback_sql(self, expression: exp.Func) -> str: 4304 args = [] 4305 4306 for key in expression.arg_types: 4307 arg_value = expression.args.get(key) 4308 4309 if isinstance(arg_value, list): 4310 for value in arg_value: 4311 args.append(value) 4312 elif arg_value is not None: 4313 args.append(arg_value) 4314 4315 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4316 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4317 else: 4318 name = expression.sql_name() 4319 4320 return self.func(name, *args) 4321 4322 def func( 4323 self, 4324 name: str, 4325 *args: t.Any, 4326 prefix: str = "(", 4327 suffix: str = ")", 4328 normalize: bool = True, 4329 ) -> str: 4330 name = self.normalize_func(name) if normalize else name 4331 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4332 4333 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4334 arg_sqls = tuple( 4335 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4336 ) 4337 if self.pretty and self.too_wide(arg_sqls): 4338 return self.indent( 4339 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4340 ) 4341 return sep.join(arg_sqls) 4342 4343 def too_wide(self, args: t.Iterable) -> bool: 4344 return sum(len(arg) for arg in args) > self.max_text_width 4345 4346 def format_time( 4347 self, 4348 expression: exp.Expr, 4349 inverse_time_mapping: dict[str, str] | None = None, 4350 inverse_time_trie: dict | None = None, 4351 ) -> str | None: 4352 return format_time( 4353 self.sql(expression, "format"), 4354 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4355 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4356 ) 4357 4358 def expressions( 4359 self, 4360 expression: exp.Expr | None = None, 4361 key: str | None = None, 4362 sqls: t.Collection[str | exp.Expr] | None = None, 4363 flat: bool = False, 4364 indent: bool = True, 4365 skip_first: bool = False, 4366 skip_last: bool = False, 4367 sep: str = ", ", 4368 prefix: str = "", 4369 dynamic: bool = False, 4370 new_line: bool = False, 4371 ) -> str: 4372 expressions = expression.args.get(key or "expressions") if expression else sqls 4373 4374 if not expressions: 4375 return "" 4376 4377 if flat: 4378 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4379 4380 num_sqls = len(expressions) 4381 result_sqls = [] 4382 4383 for i, e in enumerate(expressions): 4384 sql = self.sql(e, comment=False) 4385 if not sql: 4386 continue 4387 4388 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4389 4390 if self.pretty: 4391 if self.leading_comma: 4392 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4393 else: 4394 result_sqls.append( 4395 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4396 ) 4397 else: 4398 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4399 4400 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4401 if new_line: 4402 result_sqls.insert(0, "") 4403 result_sqls.append("") 4404 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4405 else: 4406 result_sql = "".join(result_sqls) 4407 4408 return ( 4409 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4410 if indent 4411 else result_sql 4412 ) 4413 4414 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4415 flat = flat or isinstance(expression.parent, exp.Properties) 4416 expressions_sql = self.expressions(expression, flat=flat) 4417 if flat: 4418 return f"{op} {expressions_sql}" 4419 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4420 4421 def naked_property(self, expression: exp.Property) -> str: 4422 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4423 if not property_name: 4424 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4425 return f"{property_name} {self.sql(expression, 'this')}" 4426 4427 def tag_sql(self, expression: exp.Tag) -> str: 4428 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4429 4430 def token_sql(self, token_type: TokenType) -> str: 4431 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4432 4433 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4434 this = self.sql(expression, "this") 4435 expressions = self.no_identify(self.expressions, expression) 4436 expressions = ( 4437 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4438 ) 4439 return f"{this}{expressions}" if expressions.strip() != "" else this 4440 4441 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4442 this = self.sql(expression, "this") 4443 expressions = self.expressions(expression, flat=True) 4444 return f"{this}({expressions})" 4445 4446 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4447 return self.binary(expression, "=>") 4448 4449 def when_sql(self, expression: exp.When) -> str: 4450 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4451 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4452 condition = self.sql(expression, "condition") 4453 condition = f" AND {condition}" if condition else "" 4454 4455 then_expression = expression.args.get("then") 4456 if isinstance(then_expression, exp.Insert): 4457 this = self.sql(then_expression, "this") 4458 this = f"INSERT {this}" if this else "INSERT" 4459 then = self.sql(then_expression, "expression") 4460 then = f"{this} VALUES {then}" if then else this 4461 elif isinstance(then_expression, exp.Update): 4462 if isinstance(then_expression.args.get("expressions"), exp.Star): 4463 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4464 else: 4465 expressions_sql = self.expressions(then_expression) 4466 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4467 else: 4468 then = self.sql(then_expression) 4469 4470 if isinstance(then_expression, (exp.Insert, exp.Update)): 4471 where = self.sql(then_expression, "where") 4472 if where and not self.SUPPORTS_MERGE_WHERE: 4473 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4474 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4475 where = "" 4476 then = f"{then}{where}" 4477 return f"WHEN {matched}{source}{condition} THEN {then}" 4478 4479 def whens_sql(self, expression: exp.Whens) -> str: 4480 return self.expressions(expression, sep=" ", indent=False) 4481 4482 def merge_sql(self, expression: exp.Merge) -> str: 4483 table = expression.this 4484 table_alias = "" 4485 4486 hints = table.args.get("hints") 4487 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4488 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4489 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4490 4491 this = self.sql(table) 4492 using = f"USING {self.sql(expression, 'using')}" 4493 whens = self.sql(expression, "whens") 4494 4495 on = self.sql(expression, "on") 4496 on = f"ON {on}" if on else "" 4497 4498 if not on: 4499 on = self.expressions(expression, key="using_cond") 4500 on = f"USING ({on})" if on else "" 4501 4502 returning = self.sql(expression, "returning") 4503 if returning: 4504 whens = f"{whens}{returning}" 4505 4506 sep = self.sep() 4507 4508 return self.prepend_ctes( 4509 expression, 4510 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4511 ) 4512 4513 @unsupported_args("format") 4514 def tochar_sql(self, expression: exp.ToChar) -> str: 4515 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4516 4517 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4518 if not self.SUPPORTS_TO_NUMBER: 4519 self.unsupported("Unsupported TO_NUMBER function") 4520 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4521 4522 fmt = expression.args.get("format") 4523 if not fmt: 4524 self.unsupported("Conversion format is required for TO_NUMBER") 4525 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4526 4527 return self.func("TO_NUMBER", expression.this, fmt) 4528 4529 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4530 this = self.sql(expression, "this") 4531 kind = self.sql(expression, "kind") 4532 settings_sql = self.expressions(expression, key="settings", sep=" ") 4533 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4534 return f"{this}({kind}{args})" 4535 4536 def dictrange_sql(self, expression: exp.DictRange) -> str: 4537 this = self.sql(expression, "this") 4538 max = self.sql(expression, "max") 4539 min = self.sql(expression, "min") 4540 return f"{this}(MIN {min} MAX {max})" 4541 4542 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4543 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4544 4545 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4546 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4547 4548 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4549 def uniquekeyproperty_sql( 4550 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4551 ) -> str: 4552 return f"{prefix} ({self.expressions(expression, flat=True)})" 4553 4554 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4555 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4556 expressions = self.expressions(expression, flat=True) 4557 expressions = f" {self.wrap(expressions)}" if expressions else "" 4558 buckets = self.sql(expression, "buckets") 4559 kind = self.sql(expression, "kind") 4560 buckets = f" BUCKETS {buckets}" if buckets else "" 4561 order = self.sql(expression, "order") 4562 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4563 4564 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4565 return "" 4566 4567 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4568 expressions = self.expressions(expression, key="expressions", flat=True) 4569 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4570 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4571 buckets = self.sql(expression, "buckets") 4572 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4573 4574 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4575 this = self.sql(expression, "this") 4576 having = self.sql(expression, "having") 4577 4578 if having: 4579 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4580 4581 return self.func("ANY_VALUE", this) 4582 4583 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4584 transform = self.func("TRANSFORM", *expression.expressions) 4585 row_format_before = self.sql(expression, "row_format_before") 4586 row_format_before = f" {row_format_before}" if row_format_before else "" 4587 record_writer = self.sql(expression, "record_writer") 4588 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4589 using = f" USING {self.sql(expression, 'command_script')}" 4590 schema = self.sql(expression, "schema") 4591 schema = f" AS {schema}" if schema else "" 4592 row_format_after = self.sql(expression, "row_format_after") 4593 row_format_after = f" {row_format_after}" if row_format_after else "" 4594 record_reader = self.sql(expression, "record_reader") 4595 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4596 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4597 4598 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4599 key_block_size = self.sql(expression, "key_block_size") 4600 if key_block_size: 4601 return f"KEY_BLOCK_SIZE = {key_block_size}" 4602 4603 using = self.sql(expression, "using") 4604 if using: 4605 return f"USING {using}" 4606 4607 parser = self.sql(expression, "parser") 4608 if parser: 4609 return f"WITH PARSER {parser}" 4610 4611 comment = self.sql(expression, "comment") 4612 if comment: 4613 return f"COMMENT {comment}" 4614 4615 visible = expression.args.get("visible") 4616 if visible is not None: 4617 return "VISIBLE" if visible else "INVISIBLE" 4618 4619 engine_attr = self.sql(expression, "engine_attr") 4620 if engine_attr: 4621 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4622 4623 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4624 if secondary_engine_attr: 4625 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4626 4627 self.unsupported("Unsupported index constraint option.") 4628 return "" 4629 4630 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4631 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4632 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4633 4634 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4635 kind = self.sql(expression, "kind") 4636 kind = f"{kind} INDEX" if kind else "INDEX" 4637 this = self.sql(expression, "this") 4638 this = f" {this}" if this else "" 4639 index_type = self.sql(expression, "index_type") 4640 index_type = f" USING {index_type}" if index_type else "" 4641 expressions = self.expressions(expression, flat=True) 4642 expressions = f" ({expressions})" if expressions else "" 4643 options = self.expressions(expression, key="options", sep=" ") 4644 options = f" {options}" if options else "" 4645 return f"{kind}{this}{index_type}{expressions}{options}" 4646 4647 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4648 if self.NVL2_SUPPORTED: 4649 return self.function_fallback_sql(expression) 4650 4651 case = exp.Case().when( 4652 expression.this.is_(exp.null()).not_(copy=False), 4653 expression.args["true"], 4654 copy=False, 4655 ) 4656 else_cond = expression.args.get("false") 4657 if else_cond: 4658 case.else_(else_cond, copy=False) 4659 4660 return self.sql(case) 4661 4662 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4663 this = self.sql(expression, "this") 4664 expr = self.sql(expression, "expression") 4665 position = self.sql(expression, "position") 4666 position = f", {position}" if position else "" 4667 iterator = self.sql(expression, "iterator") 4668 condition = self.sql(expression, "condition") 4669 condition = f" IF {condition}" if condition else "" 4670 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4671 4672 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4673 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4674 4675 def opclass_sql(self, expression: exp.Opclass) -> str: 4676 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4677 4678 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4679 model = self.sql(expression, "this") 4680 model = f"MODEL {model}" 4681 expr = expression.expression 4682 if expr: 4683 expr_sql = self.sql(expression, "expression") 4684 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 4685 else: 4686 expr_sql = None 4687 4688 parameters = self.sql(expression, "params_struct") or None 4689 4690 return self.func(name, model, expr_sql, parameters) 4691 4692 def predict_sql(self, expression: exp.Predict) -> str: 4693 return self._ml_sql(expression, "PREDICT") 4694 4695 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4696 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4697 return self._ml_sql(expression, name) 4698 4699 def generatetext_sql(self, expression: exp.GenerateText) -> str: 4700 return self._ml_sql(expression, "GENERATE_TEXT") 4701 4702 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 4703 return self._ml_sql(expression, "GENERATE_TABLE") 4704 4705 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 4706 return self._ml_sql(expression, "GENERATE_BOOL") 4707 4708 def generateint_sql(self, expression: exp.GenerateInt) -> str: 4709 return self._ml_sql(expression, "GENERATE_INT") 4710 4711 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 4712 return self._ml_sql(expression, "GENERATE_DOUBLE") 4713 4714 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4715 return self._ml_sql(expression, "TRANSLATE") 4716 4717 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4718 return self._ml_sql(expression, "FORECAST") 4719 4720 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4721 this_sql = self.sql(expression, "this") 4722 if isinstance(expression.this, exp.Table): 4723 this_sql = f"TABLE {this_sql}" 4724 4725 return self.func( 4726 "FORECAST", 4727 this_sql, 4728 expression.args.get("data_col"), 4729 expression.args.get("timestamp_col"), 4730 expression.args.get("model"), 4731 expression.args.get("id_cols"), 4732 expression.args.get("horizon"), 4733 expression.args.get("forecast_end_timestamp"), 4734 expression.args.get("confidence_level"), 4735 expression.args.get("output_historical_time_series"), 4736 expression.args.get("context_window"), 4737 ) 4738 4739 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4740 this_sql = self.sql(expression, "this") 4741 if isinstance(expression.this, exp.Table): 4742 this_sql = f"TABLE {this_sql}" 4743 4744 return self.func( 4745 "FEATURES_AT_TIME", 4746 this_sql, 4747 expression.args.get("time"), 4748 expression.args.get("num_rows"), 4749 expression.args.get("ignore_feature_nulls"), 4750 ) 4751 4752 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4753 this_sql = self.sql(expression, "this") 4754 if isinstance(expression.this, exp.Table): 4755 this_sql = f"TABLE {this_sql}" 4756 4757 query_table = self.sql(expression, "query_table") 4758 if isinstance(expression.args["query_table"], exp.Table): 4759 query_table = f"TABLE {query_table}" 4760 4761 return self.func( 4762 "VECTOR_SEARCH", 4763 this_sql, 4764 expression.args.get("column_to_search"), 4765 query_table, 4766 expression.args.get("query_column_to_search"), 4767 expression.args.get("top_k"), 4768 expression.args.get("distance_type"), 4769 expression.args.get("options"), 4770 ) 4771 4772 def forin_sql(self, expression: exp.ForIn) -> str: 4773 this = self.sql(expression, "this") 4774 expression_sql = self.sql(expression, "expression") 4775 return f"FOR {this} DO {expression_sql}" 4776 4777 def refresh_sql(self, expression: exp.Refresh) -> str: 4778 this = self.sql(expression, "this") 4779 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4780 return f"REFRESH {kind}{this}" 4781 4782 def toarray_sql(self, expression: exp.ToArray) -> str: 4783 arg = expression.this 4784 if not arg.type: 4785 import sqlglot.optimizer.annotate_types 4786 4787 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 4788 4789 if arg.is_type(exp.DType.ARRAY): 4790 return self.sql(arg) 4791 4792 cond_for_null = arg.is_(exp.null()) 4793 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4794 4795 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4796 this = expression.this 4797 time_format = self.format_time(expression) 4798 4799 if time_format: 4800 return self.sql( 4801 exp.cast( 4802 exp.StrToTime(this=this, format=expression.args["format"]), 4803 exp.DType.TIME, 4804 ) 4805 ) 4806 4807 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 4808 return self.sql(this) 4809 4810 return self.sql(exp.cast(this, exp.DType.TIME)) 4811 4812 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4813 this = expression.this 4814 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 4815 return self.sql(this) 4816 4817 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 4818 4819 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4820 this = expression.this 4821 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 4822 return self.sql(this) 4823 4824 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 4825 4826 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4827 this = expression.this 4828 time_format = self.format_time(expression) 4829 safe = expression.args.get("safe") 4830 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4831 return self.sql( 4832 exp.cast( 4833 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4834 exp.DType.DATE, 4835 ) 4836 ) 4837 4838 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 4839 return self.sql(this) 4840 4841 if safe: 4842 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 4843 4844 return self.sql(exp.cast(this, exp.DType.DATE)) 4845 4846 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4847 return self.sql( 4848 exp.func( 4849 "DATEDIFF", 4850 expression.this, 4851 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 4852 "day", 4853 ) 4854 ) 4855 4856 def lastday_sql(self, expression: exp.LastDay) -> str: 4857 if self.LAST_DAY_SUPPORTS_DATE_PART: 4858 return self.function_fallback_sql(expression) 4859 4860 unit = expression.text("unit") 4861 if unit and unit != "MONTH": 4862 self.unsupported("Date parts are not supported in LAST_DAY.") 4863 4864 return self.func("LAST_DAY", expression.this) 4865 4866 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4867 import sqlglot.dialects.dialect 4868 4869 return self.func( 4870 "DATE_ADD", 4871 expression.this, 4872 expression.expression, 4873 sqlglot.dialects.dialect.unit_to_str(expression), 4874 ) 4875 4876 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4877 if self.CAN_IMPLEMENT_ARRAY_ANY: 4878 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4879 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4880 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4881 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4882 4883 import sqlglot.dialects.dialect 4884 4885 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4886 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 4887 self.unsupported("ARRAY_ANY is unsupported") 4888 4889 return self.function_fallback_sql(expression) 4890 4891 def struct_sql(self, expression: exp.Struct) -> str: 4892 expression.set( 4893 "expressions", 4894 [ 4895 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4896 if isinstance(e, exp.PropertyEQ) 4897 else e 4898 for e in expression.expressions 4899 ], 4900 ) 4901 4902 return self.function_fallback_sql(expression) 4903 4904 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4905 low = self.sql(expression, "this") 4906 high = self.sql(expression, "expression") 4907 4908 return f"{low} TO {high}" 4909 4910 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4911 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4912 tables = f" {self.expressions(expression)}" 4913 4914 exists = " IF EXISTS" if expression.args.get("exists") else "" 4915 4916 on_cluster = self.sql(expression, "cluster") 4917 on_cluster = f" {on_cluster}" if on_cluster else "" 4918 4919 identity = self.sql(expression, "identity") 4920 identity = f" {identity} IDENTITY" if identity else "" 4921 4922 option = self.sql(expression, "option") 4923 option = f" {option}" if option else "" 4924 4925 partition = self.sql(expression, "partition") 4926 partition = f" {partition}" if partition else "" 4927 4928 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4929 4930 # This transpiles T-SQL's CONVERT function 4931 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4932 def convert_sql(self, expression: exp.Convert) -> str: 4933 to = expression.this 4934 value = expression.expression 4935 style = expression.args.get("style") 4936 safe = expression.args.get("safe") 4937 strict = expression.args.get("strict") 4938 4939 if not to or not value: 4940 return "" 4941 4942 # Retrieve length of datatype and override to default if not specified 4943 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4944 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4945 4946 transformed: exp.Expr | None = None 4947 cast = exp.Cast if strict else exp.TryCast 4948 4949 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4950 if isinstance(style, exp.Literal) and style.is_int: 4951 import sqlglot.dialects.tsql 4952 4953 style_value = style.name 4954 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4955 if not converted_style: 4956 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4957 4958 fmt = exp.Literal.string(converted_style) 4959 4960 if to.this == exp.DType.DATE: 4961 transformed = exp.StrToDate(this=value, format=fmt) 4962 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 4963 transformed = exp.StrToTime(this=value, format=fmt) 4964 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4965 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4966 elif to.this == exp.DType.TEXT: 4967 transformed = exp.TimeToStr(this=value, format=fmt) 4968 4969 if not transformed: 4970 transformed = cast(this=value, to=to, safe=safe) 4971 4972 return self.sql(transformed) 4973 4974 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4975 this = expression.this 4976 if isinstance(this, exp.JSONPathWildcard): 4977 this = self.json_path_part(this) 4978 return f".{this}" if this else "" 4979 4980 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4981 return f".{this}" 4982 4983 this = self.json_path_part(this) 4984 return ( 4985 f"[{this}]" 4986 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4987 else f".{this}" 4988 ) 4989 4990 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4991 this = self.json_path_part(expression.this) 4992 return f"[{this}]" if this else "" 4993 4994 def _simplify_unless_literal(self, expression: E) -> E: 4995 if not isinstance(expression, exp.Literal): 4996 import sqlglot.optimizer.simplify 4997 4998 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 4999 5000 return expression 5001 5002 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5003 this = expression.this 5004 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5005 self.unsupported( 5006 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5007 ) 5008 return self.sql(this) 5009 5010 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 5011 if self.IGNORE_NULLS_BEFORE_ORDER: 5012 # The first modifier here will be the one closest to the AggFunc's arg 5013 mods = sorted( 5014 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5015 key=lambda x: ( 5016 0 5017 if isinstance(x, exp.HavingMax) 5018 else (1 if isinstance(x, exp.Order) else 2) 5019 ), 5020 ) 5021 5022 if mods: 5023 mod = mods[0] 5024 this = expression.__class__(this=mod.this.copy()) 5025 this.meta["inline"] = True 5026 mod.this.replace(this) 5027 return self.sql(expression.this) 5028 5029 agg_func = expression.find(exp.AggFunc) 5030 5031 if agg_func: 5032 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5033 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5034 5035 return f"{self.sql(expression, 'this')} {text}" 5036 5037 def _replace_line_breaks(self, string: str) -> str: 5038 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5039 if self.pretty: 5040 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5041 return string 5042 5043 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5044 option = self.sql(expression, "this") 5045 5046 if expression.expressions: 5047 upper = option.upper() 5048 5049 # Snowflake FILE_FORMAT options are separated by whitespace 5050 sep = " " if upper == "FILE_FORMAT" else ", " 5051 5052 # Databricks copy/format options do not set their list of values with EQ 5053 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5054 values = self.expressions(expression, flat=True, sep=sep) 5055 return f"{option}{op}({values})" 5056 5057 value = self.sql(expression, "expression") 5058 5059 if not value: 5060 return option 5061 5062 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5063 5064 return f"{option}{op}{value}" 5065 5066 def credentials_sql(self, expression: exp.Credentials) -> str: 5067 cred_expr = expression.args.get("credentials") 5068 if isinstance(cred_expr, exp.Literal): 5069 # Redshift case: CREDENTIALS <string> 5070 credentials = self.sql(expression, "credentials") 5071 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5072 else: 5073 # Snowflake case: CREDENTIALS = (...) 5074 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5075 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5076 5077 storage = self.sql(expression, "storage") 5078 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5079 5080 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5081 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5082 5083 iam_role = self.sql(expression, "iam_role") 5084 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5085 5086 region = self.sql(expression, "region") 5087 region = f" REGION {region}" if region else "" 5088 5089 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5090 5091 def copy_sql(self, expression: exp.Copy) -> str: 5092 this = self.sql(expression, "this") 5093 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5094 5095 credentials = self.sql(expression, "credentials") 5096 credentials = self.seg(credentials) if credentials else "" 5097 files = self.expressions(expression, key="files", flat=True) 5098 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5099 5100 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5101 params = self.expressions( 5102 expression, 5103 key="params", 5104 sep=sep, 5105 new_line=True, 5106 skip_last=True, 5107 skip_first=True, 5108 indent=self.COPY_PARAMS_ARE_WRAPPED, 5109 ) 5110 5111 if params: 5112 if self.COPY_PARAMS_ARE_WRAPPED: 5113 params = f" WITH ({params})" 5114 elif not self.pretty and (files or credentials): 5115 params = f" {params}" 5116 5117 return f"COPY{this}{kind} {files}{credentials}{params}" 5118 5119 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5120 return "" 5121 5122 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5123 on_sql = "ON" if expression.args.get("on") else "OFF" 5124 filter_col: str | None = self.sql(expression, "filter_column") 5125 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5126 retention_period: str | None = self.sql(expression, "retention_period") 5127 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5128 5129 if filter_col or retention_period: 5130 on_sql = self.func("ON", filter_col, retention_period) 5131 5132 return f"DATA_DELETION={on_sql}" 5133 5134 def maskingpolicycolumnconstraint_sql( 5135 self, expression: exp.MaskingPolicyColumnConstraint 5136 ) -> str: 5137 this = self.sql(expression, "this") 5138 expressions = self.expressions(expression, flat=True) 5139 expressions = f" USING ({expressions})" if expressions else "" 5140 return f"MASKING POLICY {this}{expressions}" 5141 5142 def gapfill_sql(self, expression: exp.GapFill) -> str: 5143 this = self.sql(expression, "this") 5144 this = f"TABLE {this}" 5145 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5146 5147 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5148 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5149 5150 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5151 this = self.sql(expression, "this") 5152 expr = expression.expression 5153 5154 if isinstance(expr, exp.Func): 5155 # T-SQL's CLR functions are case sensitive 5156 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5157 else: 5158 expr = self.sql(expression, "expression") 5159 5160 return self.scope_resolution(expr, this) 5161 5162 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5163 if self.PARSE_JSON_NAME is None: 5164 return self.sql(expression.this) 5165 5166 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5167 5168 def rand_sql(self, expression: exp.Rand) -> str: 5169 lower = self.sql(expression, "lower") 5170 upper = self.sql(expression, "upper") 5171 5172 if lower and upper: 5173 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5174 return self.func("RAND", expression.this) 5175 5176 def changes_sql(self, expression: exp.Changes) -> str: 5177 information = self.sql(expression, "information") 5178 information = f"INFORMATION => {information}" 5179 at_before = self.sql(expression, "at_before") 5180 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5181 end = self.sql(expression, "end") 5182 end = f"{self.seg('')}{end}" if end else "" 5183 5184 return f"CHANGES ({information}){at_before}{end}" 5185 5186 def pad_sql(self, expression: exp.Pad) -> str: 5187 prefix = "L" if expression.args.get("is_left") else "R" 5188 5189 fill_pattern = self.sql(expression, "fill_pattern") or None 5190 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5191 fill_pattern = "' '" 5192 5193 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5194 5195 def summarize_sql(self, expression: exp.Summarize) -> str: 5196 table = " TABLE" if expression.args.get("table") else "" 5197 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5198 5199 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5200 generate_series = exp.GenerateSeries(**expression.args) 5201 5202 parent = expression.parent 5203 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5204 parent = parent.parent 5205 5206 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5207 return self.sql(exp.Unnest(expressions=[generate_series])) 5208 5209 if isinstance(parent, exp.Select): 5210 self.unsupported("GenerateSeries projection unnesting is not supported.") 5211 5212 return self.sql(generate_series) 5213 5214 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5215 if self.SUPPORTS_CONVERT_TIMEZONE: 5216 return self.function_fallback_sql(expression) 5217 5218 source_tz = expression.args.get("source_tz") 5219 target_tz = expression.args.get("target_tz") 5220 timestamp = expression.args.get("timestamp") 5221 5222 if source_tz and timestamp: 5223 timestamp = exp.AtTimeZone( 5224 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5225 ) 5226 5227 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5228 5229 return self.sql(expr) 5230 5231 def json_sql(self, expression: exp.JSON) -> str: 5232 this = self.sql(expression, "this") 5233 this = f" {this}" if this else "" 5234 5235 _with = expression.args.get("with_") 5236 5237 if _with is None: 5238 with_sql = "" 5239 elif not _with: 5240 with_sql = " WITHOUT" 5241 else: 5242 with_sql = " WITH" 5243 5244 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5245 5246 return f"JSON{this}{with_sql}{unique_sql}" 5247 5248 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5249 path = self.sql(expression, "path") 5250 returning = self.sql(expression, "returning") 5251 returning = f" RETURNING {returning}" if returning else "" 5252 5253 on_condition = self.sql(expression, "on_condition") 5254 on_condition = f" {on_condition}" if on_condition else "" 5255 5256 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5257 5258 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5259 regexp = " REGEXP" if expression.args.get("regexp") else "" 5260 return f"SKIP{regexp} {self.sql(expression.expression)}" 5261 5262 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5263 else_ = "ELSE " if expression.args.get("else_") else "" 5264 condition = self.sql(expression, "expression") 5265 condition = f"WHEN {condition} THEN " if condition else else_ 5266 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5267 return f"{condition}{insert}" 5268 5269 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5270 kind = self.sql(expression, "kind") 5271 expressions = self.seg(self.expressions(expression, sep=" ")) 5272 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5273 return res 5274 5275 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5276 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5277 empty = expression.args.get("empty") 5278 empty = ( 5279 f"DEFAULT {empty} ON EMPTY" 5280 if isinstance(empty, exp.Expr) 5281 else self.sql(expression, "empty") 5282 ) 5283 5284 error = expression.args.get("error") 5285 error = ( 5286 f"DEFAULT {error} ON ERROR" 5287 if isinstance(error, exp.Expr) 5288 else self.sql(expression, "error") 5289 ) 5290 5291 if error and empty: 5292 error = ( 5293 f"{empty} {error}" 5294 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5295 else f"{error} {empty}" 5296 ) 5297 empty = "" 5298 5299 null = self.sql(expression, "null") 5300 5301 return f"{empty}{error}{null}" 5302 5303 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5304 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5305 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5306 5307 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5308 this = self.sql(expression, "this") 5309 path = self.sql(expression, "path") 5310 5311 passing = self.expressions(expression, "passing") 5312 passing = f" PASSING {passing}" if passing else "" 5313 5314 on_condition = self.sql(expression, "on_condition") 5315 on_condition = f" {on_condition}" if on_condition else "" 5316 5317 path = f"{path}{passing}{on_condition}" 5318 5319 return self.func("JSON_EXISTS", this, path) 5320 5321 def _add_arrayagg_null_filter( 5322 self, 5323 array_agg_sql: str, 5324 array_agg_expr: exp.ArrayAgg, 5325 column_expr: exp.Expr, 5326 ) -> str: 5327 """ 5328 Add NULL filter to ARRAY_AGG if dialect requires it. 5329 5330 Args: 5331 array_agg_sql: The generated ARRAY_AGG SQL string 5332 array_agg_expr: The ArrayAgg expression node 5333 column_expr: The column/expression to filter (before ORDER BY wrapping) 5334 5335 Returns: 5336 SQL string with FILTER clause added if needed 5337 """ 5338 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5339 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5340 if not ( 5341 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5342 ): 5343 return array_agg_sql 5344 5345 parent = array_agg_expr.parent 5346 if isinstance(parent, exp.Filter): 5347 parent_cond = parent.expression.this 5348 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5349 elif column_expr.find(exp.Column): 5350 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5351 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5352 this_sql = ( 5353 self.expressions(column_expr) 5354 if isinstance(column_expr, exp.Distinct) 5355 else self.sql(column_expr) 5356 ) 5357 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5358 5359 return array_agg_sql 5360 5361 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5362 array_agg = self.function_fallback_sql(expression) 5363 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5364 5365 def slice_sql(self, expression: exp.Slice) -> str: 5366 step = self.sql(expression, "step") 5367 end = self.sql(expression.expression) 5368 begin = self.sql(expression.this) 5369 5370 sql = f"{end}:{step}" if step else end 5371 return f"{begin}:{sql}" if sql else f"{begin}:" 5372 5373 def apply_sql(self, expression: exp.Apply) -> str: 5374 this = self.sql(expression, "this") 5375 expr = self.sql(expression, "expression") 5376 5377 return f"{this} APPLY({expr})" 5378 5379 def _grant_or_revoke_sql( 5380 self, 5381 expression: exp.Grant | exp.Revoke, 5382 keyword: str, 5383 preposition: str, 5384 grant_option_prefix: str = "", 5385 grant_option_suffix: str = "", 5386 ) -> str: 5387 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5388 5389 kind = self.sql(expression, "kind") 5390 kind = f" {kind}" if kind else "" 5391 5392 securable = self.sql(expression, "securable") 5393 securable = f" {securable}" if securable else "" 5394 5395 principals = self.expressions(expression, key="principals", flat=True) 5396 5397 if not expression.args.get("grant_option"): 5398 grant_option_prefix = grant_option_suffix = "" 5399 5400 # cascade for revoke only 5401 cascade = self.sql(expression, "cascade") 5402 cascade = f" {cascade}" if cascade else "" 5403 5404 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5405 5406 def grant_sql(self, expression: exp.Grant) -> str: 5407 return self._grant_or_revoke_sql( 5408 expression, 5409 keyword="GRANT", 5410 preposition="TO", 5411 grant_option_suffix=" WITH GRANT OPTION", 5412 ) 5413 5414 def revoke_sql(self, expression: exp.Revoke) -> str: 5415 return self._grant_or_revoke_sql( 5416 expression, 5417 keyword="REVOKE", 5418 preposition="FROM", 5419 grant_option_prefix="GRANT OPTION FOR ", 5420 ) 5421 5422 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5423 this = self.sql(expression, "this") 5424 columns = self.expressions(expression, flat=True) 5425 columns = f"({columns})" if columns else "" 5426 5427 return f"{this}{columns}" 5428 5429 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5430 this = self.sql(expression, "this") 5431 5432 kind = self.sql(expression, "kind") 5433 kind = f"{kind} " if kind else "" 5434 5435 return f"{kind}{this}" 5436 5437 def columns_sql(self, expression: exp.Columns) -> str: 5438 func = self.function_fallback_sql(expression) 5439 if expression.args.get("unpack"): 5440 func = f"*{func}" 5441 5442 return func 5443 5444 def overlay_sql(self, expression: exp.Overlay) -> str: 5445 this = self.sql(expression, "this") 5446 expr = self.sql(expression, "expression") 5447 from_sql = self.sql(expression, "from_") 5448 for_sql = self.sql(expression, "for_") 5449 for_sql = f" FOR {for_sql}" if for_sql else "" 5450 5451 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5452 5453 @unsupported_args("format") 5454 def todouble_sql(self, expression: exp.ToDouble) -> str: 5455 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5456 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5457 5458 def string_sql(self, expression: exp.String) -> str: 5459 this = expression.this 5460 zone = expression.args.get("zone") 5461 5462 if zone: 5463 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5464 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5465 # set for source_tz to transpile the time conversion before the STRING cast 5466 this = exp.ConvertTimezone( 5467 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5468 ) 5469 5470 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5471 5472 def median_sql(self, expression: exp.Median) -> str: 5473 if not self.SUPPORTS_MEDIAN: 5474 return self.sql( 5475 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5476 ) 5477 5478 return self.function_fallback_sql(expression) 5479 5480 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5481 filler = self.sql(expression, "this") 5482 filler = f" {filler}" if filler else "" 5483 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5484 return f"TRUNCATE{filler} {with_count}" 5485 5486 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5487 if self.SUPPORTS_UNIX_SECONDS: 5488 return self.function_fallback_sql(expression) 5489 5490 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5491 5492 return self.sql( 5493 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5494 ) 5495 5496 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5497 dim = expression.expression 5498 5499 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5500 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5501 if not (dim.is_int and dim.name == "1"): 5502 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5503 dim = None 5504 5505 # If dimension is required but not specified, default initialize it 5506 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5507 dim = exp.Literal.number(1) 5508 5509 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5510 5511 def attach_sql(self, expression: exp.Attach) -> str: 5512 this = self.sql(expression, "this") 5513 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5514 expressions = self.expressions(expression) 5515 expressions = f" ({expressions})" if expressions else "" 5516 5517 return f"ATTACH{exists_sql} {this}{expressions}" 5518 5519 def detach_sql(self, expression: exp.Detach) -> str: 5520 kind = self.sql(expression, "kind") 5521 kind = f" {kind}" if kind else "" 5522 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5523 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5524 exists = " IF EXISTS" if expression.args.get("exists") else "" 5525 if exists: 5526 kind = kind or " DATABASE" 5527 5528 this = self.sql(expression, "this") 5529 this = f" {this}" if this else "" 5530 cluster = self.sql(expression, "cluster") 5531 cluster = f" {cluster}" if cluster else "" 5532 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5533 sync = " SYNC" if expression.args.get("sync") else "" 5534 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5535 5536 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5537 this = self.sql(expression, "this") 5538 value = self.sql(expression, "expression") 5539 value = f" {value}" if value else "" 5540 return f"{this}{value}" 5541 5542 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5543 return ( 5544 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5545 ) 5546 5547 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5548 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5549 encode = f"{encode} {self.sql(expression, 'this')}" 5550 5551 properties = expression.args.get("properties") 5552 if properties: 5553 encode = f"{encode} {self.properties(properties)}" 5554 5555 return encode 5556 5557 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5558 this = self.sql(expression, "this") 5559 include = f"INCLUDE {this}" 5560 5561 column_def = self.sql(expression, "column_def") 5562 if column_def: 5563 include = f"{include} {column_def}" 5564 5565 alias = self.sql(expression, "alias") 5566 if alias: 5567 include = f"{include} AS {alias}" 5568 5569 return include 5570 5571 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5572 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5573 name = f"{prefix} {self.sql(expression, 'this')}" 5574 return self.func("XMLELEMENT", name, *expression.expressions) 5575 5576 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5577 this = self.sql(expression, "this") 5578 expr = self.sql(expression, "expression") 5579 expr = f"({expr})" if expr else "" 5580 return f"{this}{expr}" 5581 5582 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5583 partitions = self.expressions(expression, "partition_expressions") 5584 create = self.expressions(expression, "create_expressions") 5585 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5586 5587 def partitionbyrangepropertydynamic_sql( 5588 self, expression: exp.PartitionByRangePropertyDynamic 5589 ) -> str: 5590 start = self.sql(expression, "start") 5591 end = self.sql(expression, "end") 5592 5593 every = expression.args["every"] 5594 if isinstance(every, exp.Interval) and every.this.is_string: 5595 every.this.replace(exp.Literal.number(every.name)) 5596 5597 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5598 5599 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5600 name = self.sql(expression, "this") 5601 values = self.expressions(expression, flat=True) 5602 5603 return f"NAME {name} VALUE {values}" 5604 5605 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5606 kind = self.sql(expression, "kind") 5607 sample = self.sql(expression, "sample") 5608 return f"SAMPLE {sample} {kind}" 5609 5610 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5611 kind = self.sql(expression, "kind") 5612 option = self.sql(expression, "option") 5613 option = f" {option}" if option else "" 5614 this = self.sql(expression, "this") 5615 this = f" {this}" if this else "" 5616 columns = self.expressions(expression) 5617 columns = f" {columns}" if columns else "" 5618 return f"{kind}{option} STATISTICS{this}{columns}" 5619 5620 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5621 this = self.sql(expression, "this") 5622 columns = self.expressions(expression) 5623 inner_expression = self.sql(expression, "expression") 5624 inner_expression = f" {inner_expression}" if inner_expression else "" 5625 update_options = self.sql(expression, "update_options") 5626 update_options = f" {update_options} UPDATE" if update_options else "" 5627 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5628 5629 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5630 kind = self.sql(expression, "kind") 5631 kind = f" {kind}" if kind else "" 5632 return f"DELETE{kind} STATISTICS" 5633 5634 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5635 inner_expression = self.sql(expression, "expression") 5636 return f"LIST CHAINED ROWS{inner_expression}" 5637 5638 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5639 kind = self.sql(expression, "kind") 5640 this = self.sql(expression, "this") 5641 this = f" {this}" if this else "" 5642 inner_expression = self.sql(expression, "expression") 5643 return f"VALIDATE {kind}{this}{inner_expression}" 5644 5645 def analyze_sql(self, expression: exp.Analyze) -> str: 5646 options = self.expressions(expression, key="options", sep=" ") 5647 options = f" {options}" if options else "" 5648 kind = self.sql(expression, "kind") 5649 kind = f" {kind}" if kind else "" 5650 this = self.sql(expression, "this") 5651 this = f" {this}" if this else "" 5652 mode = self.sql(expression, "mode") 5653 mode = f" {mode}" if mode else "" 5654 properties = self.sql(expression, "properties") 5655 properties = f" {properties}" if properties else "" 5656 partition = self.sql(expression, "partition") 5657 partition = f" {partition}" if partition else "" 5658 inner_expression = self.sql(expression, "expression") 5659 inner_expression = f" {inner_expression}" if inner_expression else "" 5660 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5661 5662 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5663 this = self.sql(expression, "this") 5664 namespaces = self.expressions(expression, key="namespaces") 5665 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5666 passing = self.expressions(expression, key="passing") 5667 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5668 columns = self.expressions(expression, key="columns") 5669 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5670 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5671 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5672 5673 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5674 this = self.sql(expression, "this") 5675 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5676 5677 def export_sql(self, expression: exp.Export) -> str: 5678 this = self.sql(expression, "this") 5679 connection = self.sql(expression, "connection") 5680 connection = f"WITH CONNECTION {connection} " if connection else "" 5681 options = self.sql(expression, "options") 5682 return f"EXPORT DATA {connection}{options} AS {this}" 5683 5684 def declare_sql(self, expression: exp.Declare) -> str: 5685 replace = "OR REPLACE " if expression.args.get("replace") else "" 5686 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 5687 5688 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5689 variables = self.expressions(expression, "this") 5690 default = self.sql(expression, "default") 5691 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5692 5693 kind = self.sql(expression, "kind") 5694 if isinstance(expression.args.get("kind"), exp.Schema): 5695 kind = f"TABLE {kind}" 5696 5697 kind = f" {kind}" if kind else "" 5698 5699 return f"{variables}{kind}{default}" 5700 5701 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5702 kind = self.sql(expression, "kind") 5703 this = self.sql(expression, "this") 5704 set = self.sql(expression, "expression") 5705 using = self.sql(expression, "using") 5706 using = f" USING {using}" if using else "" 5707 5708 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5709 5710 return f"{kind_sql} {this} SET {set}{using}" 5711 5712 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5713 params = self.expressions(expression, key="params", flat=True) 5714 return self.func(expression.name, *expression.expressions) + f"({params})" 5715 5716 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5717 return self.func(expression.name, *expression.expressions) 5718 5719 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5720 return self.anonymousaggfunc_sql(expression) 5721 5722 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5723 return self.parameterizedagg_sql(expression) 5724 5725 def show_sql(self, expression: exp.Show) -> str: 5726 self.unsupported("Unsupported SHOW statement") 5727 return "" 5728 5729 def install_sql(self, expression: exp.Install) -> str: 5730 self.unsupported("Unsupported INSTALL statement") 5731 return "" 5732 5733 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5734 # Snowflake GET/PUT statements: 5735 # PUT <file> <internalStage> <properties> 5736 # GET <internalStage> <file> <properties> 5737 props = expression.args.get("properties") 5738 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5739 this = self.sql(expression, "this") 5740 target = self.sql(expression, "target") 5741 5742 if isinstance(expression, exp.Put): 5743 return f"PUT {this} {target}{props_sql}" 5744 else: 5745 return f"GET {target} {this}{props_sql}" 5746 5747 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5748 this = self.sql(expression, "this") 5749 expr = self.sql(expression, "expression") 5750 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5751 return f"TRANSLATE({this} USING {expr}{with_error})" 5752 5753 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5754 if self.SUPPORTS_DECODE_CASE: 5755 return self.func("DECODE", *expression.expressions) 5756 5757 decode_expr, *expressions = expression.expressions 5758 5759 ifs = [] 5760 for search, result in zip(expressions[::2], expressions[1::2]): 5761 if isinstance(search, exp.Literal): 5762 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 5763 elif isinstance(search, exp.Null): 5764 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 5765 else: 5766 if isinstance(search, exp.Binary): 5767 search = exp.paren(search) 5768 5769 cond = exp.or_( 5770 decode_expr.eq(search), 5771 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5772 copy=False, 5773 ) 5774 ifs.append(exp.If(this=cond, true=result)) 5775 5776 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5777 return self.sql(case) 5778 5779 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5780 this = self.sql(expression, "this") 5781 this = self.seg(this, sep="") 5782 dimensions = self.expressions( 5783 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5784 ) 5785 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5786 metrics = self.expressions( 5787 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5788 ) 5789 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5790 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5791 facts = self.seg(f"FACTS {facts}") if facts else "" 5792 where = self.sql(expression, "where") 5793 where = self.seg(f"WHERE {where}") if where else "" 5794 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5795 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5796 5797 def getextract_sql(self, expression: exp.GetExtract) -> str: 5798 this = expression.this 5799 expr = expression.expression 5800 5801 if not this.type or not expression.type: 5802 import sqlglot.optimizer.annotate_types 5803 5804 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 5805 5806 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 5807 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5808 5809 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5810 5811 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5812 return self.sql( 5813 exp.DateAdd( 5814 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5815 expression=expression.this, 5816 unit=exp.var("DAY"), 5817 ) 5818 ) 5819 5820 def space_sql(self: Generator, expression: exp.Space) -> str: 5821 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5822 5823 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5824 return f"BUILD {self.sql(expression, 'this')}" 5825 5826 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5827 method = self.sql(expression, "method") 5828 kind = expression.args.get("kind") 5829 if not kind: 5830 return f"REFRESH {method}" 5831 5832 every = self.sql(expression, "every") 5833 unit = self.sql(expression, "unit") 5834 every = f" EVERY {every} {unit}" if every else "" 5835 starts = self.sql(expression, "starts") 5836 starts = f" STARTS {starts}" if starts else "" 5837 5838 return f"REFRESH {method} ON {kind}{every}{starts}" 5839 5840 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5841 self.unsupported("The model!attribute syntax is not supported") 5842 return "" 5843 5844 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5845 return self.func("DIRECTORY", expression.this) 5846 5847 def uuid_sql(self, expression: exp.Uuid) -> str: 5848 is_string = expression.args.get("is_string", False) 5849 uuid_func_sql = self.func("UUID") 5850 5851 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5852 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 5853 5854 return uuid_func_sql 5855 5856 def initcap_sql(self, expression: exp.Initcap) -> str: 5857 delimiters = expression.expression 5858 5859 if delimiters: 5860 # do not generate delimiters arg if we are round-tripping from default delimiters 5861 if ( 5862 delimiters.is_string 5863 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5864 ): 5865 delimiters = None 5866 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5867 self.unsupported("INITCAP does not support custom delimiters") 5868 delimiters = None 5869 5870 return self.func("INITCAP", expression.this, delimiters) 5871 5872 def localtime_sql(self, expression: exp.Localtime) -> str: 5873 this = expression.this 5874 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5875 5876 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 5877 this = expression.this 5878 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5879 5880 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5881 this = expression.this.name.upper() 5882 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5883 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5884 return "WEEK" 5885 5886 return self.func("WEEK", expression.this) 5887 5888 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5889 this = self.expressions(expression) 5890 charset = self.sql(expression, "charset") 5891 using = f" USING {charset}" if charset else "" 5892 return self.func(name, this + using) 5893 5894 def block_sql(self, expression: exp.Block) -> str: 5895 expressions = self.expressions(expression, sep="; ", flat=True) 5896 return f"{expressions}" if expressions else "" 5897 5898 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 5899 self.unsupported("Unsupported Stored Procedure syntax") 5900 return "" 5901 5902 def ifblock_sql(self, expression: exp.IfBlock) -> str: 5903 self.unsupported("Unsupported If block syntax") 5904 return "" 5905 5906 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 5907 self.unsupported("Unsupported While block syntax") 5908 return "" 5909 5910 def execute_sql(self, expression: exp.Execute) -> str: 5911 self.unsupported("Unsupported Execute syntax") 5912 return "" 5913 5914 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 5915 self.unsupported("Unsupported Execute syntax") 5916 return "" 5917 5918 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 5919 props = self.expressions(expression, sep=" ") 5920 return f"MODIFY {props}" 5921 5922 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 5923 kind = expression.args.get("kind") 5924 return f"USING {kind} {self.sql(expression, 'this')}" 5925 5926 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 5927 this = self.sql(expression, "this") 5928 to = self.sql(expression, "to") 5929 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 the LikeProperty needs to be specified inside of the schema clause 469 LIKE_PROPERTY_INSIDE_SCHEMA = False 470 471 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 472 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 473 MULTI_ARG_DISTINCT = True 474 475 # Whether the JSON extraction operators expect a value of type JSON 476 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 477 478 # Whether bracketed keys like ["foo"] are supported in JSON paths 479 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 480 481 # Whether to escape keys using single quotes in JSON paths 482 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 483 484 # The JSONPathPart expressions supported by this dialect 485 SUPPORTED_JSON_PATH_PARTS: t.ClassVar = ALL_JSON_PATH_PARTS.copy() 486 487 # Whether any(f(x) for x in array) can be implemented by this dialect 488 CAN_IMPLEMENT_ARRAY_ANY = False 489 490 # Whether the function TO_NUMBER is supported 491 SUPPORTS_TO_NUMBER = True 492 493 # Whether EXCLUDE in window specification is supported 494 SUPPORTS_WINDOW_EXCLUDE = False 495 496 # Whether or not set op modifiers apply to the outer set op or select. 497 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 498 # True means limit 1 happens after the set op, False means it it happens on y. 499 SET_OP_MODIFIERS = True 500 501 # Whether parameters from COPY statement are wrapped in parentheses 502 COPY_PARAMS_ARE_WRAPPED = True 503 504 # Whether values of params are set with "=" token or empty space 505 COPY_PARAMS_EQ_REQUIRED = False 506 507 # Whether COPY statement has INTO keyword 508 COPY_HAS_INTO_KEYWORD = True 509 510 # Whether the conditional TRY(expression) function is supported 511 TRY_SUPPORTED = True 512 513 # Whether the UESCAPE syntax in unicode strings is supported 514 SUPPORTS_UESCAPE = True 515 516 # Function used to replace escaped unicode codes in unicode strings 517 UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = None 518 519 # The keyword to use when generating a star projection with excluded columns 520 STAR_EXCEPT = "EXCEPT" 521 522 # The HEX function name 523 HEX_FUNC = "HEX" 524 525 # The keywords to use when prefixing & separating WITH based properties 526 WITH_PROPERTIES_PREFIX = "WITH" 527 528 # Whether to quote the generated expression of exp.JsonPath 529 QUOTE_JSON_PATH = True 530 531 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 532 PAD_FILL_PATTERN_IS_REQUIRED = False 533 534 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 535 SUPPORTS_EXPLODING_PROJECTIONS = True 536 537 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 538 ARRAY_CONCAT_IS_VAR_LEN = True 539 540 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 541 SUPPORTS_CONVERT_TIMEZONE = False 542 543 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 544 SUPPORTS_MEDIAN = True 545 546 # Whether UNIX_SECONDS(timestamp) is supported 547 SUPPORTS_UNIX_SECONDS = False 548 549 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 550 ALTER_SET_WRAPPED = False 551 552 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 553 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 554 # TODO: The normalization should be done by default once we've tested it across all dialects. 555 NORMALIZE_EXTRACT_DATE_PARTS = False 556 557 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 558 PARSE_JSON_NAME: str | None = "PARSE_JSON" 559 560 # The function name of the exp.ArraySize expression 561 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 562 563 # The syntax to use when altering the type of a column 564 ALTER_SET_TYPE = "SET DATA TYPE" 565 566 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 567 # None -> Doesn't support it at all 568 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 569 # True (Postgres) -> Explicitly requires it 570 ARRAY_SIZE_DIM_REQUIRED: bool | None = None 571 572 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 573 SUPPORTS_DECODE_CASE = True 574 575 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 576 SUPPORTS_BETWEEN_FLAGS = False 577 578 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 579 SUPPORTS_LIKE_QUANTIFIERS = True 580 581 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 582 MATCH_AGAINST_TABLE_PREFIX: str | None = None 583 584 # Whether to include the VARIABLE keyword for SET assignments 585 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 586 587 # The keyword to use for default value assignment in DECLARE statements 588 DECLARE_DEFAULT_ASSIGNMENT = "=" 589 590 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 591 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 592 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 593 UPDATE_STATEMENT_SUPPORTS_FROM = True 594 595 # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation. 596 STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True 597 598 # Whether DROP and ALTER statements against Iceberg tables include 'ICEBERG', e.g.: 599 # - Snowflake: DROP ICEBERG TABLE a.b; 600 # - DuckDB: DROP TABLE a.b; 601 SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY = True 602 603 TYPE_MAPPING: t.ClassVar = { 604 exp.DType.DATETIME2: "TIMESTAMP", 605 exp.DType.NCHAR: "CHAR", 606 exp.DType.NVARCHAR: "VARCHAR", 607 exp.DType.MEDIUMTEXT: "TEXT", 608 exp.DType.LONGTEXT: "TEXT", 609 exp.DType.TINYTEXT: "TEXT", 610 exp.DType.BLOB: "VARBINARY", 611 exp.DType.MEDIUMBLOB: "BLOB", 612 exp.DType.LONGBLOB: "BLOB", 613 exp.DType.TINYBLOB: "BLOB", 614 exp.DType.INET: "INET", 615 exp.DType.ROWVERSION: "VARBINARY", 616 exp.DType.SMALLDATETIME: "TIMESTAMP", 617 } 618 619 UNSUPPORTED_TYPES: t.ClassVar[set[exp.DType]] = set() 620 621 TIME_PART_SINGULARS: t.ClassVar = { 622 "MICROSECONDS": "MICROSECOND", 623 "SECONDS": "SECOND", 624 "MINUTES": "MINUTE", 625 "HOURS": "HOUR", 626 "DAYS": "DAY", 627 "WEEKS": "WEEK", 628 "MONTHS": "MONTH", 629 "QUARTERS": "QUARTER", 630 "YEARS": "YEAR", 631 } 632 633 AFTER_HAVING_MODIFIER_TRANSFORMS: t.ClassVar = { 634 "cluster": lambda self, e: self.sql(e, "cluster"), 635 "distribute": lambda self, e: self.sql(e, "distribute"), 636 "sort": lambda self, e: self.sql(e, "sort"), 637 **AFTER_HAVING_MODIFIER_TRANSFORMS, 638 } 639 640 TOKEN_MAPPING: t.ClassVar[dict[TokenType, str]] = {} 641 642 STRUCT_DELIMITER: t.ClassVar = ("<", ">") 643 644 PARAMETER_TOKEN = "@" 645 NAMED_PLACEHOLDER_TOKEN = ":" 646 647 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.ClassVar[set[str]] = set() 648 649 PROPERTIES_LOCATION: t.ClassVar = { 650 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 651 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 652 exp.ApiProperty: exp.Properties.Location.POST_CREATE, 653 exp.ApplicationProperty: exp.Properties.Location.POST_CREATE, 654 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 655 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 656 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 657 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 658 exp.CatalogProperty: exp.Properties.Location.POST_CREATE, 659 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 660 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 661 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 662 exp.ComputeProperty: exp.Properties.Location.POST_CREATE, 663 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 664 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 665 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 666 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 667 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 668 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 669 exp.DatabaseProperty: exp.Properties.Location.POST_CREATE, 670 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 671 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 672 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 673 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 674 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 675 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 676 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 677 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 678 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 679 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 680 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 681 exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA, 682 exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA, 683 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 684 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 685 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 686 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 687 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 688 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 689 exp.HeapProperty: exp.Properties.Location.POST_WITH, 690 exp.HybridProperty: exp.Properties.Location.POST_CREATE, 691 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 692 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 693 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 694 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 695 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 696 exp.JournalProperty: exp.Properties.Location.POST_NAME, 697 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 698 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 699 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 700 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 701 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 702 exp.LogProperty: exp.Properties.Location.POST_NAME, 703 exp.MaskingProperty: exp.Properties.Location.POST_CREATE, 704 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 705 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 706 exp.ModuleProperty: exp.Properties.Location.POST_SCHEMA, 707 exp.NetworkProperty: exp.Properties.Location.POST_CREATE, 708 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 709 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 710 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 711 exp.Order: exp.Properties.Location.POST_SCHEMA, 712 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 713 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 714 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 715 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 716 exp.Property: exp.Properties.Location.POST_WITH, 717 exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA, 718 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 719 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 720 exp.RollupProperty: exp.Properties.Location.UNSUPPORTED, 721 exp.RowAccessProperty: exp.Properties.Location.UNSUPPORTED, 722 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 723 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 724 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 725 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 726 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 727 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 728 exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE, 729 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 730 exp.Set: exp.Properties.Location.POST_SCHEMA, 731 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 732 exp.SetProperty: exp.Properties.Location.POST_CREATE, 733 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 734 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 735 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 736 exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION, 737 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 738 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 739 exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA, 740 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 741 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 742 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 743 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 744 exp.Tags: exp.Properties.Location.POST_WITH, 745 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 746 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 747 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 748 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 749 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 750 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 751 exp.UsingProperty: exp.Properties.Location.POST_EXPRESSION, 752 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 753 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 754 exp.VirtualProperty: exp.Properties.Location.POST_CREATE, 755 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 756 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 757 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 758 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 759 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 760 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 761 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 762 } 763 764 # Keywords that can't be used as unquoted identifier names 765 RESERVED_KEYWORDS: t.ClassVar[set[str]] = set() 766 767 # Exprs whose comments are separated from them for better formatting 768 WITH_SEPARATED_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 769 exp.Command, 770 exp.Create, 771 exp.Describe, 772 exp.Delete, 773 exp.Drop, 774 exp.From, 775 exp.Insert, 776 exp.Join, 777 exp.MultitableInserts, 778 exp.Order, 779 exp.Group, 780 exp.Having, 781 exp.Select, 782 exp.SetOperation, 783 exp.Update, 784 exp.Where, 785 exp.With, 786 ) 787 788 # Exprs that should not have their comments generated in maybe_comment 789 EXCLUDE_COMMENTS: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 790 exp.Binary, 791 exp.SetOperation, 792 ) 793 794 # Exprs that can remain unwrapped when appearing in the context of an INTERVAL 795 UNWRAPPED_INTERVAL_VALUES: t.ClassVar[tuple[type[exp.Expr], ...]] = ( 796 exp.Column, 797 exp.Literal, 798 exp.Neg, 799 exp.Paren, 800 ) 801 802 PARAMETERIZABLE_TEXT_TYPES: t.ClassVar = { 803 exp.DType.NVARCHAR, 804 exp.DType.VARCHAR, 805 exp.DType.CHAR, 806 exp.DType.NCHAR, 807 } 808 809 # Exprs that need to have all CTEs under them bubbled up to them 810 EXPRESSIONS_WITHOUT_NESTED_CTES: t.ClassVar[set[type[exp.Expr]]] = set() 811 812 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.ClassVar[tuple[type[exp.Expr], ...]] = () 813 814 SAFE_JSON_PATH_KEY_RE: t.ClassVar = exp.SAFE_IDENTIFIER_RE 815 816 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 817 818 __slots__ = ( 819 "pretty", 820 "identify", 821 "normalize", 822 "pad", 823 "_indent", 824 "normalize_functions", 825 "unsupported_level", 826 "max_unsupported", 827 "leading_comma", 828 "max_text_width", 829 "comments", 830 "dialect", 831 "unsupported_messages", 832 "_escaped_quote_end", 833 "_escaped_byte_quote_end", 834 "_escaped_identifier_end", 835 "_next_name", 836 "_identifier_start", 837 "_identifier_end", 838 "_quote_json_path_key_using_brackets", 839 "_dispatch", 840 ) 841 842 def __init__( 843 self, 844 pretty: bool | int | None = None, 845 identify: str | bool = False, 846 normalize: bool = False, 847 pad: int = 2, 848 indent: int = 2, 849 normalize_functions: str | bool | None = None, 850 unsupported_level: ErrorLevel = ErrorLevel.WARN, 851 max_unsupported: int = 3, 852 leading_comma: bool = False, 853 max_text_width: int = 80, 854 comments: bool = True, 855 dialect: DialectType = None, 856 ): 857 import sqlglot 858 import sqlglot.dialects.dialect 859 860 self.pretty = pretty if pretty is not None else sqlglot.pretty 861 self.identify = identify 862 self.normalize = normalize 863 self.pad = pad 864 self._indent = indent 865 self.unsupported_level = unsupported_level 866 self.max_unsupported = max_unsupported 867 self.leading_comma = leading_comma 868 self.max_text_width = max_text_width 869 self.comments = comments 870 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 871 872 # This is both a Dialect property and a Generator argument, so we prioritize the latter 873 self.normalize_functions = ( 874 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 875 ) 876 877 self.unsupported_messages: list[str] = [] 878 self._escaped_quote_end: str = ( 879 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 880 ) 881 self._escaped_byte_quote_end: str = ( 882 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 883 if self.dialect.BYTE_END 884 else "" 885 ) 886 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 887 888 self._next_name = name_sequence("_t") 889 890 self._identifier_start = self.dialect.IDENTIFIER_START 891 self._identifier_end = self.dialect.IDENTIFIER_END 892 893 self._quote_json_path_key_using_brackets = True 894 895 cls = type(self) 896 dispatch = _DISPATCH_CACHE.get(cls) 897 if dispatch is None: 898 dispatch = _build_dispatch(cls) 899 _DISPATCH_CACHE[cls] = dispatch 900 self._dispatch = dispatch 901 902 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 903 """ 904 Generates the SQL string corresponding to the given syntax tree. 905 906 Args: 907 expression: The syntax tree. 908 copy: Whether to copy the expression. The generator performs mutations so 909 it is safer to copy. 910 911 Returns: 912 The SQL string corresponding to `expression`. 913 """ 914 if copy: 915 expression = expression.copy() 916 917 expression = self.preprocess(expression) 918 919 self.unsupported_messages = [] 920 sql = self.sql(expression).strip() 921 922 if self.pretty: 923 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 924 925 if self.unsupported_level == ErrorLevel.IGNORE: 926 return sql 927 928 if self.unsupported_level == ErrorLevel.WARN: 929 for msg in self.unsupported_messages: 930 logger.warning(msg) 931 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 932 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 933 934 return sql 935 936 def preprocess(self, expression: exp.Expr) -> exp.Expr: 937 """Apply generic preprocessing transformations to a given expression.""" 938 expression = self._move_ctes_to_top_level(expression) 939 940 if self.ENSURE_BOOLS: 941 import sqlglot.transforms 942 943 expression = sqlglot.transforms.ensure_bools(expression) 944 945 return expression 946 947 def _move_ctes_to_top_level(self, expression: E) -> E: 948 if ( 949 not expression.parent 950 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 951 and any(node.parent is not expression for node in expression.find_all(exp.With)) 952 ): 953 import sqlglot.transforms 954 955 expression = sqlglot.transforms.move_ctes_to_top_level(expression) 956 return expression 957 958 def unsupported(self, message: str) -> None: 959 if self.unsupported_level == ErrorLevel.IMMEDIATE: 960 raise UnsupportedError(message) 961 self.unsupported_messages.append(message) 962 963 def sep(self, sep: str = " ") -> str: 964 return f"{sep.strip()}\n" if self.pretty else sep 965 966 def seg(self, sql: str, sep: str = " ") -> str: 967 return f"{self.sep(sep)}{sql}" 968 969 def sanitize_comment(self, comment: str) -> str: 970 comment = " " + comment if comment[0].strip() else comment 971 comment = comment + " " if comment[-1].strip() else comment 972 973 # Escape block comment markers to prevent premature closure or unintended nesting. 974 # This is necessary because single-line comments (--) are converted to block comments 975 # (/* */) on output, and any */ in the original text would close the comment early. 976 comment = comment.replace("*/", "* /").replace("/*", "/ *") 977 978 return comment 979 980 def maybe_comment( 981 self, 982 sql: str, 983 expression: exp.Expr | None = None, 984 comments: list[str] | None = None, 985 separated: bool = False, 986 ) -> str: 987 comments = ( 988 ((expression and expression.comments) if comments is None else comments) # type: ignore 989 if self.comments 990 else None 991 ) 992 993 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 994 return sql 995 996 comments_sql = " ".join( 997 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 998 ) 999 1000 if not comments_sql: 1001 return sql 1002 1003 comments_sql = self._replace_line_breaks(comments_sql) 1004 1005 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1006 return ( 1007 f"{self.sep()}{comments_sql}{sql}" 1008 if not sql or sql[0].isspace() 1009 else f"{comments_sql}{self.sep()}{sql}" 1010 ) 1011 1012 return f"{sql} {comments_sql}" 1013 1014 def wrap(self, expression: exp.Expr | str) -> str: 1015 this_sql = ( 1016 self.sql(expression) 1017 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1018 else self.sql(expression, "this") 1019 ) 1020 if not this_sql: 1021 return "()" 1022 1023 this_sql = self.indent(this_sql, level=1, pad=0) 1024 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 1025 1026 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 1027 original = self.identify 1028 self.identify = False 1029 result = func(*args, **kwargs) 1030 self.identify = original 1031 return result 1032 1033 def normalize_func(self, name: str) -> str: 1034 if self.normalize_functions == "upper" or self.normalize_functions is True: 1035 return name.upper() 1036 if self.normalize_functions == "lower": 1037 return name.lower() 1038 return name 1039 1040 def indent( 1041 self, 1042 sql: str, 1043 level: int = 0, 1044 pad: int | None = None, 1045 skip_first: bool = False, 1046 skip_last: bool = False, 1047 ) -> str: 1048 if not self.pretty or not sql: 1049 return sql 1050 1051 pad = self.pad if pad is None else pad 1052 lines = sql.split("\n") 1053 1054 return "\n".join( 1055 ( 1056 line 1057 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1058 else f"{' ' * (level * self._indent + pad)}{line}" 1059 ) 1060 for i, line in enumerate(lines) 1061 ) 1062 1063 def sql( 1064 self, 1065 expression: str | exp.Expr | None, 1066 key: str | None = None, 1067 comment: bool = True, 1068 ) -> str: 1069 if not expression: 1070 return "" 1071 1072 if isinstance(expression, str): 1073 return expression 1074 1075 if key: 1076 value = expression.args.get(key) 1077 if value: 1078 return self.sql(value) 1079 return "" 1080 1081 handler = self._dispatch.get(expression.__class__) 1082 1083 if handler: 1084 sql = handler(self, expression) 1085 elif isinstance(expression, exp.Func): 1086 sql = self.function_fallback_sql(expression) 1087 elif isinstance(expression, exp.Property): 1088 sql = self.property_sql(expression) 1089 else: 1090 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1091 1092 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1093 1094 def uncache_sql(self, expression: exp.Uncache) -> str: 1095 table = self.sql(expression, "this") 1096 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1097 return f"UNCACHE TABLE{exists_sql} {table}" 1098 1099 def cache_sql(self, expression: exp.Cache) -> str: 1100 lazy = " LAZY" if expression.args.get("lazy") else "" 1101 table = self.sql(expression, "this") 1102 options = expression.args.get("options") 1103 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1104 sql = self.sql(expression, "expression") 1105 sql = f" AS{self.sep()}{sql}" if sql else "" 1106 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1107 return self.prepend_ctes(expression, sql) 1108 1109 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1110 default = "DEFAULT " if expression.args.get("default") else "" 1111 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1112 1113 def column_parts(self, expression: exp.Column) -> str: 1114 return ".".join( 1115 self.sql(part) 1116 for part in ( 1117 expression.args.get("catalog"), 1118 expression.args.get("db"), 1119 expression.args.get("table"), 1120 expression.args.get("this"), 1121 ) 1122 if part 1123 ) 1124 1125 def column_sql(self, expression: exp.Column) -> str: 1126 join_mark = " (+)" if expression.args.get("join_mark") else "" 1127 1128 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1129 join_mark = "" 1130 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1131 1132 return f"{self.column_parts(expression)}{join_mark}" 1133 1134 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1135 return self.column_sql(expression) 1136 1137 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1138 this = self.sql(expression, "this") 1139 this = f" {this}" if this else "" 1140 position = self.sql(expression, "position") 1141 return f"{position}{this}" 1142 1143 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1144 column = self.sql(expression, "this") 1145 kind = self.sql(expression, "kind") 1146 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1147 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1148 kind = f"{sep}{kind}" if kind else "" 1149 constraints = f" {constraints}" if constraints else "" 1150 position = self.sql(expression, "position") 1151 position = f" {position}" if position else "" 1152 1153 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1154 kind = "" 1155 1156 return f"{exists}{column}{kind}{constraints}{position}" 1157 1158 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1159 this = self.sql(expression, "this") 1160 kind_sql = self.sql(expression, "kind").strip() 1161 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1162 1163 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1164 this = self.sql(expression, "this") 1165 if expression.args.get("not_null"): 1166 persisted = " PERSISTED NOT NULL" 1167 elif expression.args.get("persisted"): 1168 persisted = " PERSISTED" 1169 else: 1170 persisted = "" 1171 1172 return f"AS {this}{persisted}" 1173 1174 def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str: 1175 return self.token_sql(TokenType.AUTO_INCREMENT) 1176 1177 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1178 if isinstance(expression.this, list): 1179 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1180 else: 1181 this = self.sql(expression, "this") 1182 1183 return f"COMPRESS {this}" 1184 1185 def generatedasidentitycolumnconstraint_sql( 1186 self, expression: exp.GeneratedAsIdentityColumnConstraint 1187 ) -> str: 1188 this = "" 1189 if expression.this is not None: 1190 on_null = " ON NULL" if expression.args.get("on_null") else "" 1191 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1192 1193 start = expression.args.get("start") 1194 start = f"START WITH {start}" if start else "" 1195 increment = expression.args.get("increment") 1196 increment = f" INCREMENT BY {increment}" if increment else "" 1197 minvalue = expression.args.get("minvalue") 1198 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1199 maxvalue = expression.args.get("maxvalue") 1200 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1201 cycle = expression.args.get("cycle") 1202 cycle_sql = "" 1203 1204 if cycle is not None: 1205 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1206 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1207 1208 sequence_opts = "" 1209 if start or increment or cycle_sql: 1210 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1211 sequence_opts = f" ({sequence_opts.strip()})" 1212 1213 expr = self.sql(expression, "expression") 1214 expr = f"({expr})" if expr else "IDENTITY" 1215 1216 return f"GENERATED{this} AS {expr}{sequence_opts}" 1217 1218 def generatedasrowcolumnconstraint_sql( 1219 self, expression: exp.GeneratedAsRowColumnConstraint 1220 ) -> str: 1221 start = "START" if expression.args.get("start") else "END" 1222 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1223 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1224 1225 def periodforsystemtimeconstraint_sql( 1226 self, expression: exp.PeriodForSystemTimeConstraint 1227 ) -> str: 1228 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1229 1230 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1231 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1232 1233 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1234 desc = expression.args.get("desc") 1235 if desc is not None: 1236 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1237 options = self.expressions(expression, key="options", flat=True, sep=" ") 1238 options = f" {options}" if options else "" 1239 return f"PRIMARY KEY{options}" 1240 1241 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1242 this = self.sql(expression, "this") 1243 this = f" {this}" if this else "" 1244 index_type = expression.args.get("index_type") 1245 index_type = f" USING {index_type}" if index_type else "" 1246 on_conflict = self.sql(expression, "on_conflict") 1247 on_conflict = f" {on_conflict}" if on_conflict else "" 1248 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1249 options = self.expressions(expression, key="options", flat=True, sep=" ") 1250 options = f" {options}" if options else "" 1251 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1252 1253 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1254 input_ = expression.args.get("input_") 1255 output = expression.args.get("output") 1256 variadic = expression.args.get("variadic") 1257 1258 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1259 if variadic: 1260 return "VARIADIC" 1261 1262 if input_ and output: 1263 return f"IN{self.INOUT_SEPARATOR}OUT" 1264 if input_: 1265 return "IN" 1266 if output: 1267 return "OUT" 1268 1269 return "" 1270 1271 def createable_sql(self, expression: exp.Create, locations: defaultdict) -> str: 1272 return self.sql(expression, "this") 1273 1274 def create_sql(self, expression: exp.Create) -> str: 1275 kind = self.sql(expression, "kind") 1276 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1277 1278 properties = expression.args.get("properties") 1279 1280 if ( 1281 kind == "TRIGGER" 1282 and properties 1283 and properties.expressions 1284 and isinstance(properties.expressions[0], exp.TriggerProperties) 1285 and properties.expressions[0].args.get("constraint") 1286 ): 1287 kind = f"CONSTRAINT {kind}" 1288 1289 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1290 1291 this = self.createable_sql(expression, properties_locs) 1292 1293 properties_sql = "" 1294 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1295 exp.Properties.Location.POST_WITH 1296 ): 1297 props_ast = exp.Properties( 1298 expressions=[ 1299 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1300 *properties_locs[exp.Properties.Location.POST_WITH], 1301 ] 1302 ) 1303 props_ast.parent = expression 1304 properties_sql = self.sql(props_ast) 1305 1306 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1307 properties_sql = self.sep() + properties_sql 1308 elif not self.pretty: 1309 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1310 properties_sql = f" {properties_sql}" 1311 1312 begin = " BEGIN" if expression.args.get("begin") else "" 1313 1314 expression_sql = self.sql(expression, "expression") 1315 if expression_sql: 1316 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1317 1318 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1319 postalias_props_sql = "" 1320 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1321 postalias_props_sql = self.properties( 1322 exp.Properties( 1323 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1324 ), 1325 wrapped=False, 1326 ) 1327 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1328 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1329 1330 postindex_props_sql = "" 1331 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1332 postindex_props_sql = self.properties( 1333 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1334 wrapped=False, 1335 prefix=" ", 1336 ) 1337 1338 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1339 indexes = f" {indexes}" if indexes else "" 1340 index_sql = indexes + postindex_props_sql 1341 1342 replace = " OR REPLACE" if expression.args.get("replace") else "" 1343 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1344 unique = " UNIQUE" if expression.args.get("unique") else "" 1345 1346 clustered = expression.args.get("clustered") 1347 if clustered is None: 1348 clustered_sql = "" 1349 elif clustered: 1350 clustered_sql = " CLUSTERED COLUMNSTORE" 1351 else: 1352 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1353 1354 postcreate_props_sql = "" 1355 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1356 postcreate_props_sql = self.properties( 1357 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1358 sep=" ", 1359 prefix=" ", 1360 wrapped=False, 1361 ) 1362 1363 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1364 1365 postexpression_props_sql = "" 1366 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1367 postexpression_props_sql = self.properties( 1368 exp.Properties( 1369 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1370 ), 1371 sep=" ", 1372 prefix=" ", 1373 wrapped=False, 1374 ) 1375 1376 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1377 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1378 no_schema_binding = ( 1379 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1380 ) 1381 1382 clone = self.sql(expression, "clone") 1383 clone = f" {clone}" if clone else "" 1384 1385 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1386 properties_expression = f"{expression_sql}{properties_sql}" 1387 else: 1388 properties_expression = f"{properties_sql}{expression_sql}" 1389 1390 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1391 return self.prepend_ctes(expression, expression_sql) 1392 1393 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1394 start = self.sql(expression, "start") 1395 start = f"START WITH {start}" if start else "" 1396 increment = self.sql(expression, "increment") 1397 increment = f" INCREMENT BY {increment}" if increment else "" 1398 minvalue = self.sql(expression, "minvalue") 1399 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1400 maxvalue = self.sql(expression, "maxvalue") 1401 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1402 owned = self.sql(expression, "owned") 1403 owned = f" OWNED BY {owned}" if owned else "" 1404 1405 cache = expression.args.get("cache") 1406 if cache is None: 1407 cache_str = "" 1408 elif cache is True: 1409 cache_str = " CACHE" 1410 else: 1411 cache_str = f" CACHE {cache}" 1412 1413 options = self.expressions(expression, key="options", flat=True, sep=" ") 1414 options = f" {options}" if options else "" 1415 1416 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1417 1418 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1419 timing = expression.args.get("timing", "") 1420 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1421 timing_events = f"{timing} {events}".strip() if timing or events else "" 1422 1423 parts = [timing_events, "ON", self.sql(expression, "table")] 1424 1425 if referenced_table := expression.args.get("referenced_table"): 1426 parts.extend(["FROM", self.sql(referenced_table)]) 1427 1428 if deferrable := expression.args.get("deferrable"): 1429 parts.append(deferrable) 1430 1431 if initially := expression.args.get("initially"): 1432 parts.append(f"INITIALLY {initially}") 1433 1434 if referencing := expression.args.get("referencing"): 1435 parts.append(self.sql(referencing)) 1436 1437 if for_each := expression.args.get("for_each"): 1438 parts.append(f"FOR EACH {for_each}") 1439 1440 if when := expression.args.get("when"): 1441 parts.append(f"WHEN ({self.sql(when)})") 1442 1443 parts.append(self.sql(expression, "execute")) 1444 1445 return self.sep().join(parts) 1446 1447 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1448 parts = [] 1449 1450 if old_alias := expression.args.get("old"): 1451 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1452 1453 if new_alias := expression.args.get("new"): 1454 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1455 1456 return f"REFERENCING {' '.join(parts)}" 1457 1458 def triggerevent_sql(self, expression: exp.TriggerEvent) -> str: 1459 columns = expression.args.get("columns") 1460 if columns: 1461 return f"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}" 1462 1463 return self.sql(expression, "this") 1464 1465 def clone_sql(self, expression: exp.Clone) -> str: 1466 this = self.sql(expression, "this") 1467 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1468 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1469 return f"{shallow}{keyword} {this}" 1470 1471 def describe_sql(self, expression: exp.Describe) -> str: 1472 style = expression.args.get("style") 1473 style = f" {style}" if style else "" 1474 partition = self.sql(expression, "partition") 1475 partition = f" {partition}" if partition else "" 1476 format = self.sql(expression, "format") 1477 format = f" {format}" if format else "" 1478 as_json = " AS JSON" if expression.args.get("as_json") else "" 1479 1480 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}" 1481 1482 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1483 tag = self.sql(expression, "tag") 1484 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1485 1486 def prepend_ctes(self, expression: exp.Expr, sql: str) -> str: 1487 with_ = self.sql(expression, "with_") 1488 if with_: 1489 sql = f"{with_}{self.sep()}{sql}" 1490 return sql 1491 1492 def with_sql(self, expression: exp.With) -> str: 1493 sql = self.expressions(expression, flat=True) 1494 recursive = ( 1495 "RECURSIVE " 1496 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1497 else "" 1498 ) 1499 search = self.sql(expression, "search") 1500 search = f" {search}" if search else "" 1501 1502 return f"WITH {recursive}{sql}{search}" 1503 1504 def cte_sql(self, expression: exp.CTE) -> str: 1505 alias = expression.args.get("alias") 1506 if alias: 1507 alias.add_comments(expression.pop_comments()) 1508 1509 alias_sql = self.sql(expression, "alias") 1510 1511 materialized = expression.args.get("materialized") 1512 if materialized is False: 1513 materialized = "NOT MATERIALIZED " 1514 elif materialized: 1515 materialized = "MATERIALIZED " 1516 1517 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1518 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1519 1520 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1521 1522 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1523 alias = self.sql(expression, "this") 1524 columns = self.expressions(expression, key="columns", flat=True) 1525 columns = f"({columns})" if columns else "" 1526 1527 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1528 columns = "" 1529 self.unsupported("Named columns are not supported in table alias.") 1530 1531 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1532 alias = self._next_name() 1533 1534 return f"{alias}{columns}" 1535 1536 def bitstring_sql(self, expression: exp.BitString) -> str: 1537 this = self.sql(expression, "this") 1538 if self.dialect.BIT_START: 1539 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1540 return f"{int(this, 2)}" 1541 1542 def hexstring_sql( 1543 self, expression: exp.HexString, binary_function_repr: str | None = None 1544 ) -> str: 1545 this = self.sql(expression, "this") 1546 is_integer_type = expression.args.get("is_integer") 1547 1548 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1549 not self.dialect.HEX_START and not binary_function_repr 1550 ): 1551 # Integer representation will be returned if: 1552 # - The read dialect treats the hex value as integer literal but not the write 1553 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1554 return f"{int(this, 16)}" 1555 1556 if not is_integer_type: 1557 # Read dialect treats the hex value as BINARY/BLOB 1558 if binary_function_repr: 1559 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1560 return self.func(binary_function_repr, exp.Literal.string(this)) 1561 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1562 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1563 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1564 1565 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1566 1567 def bytestring_sql(self, expression: exp.ByteString) -> str: 1568 this = self.sql(expression, "this") 1569 if self.dialect.BYTE_START: 1570 escaped_byte_string = self.escape_str( 1571 this, 1572 escape_backslash=False, 1573 delimiter=self.dialect.BYTE_END, 1574 escaped_delimiter=self._escaped_byte_quote_end, 1575 is_byte_string=True, 1576 ) 1577 is_bytes = expression.args.get("is_bytes", False) 1578 delimited_byte_string = ( 1579 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1580 ) 1581 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1582 return self.sql( 1583 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1584 ) 1585 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1586 return self.sql( 1587 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1588 ) 1589 1590 return delimited_byte_string 1591 return this 1592 1593 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1594 this = self.sql(expression, "this") 1595 escape = expression.args.get("escape") 1596 1597 if self.dialect.UNICODE_START: 1598 escape_substitute = r"\\\1" 1599 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1600 else: 1601 escape_substitute = r"\\u\1" 1602 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1603 1604 if escape: 1605 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1606 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1607 else: 1608 escape_pattern = ESCAPED_UNICODE_RE 1609 escape_sql = "" 1610 1611 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1612 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1613 1614 return f"{left_quote}{this}{right_quote}{escape_sql}" 1615 1616 def rawstring_sql(self, expression: exp.RawString) -> str: 1617 string = expression.this 1618 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1619 string = string.replace("\\", "\\\\") 1620 1621 string = self.escape_str(string, escape_backslash=False) 1622 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1623 1624 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1625 this = self.sql(expression, "this") 1626 specifier = self.sql(expression, "expression") 1627 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1628 return f"{this}{specifier}" 1629 1630 def datatype_sql(self, expression: exp.DataType) -> str: 1631 nested = "" 1632 values = "" 1633 1634 expr_nested = expression.args.get("nested") 1635 interior = ( 1636 self.expressions( 1637 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1638 ) 1639 if expr_nested and self.pretty 1640 else self.expressions(expression, flat=True) 1641 ) 1642 1643 type_value = expression.this 1644 if type_value in self.UNSUPPORTED_TYPES: 1645 self.unsupported( 1646 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1647 ) 1648 1649 type_sql: t.Any = "" 1650 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1651 type_sql = self.sql(expression, "kind") 1652 elif type_value == exp.DType.CHARACTER_SET: 1653 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1654 else: 1655 type_sql = ( 1656 self.TYPE_MAPPING.get(type_value, type_value.value) 1657 if isinstance(type_value, exp.DType) 1658 else type_value 1659 ) 1660 1661 if interior: 1662 if expr_nested: 1663 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1664 if expression.args.get("values") is not None: 1665 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1666 values = self.expressions(expression, key="values", flat=True) 1667 values = f"{delimiters[0]}{values}{delimiters[1]}" 1668 elif type_value == exp.DType.INTERVAL: 1669 nested = f" {interior}" 1670 else: 1671 nested = f"({interior})" 1672 1673 type_sql = f"{type_sql}{nested}{values}" 1674 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1675 exp.DType.TIMETZ, 1676 exp.DType.TIMESTAMPTZ, 1677 ): 1678 type_sql = f"{type_sql} WITH TIME ZONE" 1679 1680 return type_sql 1681 1682 def directory_sql(self, expression: exp.Directory) -> str: 1683 local = "LOCAL " if expression.args.get("local") else "" 1684 row_format = self.sql(expression, "row_format") 1685 row_format = f" {row_format}" if row_format else "" 1686 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1687 1688 def delete_sql(self, expression: exp.Delete) -> str: 1689 hint = self.sql(expression, "hint") 1690 this = self.sql(expression, "this") 1691 this = f" FROM {this}" if this else "" 1692 using = self.expressions(expression, key="using") 1693 using = f" USING {using}" if using else "" 1694 cluster = self.sql(expression, "cluster") 1695 cluster = f" {cluster}" if cluster else "" 1696 where = self.sql(expression, "where") 1697 returning = self.sql(expression, "returning") 1698 order = self.sql(expression, "order") 1699 limit = self.sql(expression, "limit") 1700 tables = self.expressions(expression, key="tables") 1701 tables = f" {tables}" if tables else "" 1702 if self.RETURNING_END: 1703 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1704 else: 1705 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1706 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}") 1707 1708 def drop_sql(self, expression: exp.Drop) -> str: 1709 this = self.sql(expression, "this") 1710 expressions = self.expressions(expression, flat=True) 1711 expressions = f" ({expressions})" if expressions else "" 1712 kind = expression.args["kind"] 1713 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1714 iceberg = ( 1715 " ICEBERG" 1716 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1717 else "" 1718 ) 1719 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1720 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1721 on_cluster = self.sql(expression, "cluster") 1722 on_cluster = f" {on_cluster}" if on_cluster else "" 1723 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1724 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1725 cascade = " CASCADE" if expression.args.get("cascade") else "" 1726 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1727 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1728 purge = " PURGE" if expression.args.get("purge") else "" 1729 sync = " SYNC" if expression.args.get("sync") else "" 1730 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}" 1731 1732 def set_operation(self, expression: exp.SetOperation) -> str: 1733 op_type = type(expression) 1734 op_name = op_type.key.upper() 1735 1736 distinct = expression.args.get("distinct") 1737 if ( 1738 distinct is False 1739 and op_type in (exp.Except, exp.Intersect) 1740 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1741 ): 1742 self.unsupported(f"{op_name} ALL is not supported") 1743 1744 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1745 1746 if distinct is None: 1747 distinct = default_distinct 1748 if distinct is None: 1749 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1750 1751 if distinct is default_distinct: 1752 distinct_or_all = "" 1753 else: 1754 distinct_or_all = " DISTINCT" if distinct else " ALL" 1755 1756 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1757 side_kind = f"{side_kind} " if side_kind else "" 1758 1759 by_name = " BY NAME" if expression.args.get("by_name") else "" 1760 on = self.expressions(expression, key="on", flat=True) 1761 on = f" ON ({on})" if on else "" 1762 1763 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1764 1765 def set_operations(self, expression: exp.SetOperation) -> str: 1766 if not self.SET_OP_MODIFIERS: 1767 limit = expression.args.get("limit") 1768 order = expression.args.get("order") 1769 1770 if limit or order: 1771 select = self._move_ctes_to_top_level( 1772 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1773 ) 1774 1775 if limit: 1776 select = select.limit(limit.pop(), copy=False) 1777 if order: 1778 select = select.order_by(order.pop(), copy=False) 1779 return self.sql(select) 1780 1781 sqls: list[str] = [] 1782 stack: list[str | exp.Expr] = [expression] 1783 1784 while stack: 1785 node = stack.pop() 1786 1787 if isinstance(node, exp.SetOperation): 1788 stack.append(node.expression) 1789 stack.append( 1790 self.maybe_comment( 1791 self.set_operation(node), comments=node.comments, separated=True 1792 ) 1793 ) 1794 stack.append(node.this) 1795 else: 1796 sqls.append(self.sql(node)) 1797 1798 this = self.sep().join(sqls) 1799 this = self.query_modifiers(expression, this) 1800 return self.prepend_ctes(expression, this) 1801 1802 def fetch_sql(self, expression: exp.Fetch) -> str: 1803 direction = expression.args.get("direction") 1804 direction = f" {direction}" if direction else "" 1805 count = self.sql(expression, "count") 1806 count = f" {count}" if count else "" 1807 limit_options = self.sql(expression, "limit_options") 1808 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1809 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1810 1811 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1812 percent = " PERCENT" if expression.args.get("percent") else "" 1813 rows = " ROWS" if expression.args.get("rows") else "" 1814 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1815 if not with_ties and rows: 1816 with_ties = " ONLY" 1817 return f"{percent}{rows}{with_ties}" 1818 1819 def filter_sql(self, expression: exp.Filter) -> str: 1820 if self.AGGREGATE_FILTER_SUPPORTED: 1821 this = self.sql(expression, "this") 1822 where = self.sql(expression, "expression").strip() 1823 return f"{this} FILTER({where})" 1824 1825 agg = expression.this 1826 agg_arg = agg.this 1827 cond = expression.expression.this 1828 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1829 return self.sql(agg) 1830 1831 def hint_sql(self, expression: exp.Hint) -> str: 1832 if not self.QUERY_HINTS: 1833 self.unsupported("Hints are not supported") 1834 return "" 1835 1836 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1837 1838 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1839 using = self.sql(expression, "using") 1840 using = f" USING {using}" if using else "" 1841 columns = self.expressions(expression, key="columns", flat=True) 1842 columns = f"({columns})" if columns else "" 1843 partition_by = self.expressions(expression, key="partition_by", flat=True) 1844 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1845 where = self.sql(expression, "where") 1846 include = self.expressions(expression, key="include", flat=True) 1847 if include: 1848 include = f" INCLUDE ({include})" 1849 with_storage = self.expressions(expression, key="with_storage", flat=True) 1850 with_storage = f" WITH ({with_storage})" if with_storage else "" 1851 tablespace = self.sql(expression, "tablespace") 1852 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1853 on = self.sql(expression, "on") 1854 on = f" ON {on}" if on else "" 1855 1856 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1857 1858 def index_sql(self, expression: exp.Index) -> str: 1859 unique = "UNIQUE " if expression.args.get("unique") else "" 1860 primary = "PRIMARY " if expression.args.get("primary") else "" 1861 amp = "AMP " if expression.args.get("amp") else "" 1862 name = self.sql(expression, "this") 1863 name = f"{name} " if name else "" 1864 table = self.sql(expression, "table") 1865 table = f"{self.INDEX_ON} {table}" if table else "" 1866 1867 index = "INDEX " if not table else "" 1868 1869 params = self.sql(expression, "params") 1870 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1871 1872 def identifier_sql(self, expression: exp.Identifier) -> str: 1873 text = expression.name 1874 lower = text.lower() 1875 quoted = expression.quoted 1876 text = lower if self.normalize and not quoted else text 1877 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1878 if ( 1879 quoted 1880 or self.dialect.can_quote(expression, self.identify) 1881 or lower in self.RESERVED_KEYWORDS 1882 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1883 ): 1884 text = f"{self._identifier_start}{text}{self._identifier_end}" 1885 return text 1886 1887 def hex_sql(self, expression: exp.Hex) -> str: 1888 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1889 if self.dialect.HEX_LOWERCASE: 1890 text = self.func("LOWER", text) 1891 1892 return text 1893 1894 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1895 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1896 if not self.dialect.HEX_LOWERCASE: 1897 text = self.func("LOWER", text) 1898 return text 1899 1900 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1901 input_format = self.sql(expression, "input_format") 1902 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1903 output_format = self.sql(expression, "output_format") 1904 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1905 return self.sep().join((input_format, output_format)) 1906 1907 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1908 string = self.sql(exp.Literal.string(expression.name)) 1909 return f"{prefix}{string}" 1910 1911 def partition_sql(self, expression: exp.Partition) -> str: 1912 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1913 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1914 1915 def properties_sql(self, expression: exp.Properties) -> str: 1916 root_properties = [] 1917 with_properties = [] 1918 1919 for p in expression.expressions: 1920 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1921 if p_loc == exp.Properties.Location.POST_WITH: 1922 with_properties.append(p) 1923 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1924 root_properties.append(p) 1925 1926 root_props_ast = exp.Properties(expressions=root_properties) 1927 root_props_ast.parent = expression.parent 1928 1929 with_props_ast = exp.Properties(expressions=with_properties) 1930 with_props_ast.parent = expression.parent 1931 1932 root_props = self.root_properties(root_props_ast) 1933 with_props = self.with_properties(with_props_ast) 1934 1935 if root_props and with_props and not self.pretty: 1936 with_props = " " + with_props 1937 1938 return root_props + with_props 1939 1940 def root_properties(self, properties: exp.Properties) -> str: 1941 if properties.expressions: 1942 return self.expressions(properties, indent=False, sep=" ") 1943 return "" 1944 1945 def properties( 1946 self, 1947 properties: exp.Properties, 1948 prefix: str = "", 1949 sep: str = ", ", 1950 suffix: str = "", 1951 wrapped: bool = True, 1952 ) -> str: 1953 if properties.expressions: 1954 expressions = self.expressions(properties, sep=sep, indent=False) 1955 if expressions: 1956 expressions = self.wrap(expressions) if wrapped else expressions 1957 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1958 return "" 1959 1960 def with_properties(self, properties: exp.Properties) -> str: 1961 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1962 1963 def locate_properties(self, properties: exp.Properties) -> defaultdict: 1964 properties_locs = defaultdict(list) 1965 for p in properties.expressions: 1966 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1967 if p_loc != exp.Properties.Location.UNSUPPORTED: 1968 properties_locs[p_loc].append(p) 1969 else: 1970 self.unsupported(f"Unsupported property {p.key}") 1971 1972 return properties_locs 1973 1974 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1975 if isinstance(expression.this, exp.Dot): 1976 return self.sql(expression, "this") 1977 return f"'{expression.name}'" if string_key else expression.name 1978 1979 def property_sql(self, expression: exp.Property) -> str: 1980 property_cls = expression.__class__ 1981 if property_cls == exp.Property: 1982 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1983 1984 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1985 if not property_name: 1986 self.unsupported(f"Unsupported property {expression.key}") 1987 1988 return f"{property_name}={self.sql(expression, 'this')}" 1989 1990 def uuidproperty_sql(self, expression: exp.UuidProperty) -> str: 1991 return f"UUID {self.sql(expression, 'this')}" 1992 1993 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1994 if self.SUPPORTS_CREATE_TABLE_LIKE: 1995 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1996 options = f" {options}" if options else "" 1997 1998 like = f"LIKE {self.sql(expression, 'this')}{options}" 1999 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2000 like = f"({like})" 2001 2002 return like 2003 2004 if expression.expressions: 2005 self.unsupported("Transpilation of LIKE property options is unsupported") 2006 2007 select = exp.select("*").from_(expression.this).limit(0) 2008 return f"AS {self.sql(select)}" 2009 2010 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 2011 no = "NO " if expression.args.get("no") else "" 2012 protection = " PROTECTION" if expression.args.get("protection") else "" 2013 return f"{no}FALLBACK{protection}" 2014 2015 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2016 no = "NO " if expression.args.get("no") else "" 2017 local = expression.args.get("local") 2018 local = f"{local} " if local else "" 2019 dual = "DUAL " if expression.args.get("dual") else "" 2020 before = "BEFORE " if expression.args.get("before") else "" 2021 after = "AFTER " if expression.args.get("after") else "" 2022 return f"{no}{local}{dual}{before}{after}JOURNAL" 2023 2024 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 2025 freespace = self.sql(expression, "this") 2026 percent = " PERCENT" if expression.args.get("percent") else "" 2027 return f"FREESPACE={freespace}{percent}" 2028 2029 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 2030 if expression.args.get("default"): 2031 property = "DEFAULT" 2032 elif expression.args.get("on"): 2033 property = "ON" 2034 else: 2035 property = "OFF" 2036 return f"CHECKSUM={property}" 2037 2038 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2039 if expression.args.get("no"): 2040 return "NO MERGEBLOCKRATIO" 2041 if expression.args.get("default"): 2042 return "DEFAULT MERGEBLOCKRATIO" 2043 2044 percent = " PERCENT" if expression.args.get("percent") else "" 2045 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 2046 2047 def moduleproperty_sql(self, expression: exp.ModuleProperty) -> str: 2048 expressions = self.expressions(expression, flat=True) 2049 expressions = f"({expressions})" if expressions else "" 2050 return f"USING {self.sql(expression, 'this')}{expressions}" 2051 2052 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2053 default = expression.args.get("default") 2054 minimum = expression.args.get("minimum") 2055 maximum = expression.args.get("maximum") 2056 if default or minimum or maximum: 2057 if default: 2058 prop = "DEFAULT" 2059 elif minimum: 2060 prop = "MINIMUM" 2061 else: 2062 prop = "MAXIMUM" 2063 return f"{prop} DATABLOCKSIZE" 2064 units = expression.args.get("units") 2065 units = f" {units}" if units else "" 2066 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 2067 2068 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2069 autotemp = expression.args.get("autotemp") 2070 always = expression.args.get("always") 2071 default = expression.args.get("default") 2072 manual = expression.args.get("manual") 2073 never = expression.args.get("never") 2074 2075 if autotemp is not None: 2076 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2077 elif always: 2078 prop = "ALWAYS" 2079 elif default: 2080 prop = "DEFAULT" 2081 elif manual: 2082 prop = "MANUAL" 2083 elif never: 2084 prop = "NEVER" 2085 return f"BLOCKCOMPRESSION={prop}" 2086 2087 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2088 no = expression.args.get("no") 2089 no = " NO" if no else "" 2090 concurrent = expression.args.get("concurrent") 2091 concurrent = " CONCURRENT" if concurrent else "" 2092 target = self.sql(expression, "target") 2093 target = f" {target}" if target else "" 2094 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 2095 2096 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2097 if isinstance(expression.this, list): 2098 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2099 if expression.this: 2100 modulus = self.sql(expression, "this") 2101 remainder = self.sql(expression, "expression") 2102 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2103 2104 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2105 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2106 return f"FROM ({from_expressions}) TO ({to_expressions})" 2107 2108 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2109 this = self.sql(expression, "this") 2110 2111 for_values_or_default = expression.expression 2112 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2113 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2114 else: 2115 for_values_or_default = " DEFAULT" 2116 2117 return f"PARTITION OF {this}{for_values_or_default}" 2118 2119 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2120 kind = expression.args.get("kind") 2121 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2122 for_or_in = expression.args.get("for_or_in") 2123 for_or_in = f" {for_or_in}" if for_or_in else "" 2124 lock_type = expression.args.get("lock_type") 2125 override = " OVERRIDE" if expression.args.get("override") else "" 2126 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 2127 2128 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2129 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2130 statistics = expression.args.get("statistics") 2131 statistics_sql = "" 2132 if statistics is not None: 2133 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2134 return f"{data_sql}{statistics_sql}" 2135 2136 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2137 this = self.sql(expression, "this") 2138 this = f"HISTORY_TABLE={this}" if this else "" 2139 data_consistency: str | None = self.sql(expression, "data_consistency") 2140 data_consistency = ( 2141 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2142 ) 2143 retention_period: str | None = self.sql(expression, "retention_period") 2144 retention_period = ( 2145 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2146 ) 2147 2148 if this: 2149 on_sql = self.func("ON", this, data_consistency, retention_period) 2150 else: 2151 on_sql = "ON" if expression.args.get("on") else "OFF" 2152 2153 sql = f"SYSTEM_VERSIONING={on_sql}" 2154 2155 return f"WITH({sql})" if expression.args.get("with_") else sql 2156 2157 def insert_sql(self, expression: exp.Insert) -> str: 2158 hint = self.sql(expression, "hint") 2159 overwrite = expression.args.get("overwrite") 2160 2161 if isinstance(expression.this, exp.Directory): 2162 this = " OVERWRITE" if overwrite else " INTO" 2163 else: 2164 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2165 2166 stored = self.sql(expression, "stored") 2167 stored = f" {stored}" if stored else "" 2168 alternative = expression.args.get("alternative") 2169 alternative = f" OR {alternative}" if alternative else "" 2170 ignore = " IGNORE" if expression.args.get("ignore") else "" 2171 is_function = expression.args.get("is_function") 2172 if is_function: 2173 this = f"{this} FUNCTION" 2174 this = f"{this} {self.sql(expression, 'this')}" 2175 2176 exists = " IF EXISTS" if expression.args.get("exists") else "" 2177 where = self.sql(expression, "where") 2178 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2179 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2180 on_conflict = self.sql(expression, "conflict") 2181 on_conflict = f" {on_conflict}" if on_conflict else "" 2182 by_name = " BY NAME" if expression.args.get("by_name") else "" 2183 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2184 returning = self.sql(expression, "returning") 2185 2186 if self.RETURNING_END: 2187 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2188 else: 2189 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2190 2191 partition_by = self.sql(expression, "partition") 2192 partition_by = f" {partition_by}" if partition_by else "" 2193 settings = self.sql(expression, "settings") 2194 settings = f" {settings}" if settings else "" 2195 2196 source = self.sql(expression, "source") 2197 source = f"TABLE {source}" if source else "" 2198 2199 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2200 return self.prepend_ctes(expression, sql) 2201 2202 def introducer_sql(self, expression: exp.Introducer) -> str: 2203 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2204 2205 def kill_sql(self, expression: exp.Kill) -> str: 2206 kind = self.sql(expression, "kind") 2207 kind = f" {kind}" if kind else "" 2208 this = self.sql(expression, "this") 2209 this = f" {this}" if this else "" 2210 return f"KILL{kind}{this}" 2211 2212 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2213 return expression.name 2214 2215 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2216 return expression.name 2217 2218 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2219 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2220 2221 constraint = self.sql(expression, "constraint") 2222 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2223 2224 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2225 if conflict_keys: 2226 conflict_keys = f"({conflict_keys})" 2227 2228 index_predicate = self.sql(expression, "index_predicate") 2229 conflict_keys = f"{conflict_keys}{index_predicate} " 2230 2231 action = self.sql(expression, "action") 2232 2233 expressions = self.expressions(expression, flat=True) 2234 if expressions: 2235 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2236 expressions = f" {set_keyword}{expressions}" 2237 2238 where = self.sql(expression, "where") 2239 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2240 2241 def returning_sql(self, expression: exp.Returning) -> str: 2242 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2243 2244 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2245 fields = self.sql(expression, "fields") 2246 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2247 escaped = self.sql(expression, "escaped") 2248 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2249 items = self.sql(expression, "collection_items") 2250 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2251 keys = self.sql(expression, "map_keys") 2252 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2253 lines = self.sql(expression, "lines") 2254 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2255 null = self.sql(expression, "null") 2256 null = f" NULL DEFINED AS {null}" if null else "" 2257 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2258 2259 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2260 return f"WITH ({self.expressions(expression, flat=True)})" 2261 2262 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2263 this = f"{self.sql(expression, 'this')} INDEX" 2264 target = self.sql(expression, "target") 2265 target = f" FOR {target}" if target else "" 2266 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2267 2268 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2269 this = self.sql(expression, "this") 2270 kind = self.sql(expression, "kind") 2271 expr = self.sql(expression, "expression") 2272 return f"{this} ({kind} => {expr})" 2273 2274 def table_parts(self, expression: exp.Table) -> str: 2275 return ".".join( 2276 self.sql(part) 2277 for part in ( 2278 expression.args.get("catalog"), 2279 expression.args.get("db"), 2280 expression.args.get("this"), 2281 ) 2282 if part is not None 2283 ) 2284 2285 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2286 table = self.table_parts(expression) 2287 only = "ONLY " if expression.args.get("only") else "" 2288 partition = self.sql(expression, "partition") 2289 partition = f" {partition}" if partition else "" 2290 version = self.sql(expression, "version") 2291 version = f" {version}" if version else "" 2292 alias = self.sql(expression, "alias") 2293 alias = f"{sep}{alias}" if alias else "" 2294 2295 sample = self.sql(expression, "sample") 2296 post_alias = "" 2297 pre_alias = "" 2298 2299 if self.dialect.ALIAS_POST_TABLESAMPLE: 2300 pre_alias = sample 2301 else: 2302 post_alias = sample 2303 2304 if self.dialect.ALIAS_POST_VERSION: 2305 pre_alias = f"{pre_alias}{version}" 2306 else: 2307 post_alias = f"{post_alias}{version}" 2308 2309 hints = self.expressions(expression, key="hints", sep=" ") 2310 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2311 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2312 joins = self.indent( 2313 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2314 ) 2315 laterals = self.expressions(expression, key="laterals", sep="") 2316 2317 file_format = self.sql(expression, "format") 2318 if file_format: 2319 pattern = self.sql(expression, "pattern") 2320 pattern = f", PATTERN => {pattern}" if pattern else "" 2321 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2322 2323 ordinality = expression.args.get("ordinality") or "" 2324 if ordinality: 2325 ordinality = f" WITH ORDINALITY{alias}" 2326 alias = "" 2327 2328 when = self.sql(expression, "when") 2329 if when: 2330 table = f"{table} {when}" 2331 2332 changes = self.sql(expression, "changes") 2333 changes = f" {changes}" if changes else "" 2334 2335 rows_from = self.expressions(expression, key="rows_from") 2336 if rows_from: 2337 table = f"ROWS FROM {self.wrap(rows_from)}" 2338 2339 indexed = expression.args.get("indexed") 2340 if indexed is not None: 2341 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2342 else: 2343 indexed = "" 2344 2345 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}" 2346 2347 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2348 table = self.func("TABLE", expression.this) 2349 alias = self.sql(expression, "alias") 2350 alias = f" AS {alias}" if alias else "" 2351 sample = self.sql(expression, "sample") 2352 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2353 joins = self.indent( 2354 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2355 ) 2356 return f"{table}{alias}{pivots}{sample}{joins}" 2357 2358 def tablesample_sql( 2359 self, 2360 expression: exp.TableSample, 2361 tablesample_keyword: str | None = None, 2362 ) -> str: 2363 method = self.sql(expression, "method") 2364 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2365 numerator = self.sql(expression, "bucket_numerator") 2366 denominator = self.sql(expression, "bucket_denominator") 2367 field = self.sql(expression, "bucket_field") 2368 field = f" ON {field}" if field else "" 2369 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2370 seed = self.sql(expression, "seed") 2371 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2372 2373 size = self.sql(expression, "size") 2374 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2375 size = f"{size} ROWS" 2376 2377 percent = self.sql(expression, "percent") 2378 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2379 percent = f"{percent} PERCENT" 2380 2381 expr = f"{bucket}{percent}{size}" 2382 if self.TABLESAMPLE_REQUIRES_PARENS: 2383 expr = f"({expr})" 2384 2385 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2386 2387 def pivot_sql(self, expression: exp.Pivot) -> str: 2388 expressions = self.expressions(expression, flat=True) 2389 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2390 2391 group = self.sql(expression, "group") 2392 2393 if expression.this: 2394 this = self.sql(expression, "this") 2395 if not expressions: 2396 sql = f"UNPIVOT {this}" 2397 else: 2398 on = f"{self.seg('ON')} {expressions}" 2399 into = self.sql(expression, "into") 2400 into = f"{self.seg('INTO')} {into}" if into else "" 2401 using = self.expressions(expression, key="using", flat=True) 2402 using = f"{self.seg('USING')} {using}" if using else "" 2403 sql = f"{direction} {this}{on}{into}{using}{group}" 2404 return self.prepend_ctes(expression, sql) 2405 2406 alias = self.sql(expression, "alias") 2407 alias = f" AS {alias}" if alias else "" 2408 2409 fields = self.expressions( 2410 expression, 2411 "fields", 2412 sep=" ", 2413 dynamic=True, 2414 new_line=True, 2415 skip_first=True, 2416 skip_last=True, 2417 ) 2418 2419 include_nulls = expression.args.get("include_nulls") 2420 if include_nulls is not None: 2421 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2422 else: 2423 nulls = "" 2424 2425 default_on_null = self.sql(expression, "default_on_null") 2426 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2427 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2428 return self.prepend_ctes(expression, sql) 2429 2430 def version_sql(self, expression: exp.Version) -> str: 2431 this = f"FOR {expression.name}" 2432 kind = expression.text("kind") 2433 expr = self.sql(expression, "expression") 2434 return f"{this} {kind} {expr}" 2435 2436 def tuple_sql(self, expression: exp.Tuple) -> str: 2437 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2438 2439 def _update_from_joins_sql(self, expression: exp.Update) -> tuple[str, str]: 2440 """ 2441 Returns (join_sql, from_sql) for UPDATE statements. 2442 - join_sql: placed after UPDATE table, before SET 2443 - from_sql: placed after SET clause (standard position) 2444 Dialects like MySQL need to convert FROM to JOIN syntax. 2445 """ 2446 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2447 return ("", self.sql(expression, "from_")) 2448 2449 # Qualify unqualified columns in SET clause with the target table 2450 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2451 target_table = expression.this 2452 if isinstance(target_table, exp.Table): 2453 target_name = exp.to_identifier(target_table.alias_or_name) 2454 for eq in expression.expressions: 2455 col = eq.this 2456 if isinstance(col, exp.Column) and not col.table: 2457 col.set("table", target_name) 2458 2459 table = from_expr.this 2460 if nested_joins := table.args.get("joins", []): 2461 table.set("joins", None) 2462 2463 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2464 for nested in nested_joins: 2465 if not nested.args.get("on") and not nested.args.get("using"): 2466 nested.set("on", exp.true()) 2467 join_sql += self.sql(nested) 2468 2469 return (join_sql, "") 2470 2471 def update_sql(self, expression: exp.Update) -> str: 2472 hint = self.sql(expression, "hint") 2473 this = self.sql(expression, "this") 2474 join_sql, from_sql = self._update_from_joins_sql(expression) 2475 set_sql = self.expressions(expression, flat=True) 2476 where_sql = self.sql(expression, "where") 2477 returning = self.sql(expression, "returning") 2478 order = self.sql(expression, "order") 2479 limit = self.sql(expression, "limit") 2480 if self.RETURNING_END: 2481 expression_sql = f"{from_sql}{where_sql}{returning}" 2482 else: 2483 expression_sql = f"{returning}{from_sql}{where_sql}" 2484 options = self.expressions(expression, key="options") 2485 options = f" OPTION({options})" if options else "" 2486 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2487 return self.prepend_ctes(expression, sql) 2488 2489 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2490 values_as_table = values_as_table and self.VALUES_AS_TABLE 2491 2492 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2493 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2494 args = self.expressions(expression) 2495 alias = self.sql(expression, "alias") 2496 values = f"VALUES{self.seg('')}{args}" 2497 values = ( 2498 f"({values})" 2499 if self.WRAP_DERIVED_VALUES 2500 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2501 else values 2502 ) 2503 values = self.query_modifiers(expression, values) 2504 return f"{values} AS {alias}" if alias else values 2505 2506 # Converts `VALUES...` expression into a series of select unions. 2507 alias_node = expression.args.get("alias") 2508 column_names = alias_node and alias_node.columns 2509 2510 selects: list[exp.Query] = [] 2511 2512 for i, tup in enumerate(expression.expressions): 2513 row = tup.expressions 2514 2515 if i == 0 and column_names: 2516 row = [ 2517 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2518 ] 2519 2520 selects.append(exp.Select(expressions=row)) 2521 2522 if self.pretty: 2523 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2524 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2525 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2526 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2527 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2528 2529 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2530 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2531 return f"({unions}){alias}" 2532 2533 def var_sql(self, expression: exp.Var) -> str: 2534 return self.sql(expression, "this") 2535 2536 @unsupported_args("expressions") 2537 def into_sql(self, expression: exp.Into) -> str: 2538 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2539 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2540 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2541 2542 def from_sql(self, expression: exp.From) -> str: 2543 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2544 2545 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2546 grouping_sets = self.expressions(expression, indent=False) 2547 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2548 2549 def rollup_sql(self, expression: exp.Rollup) -> str: 2550 expressions = self.expressions(expression, indent=False) 2551 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2552 2553 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2554 this = self.sql(expression, "this") 2555 2556 columns = self.expressions(expression, flat=True) 2557 2558 from_sql = self.sql(expression, "from_index") 2559 from_sql = f" FROM {from_sql}" if from_sql else "" 2560 2561 properties = expression.args.get("properties") 2562 properties_sql = ( 2563 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2564 ) 2565 2566 return f"{this}({columns}){from_sql}{properties_sql}" 2567 2568 def rollupproperty_sql(self, expression: exp.RollupProperty) -> str: 2569 return f"ROLLUP ({self.expressions(expression, flat=True)})" 2570 2571 def cube_sql(self, expression: exp.Cube) -> str: 2572 expressions = self.expressions(expression, indent=False) 2573 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2574 2575 def group_sql(self, expression: exp.Group) -> str: 2576 group_by_all = expression.args.get("all") 2577 if group_by_all is True: 2578 modifier = " ALL" 2579 elif group_by_all is False: 2580 modifier = " DISTINCT" 2581 else: 2582 modifier = "" 2583 2584 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2585 2586 grouping_sets = self.expressions(expression, key="grouping_sets") 2587 cube = self.expressions(expression, key="cube") 2588 rollup = self.expressions(expression, key="rollup") 2589 2590 groupings = csv( 2591 self.seg(grouping_sets) if grouping_sets else "", 2592 self.seg(cube) if cube else "", 2593 self.seg(rollup) if rollup else "", 2594 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2595 sep=self.GROUPINGS_SEP, 2596 ) 2597 2598 if ( 2599 expression.expressions 2600 and groupings 2601 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2602 ): 2603 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2604 2605 return f"{group_by}{groupings}" 2606 2607 def having_sql(self, expression: exp.Having) -> str: 2608 this = self.indent(self.sql(expression, "this")) 2609 return f"{self.seg('HAVING')}{self.sep()}{this}" 2610 2611 def connect_sql(self, expression: exp.Connect) -> str: 2612 start = self.sql(expression, "start") 2613 start = self.seg(f"START WITH {start}") if start else "" 2614 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2615 connect = self.sql(expression, "connect") 2616 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2617 return start + connect 2618 2619 def prior_sql(self, expression: exp.Prior) -> str: 2620 return f"PRIOR {self.sql(expression, 'this')}" 2621 2622 def join_sql(self, expression: exp.Join) -> str: 2623 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2624 side = None 2625 else: 2626 side = expression.side 2627 2628 op_sql = " ".join( 2629 op 2630 for op in ( 2631 expression.method, 2632 "GLOBAL" if expression.args.get("global_") else None, 2633 side, 2634 expression.kind, 2635 expression.hint if self.JOIN_HINTS else None, 2636 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2637 ) 2638 if op 2639 ) 2640 match_cond = self.sql(expression, "match_condition") 2641 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2642 on_sql = self.sql(expression, "on") 2643 using = expression.args.get("using") 2644 2645 if not on_sql and using: 2646 on_sql = csv(*(self.sql(column) for column in using)) 2647 2648 this = expression.this 2649 this_sql = self.sql(this) 2650 2651 exprs = self.expressions(expression) 2652 if exprs: 2653 this_sql = f"{this_sql},{self.seg(exprs)}" 2654 2655 if on_sql: 2656 on_sql = self.indent(on_sql, skip_first=True) 2657 space = self.seg(" " * self.pad) if self.pretty else " " 2658 if using: 2659 on_sql = f"{space}USING ({on_sql})" 2660 else: 2661 on_sql = f"{space}ON {on_sql}" 2662 elif not op_sql: 2663 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2664 return f" {this_sql}" 2665 2666 return f", {this_sql}" 2667 2668 if op_sql != "STRAIGHT_JOIN": 2669 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2670 2671 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2672 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2673 2674 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2675 args = self.expressions(expression, flat=True) 2676 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2677 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2678 2679 def lateral_op(self, expression: exp.Lateral) -> str: 2680 cross_apply = expression.args.get("cross_apply") 2681 2682 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2683 if cross_apply is True: 2684 op = "INNER JOIN " 2685 elif cross_apply is False: 2686 op = "LEFT JOIN " 2687 else: 2688 op = "" 2689 2690 return f"{op}LATERAL" 2691 2692 def lateral_sql(self, expression: exp.Lateral) -> str: 2693 this = self.sql(expression, "this") 2694 2695 if expression.args.get("view"): 2696 alias = expression.args["alias"] 2697 columns = self.expressions(alias, key="columns", flat=True) 2698 table = f" {alias.name}" if alias.name else "" 2699 columns = f" AS {columns}" if columns else "" 2700 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2701 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2702 2703 alias = self.sql(expression, "alias") 2704 alias = f" AS {alias}" if alias else "" 2705 2706 ordinality = expression.args.get("ordinality") or "" 2707 if ordinality: 2708 ordinality = f" WITH ORDINALITY{alias}" 2709 alias = "" 2710 2711 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2712 2713 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2714 this = self.sql(expression, "this") 2715 2716 args = [ 2717 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2718 for e in (expression.args.get(k) for k in ("offset", "expression")) 2719 if e 2720 ] 2721 2722 args_sql = ", ".join(self.sql(e) for e in args) 2723 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2724 expressions = self.expressions(expression, flat=True) 2725 limit_options = self.sql(expression, "limit_options") 2726 expressions = f" BY {expressions}" if expressions else "" 2727 2728 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2729 2730 def offset_sql(self, expression: exp.Offset) -> str: 2731 this = self.sql(expression, "this") 2732 value = expression.expression 2733 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2734 expressions = self.expressions(expression, flat=True) 2735 expressions = f" BY {expressions}" if expressions else "" 2736 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2737 2738 def setitem_sql(self, expression: exp.SetItem) -> str: 2739 kind = self.sql(expression, "kind") 2740 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2741 kind = "" 2742 else: 2743 kind = f"{kind} " if kind else "" 2744 this = self.sql(expression, "this") 2745 expressions = self.expressions(expression) 2746 collate = self.sql(expression, "collate") 2747 collate = f" COLLATE {collate}" if collate else "" 2748 global_ = "GLOBAL " if expression.args.get("global_") else "" 2749 return f"{global_}{kind}{this}{expressions}{collate}" 2750 2751 def set_sql(self, expression: exp.Set) -> str: 2752 expressions = f" {self.expressions(expression, flat=True)}" 2753 tag = " TAG" if expression.args.get("tag") else "" 2754 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2755 2756 def queryband_sql(self, expression: exp.QueryBand) -> str: 2757 this = self.sql(expression, "this") 2758 update = " UPDATE" if expression.args.get("update") else "" 2759 scope = self.sql(expression, "scope") 2760 scope = f" FOR {scope}" if scope else "" 2761 2762 return f"QUERY_BAND = {this}{update}{scope}" 2763 2764 def pragma_sql(self, expression: exp.Pragma) -> str: 2765 return f"PRAGMA {self.sql(expression, 'this')}" 2766 2767 def lock_sql(self, expression: exp.Lock) -> str: 2768 if not self.LOCKING_READS_SUPPORTED: 2769 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2770 return "" 2771 2772 update = expression.args["update"] 2773 key = expression.args.get("key") 2774 if update: 2775 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2776 else: 2777 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2778 expressions = self.expressions(expression, flat=True) 2779 expressions = f" OF {expressions}" if expressions else "" 2780 wait = expression.args.get("wait") 2781 2782 if wait is not None: 2783 if isinstance(wait, exp.Literal): 2784 wait = f" WAIT {self.sql(wait)}" 2785 else: 2786 wait = " NOWAIT" if wait else " SKIP LOCKED" 2787 2788 return f"{lock_type}{expressions}{wait or ''}" 2789 2790 def literal_sql(self, expression: exp.Literal) -> str: 2791 text = expression.this or "" 2792 if expression.is_string: 2793 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2794 return text 2795 2796 def escape_str( 2797 self, 2798 text: str, 2799 escape_backslash: bool = True, 2800 delimiter: str | None = None, 2801 escaped_delimiter: str | None = None, 2802 is_byte_string: bool = False, 2803 ) -> str: 2804 if is_byte_string: 2805 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2806 else: 2807 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2808 2809 if supports_escape_sequences: 2810 text = "".join( 2811 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2812 for ch in text 2813 ) 2814 2815 delimiter = delimiter or self.dialect.QUOTE_END 2816 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2817 2818 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2819 2820 def loaddata_sql(self, expression: exp.LoadData) -> str: 2821 is_overwrite = expression.args.get("overwrite") 2822 overwrite = " OVERWRITE" if is_overwrite else "" 2823 this = self.sql(expression, "this") 2824 2825 files = expression.args.get("files") 2826 if files: 2827 files_sql = self.expressions(files, flat=True) 2828 files_sql = f"FILES{self.wrap(files_sql)}" 2829 this = f" {this}" if is_overwrite else f" INTO TABLE {this}" 2830 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2831 2832 local = " LOCAL" if expression.args.get("local") else "" 2833 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2834 this = f" INTO TABLE {this}" 2835 partition = self.sql(expression, "partition") 2836 partition = f" {partition}" if partition else "" 2837 input_format = self.sql(expression, "input_format") 2838 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2839 serde = self.sql(expression, "serde") 2840 serde = f" SERDE {serde}" if serde else "" 2841 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2842 2843 def null_sql(self, *_) -> str: 2844 return "NULL" 2845 2846 def boolean_sql(self, expression: exp.Boolean) -> str: 2847 return "TRUE" if expression.this else "FALSE" 2848 2849 def booland_sql(self, expression: exp.Booland) -> str: 2850 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2851 2852 def boolor_sql(self, expression: exp.Boolor) -> str: 2853 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2854 2855 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2856 this = self.sql(expression, "this") 2857 this = f"{this} " if this else this 2858 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2859 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat) 2860 2861 def withfill_sql(self, expression: exp.WithFill) -> str: 2862 from_sql = self.sql(expression, "from_") 2863 from_sql = f" FROM {from_sql}" if from_sql else "" 2864 to_sql = self.sql(expression, "to") 2865 to_sql = f" TO {to_sql}" if to_sql else "" 2866 step_sql = self.sql(expression, "step") 2867 step_sql = f" STEP {step_sql}" if step_sql else "" 2868 interpolated_values = [ 2869 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2870 if isinstance(e, exp.Alias) 2871 else self.sql(e, "this") 2872 for e in expression.args.get("interpolate") or [] 2873 ] 2874 interpolate = ( 2875 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2876 ) 2877 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2878 2879 def cluster_sql(self, expression: exp.Cluster) -> str: 2880 return self.op_expressions("CLUSTER BY", expression) 2881 2882 def distribute_sql(self, expression: exp.Distribute) -> str: 2883 return self.op_expressions("DISTRIBUTE BY", expression) 2884 2885 def sort_sql(self, expression: exp.Sort) -> str: 2886 return self.op_expressions("SORT BY", expression) 2887 2888 def ordered_sql(self, expression: exp.Ordered) -> str: 2889 desc = expression.args.get("desc") 2890 asc = not desc 2891 2892 nulls_first = expression.args.get("nulls_first") 2893 nulls_last = not nulls_first 2894 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2895 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2896 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2897 2898 this = self.sql(expression, "this") 2899 2900 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2901 nulls_sort_change = "" 2902 if nulls_first and ( 2903 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2904 ): 2905 nulls_sort_change = " NULLS FIRST" 2906 elif ( 2907 nulls_last 2908 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2909 and not nulls_are_last 2910 ): 2911 nulls_sort_change = " NULLS LAST" 2912 2913 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2914 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2915 window = expression.find_ancestor(exp.Window, exp.Select) 2916 2917 if isinstance(window, exp.Window): 2918 window_this = window.this 2919 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 2920 window_this = window_this.this 2921 spec = window.args.get("spec") 2922 else: 2923 window_this = None 2924 spec = None 2925 2926 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 2927 # without a spec or with a ROWS spec, but not with RANGE 2928 if not ( 2929 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 2930 and (not spec or spec.text("kind").upper() == "ROWS") 2931 ): 2932 if window_this and spec: 2933 self.unsupported( 2934 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 2935 ) 2936 nulls_sort_change = "" 2937 elif self.NULL_ORDERING_SUPPORTED is False and ( 2938 (asc and nulls_sort_change == " NULLS LAST") 2939 or (desc and nulls_sort_change == " NULLS FIRST") 2940 ): 2941 # BigQuery does not allow these ordering/nulls combinations when used under 2942 # an aggregation func or under a window containing one 2943 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2944 2945 if isinstance(ancestor, exp.Window): 2946 ancestor = ancestor.this 2947 if isinstance(ancestor, exp.AggFunc): 2948 self.unsupported( 2949 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 2950 ) 2951 nulls_sort_change = "" 2952 elif self.NULL_ORDERING_SUPPORTED is None: 2953 if expression.this.is_int: 2954 self.unsupported( 2955 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2956 ) 2957 elif not isinstance(expression.this, exp.Rand): 2958 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2959 this = ( 2960 f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2961 ) 2962 nulls_sort_change = "" 2963 2964 with_fill = self.sql(expression, "with_fill") 2965 with_fill = f" {with_fill}" if with_fill else "" 2966 2967 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2968 2969 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2970 window_frame = self.sql(expression, "window_frame") 2971 window_frame = f"{window_frame} " if window_frame else "" 2972 2973 this = self.sql(expression, "this") 2974 2975 return f"{window_frame}{this}" 2976 2977 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2978 partition = self.partition_by_sql(expression) 2979 order = self.sql(expression, "order") 2980 measures = self.expressions(expression, key="measures") 2981 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2982 rows = self.sql(expression, "rows") 2983 rows = self.seg(rows) if rows else "" 2984 after = self.sql(expression, "after") 2985 after = self.seg(after) if after else "" 2986 pattern = self.sql(expression, "pattern") 2987 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2988 definition_sqls = [ 2989 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2990 for definition in expression.args.get("define", []) 2991 ] 2992 definitions = self.expressions(sqls=definition_sqls) 2993 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2994 body = "".join( 2995 ( 2996 partition, 2997 order, 2998 measures, 2999 rows, 3000 after, 3001 pattern, 3002 define, 3003 ) 3004 ) 3005 alias = self.sql(expression, "alias") 3006 alias = f" {alias}" if alias else "" 3007 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 3008 3009 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3010 limit = expression.args.get("limit") 3011 3012 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3013 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3014 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3015 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3016 3017 return csv( 3018 *sqls, 3019 *[self.sql(join) for join in expression.args.get("joins") or []], 3020 self.sql(expression, "match"), 3021 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3022 self.sql(expression, "prewhere"), 3023 self.sql(expression, "where"), 3024 self.sql(expression, "connect"), 3025 self.sql(expression, "group"), 3026 self.sql(expression, "having"), 3027 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3028 self.sql(expression, "order"), 3029 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3030 *self.after_limit_modifiers(expression), 3031 self.options_modifier(expression), 3032 self.for_modifiers(expression), 3033 sep="", 3034 ) 3035 3036 def options_modifier(self, expression: exp.Expr) -> str: 3037 options = self.expressions(expression, key="options") 3038 return f" {options}" if options else "" 3039 3040 def for_modifiers(self, expression: exp.Expr) -> str: 3041 for_modifiers = self.expressions(expression, key="for_") 3042 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 3043 3044 def queryoption_sql(self, expression: exp.QueryOption) -> str: 3045 self.unsupported("Unsupported query option.") 3046 return "" 3047 3048 def offset_limit_modifiers( 3049 self, expression: exp.Expr, fetch: bool, limit: exp.Fetch | exp.Limit | None 3050 ) -> list[str]: 3051 return [ 3052 self.sql(expression, "offset") if fetch else self.sql(limit), 3053 self.sql(limit) if fetch else self.sql(expression, "offset"), 3054 ] 3055 3056 def after_limit_modifiers(self, expression: exp.Expr) -> list[str]: 3057 locks = self.expressions(expression, key="locks", sep=" ") 3058 locks = f" {locks}" if locks else "" 3059 return [locks, self.sql(expression, "sample")] 3060 3061 def select_sql(self, expression: exp.Select) -> str: 3062 into = expression.args.get("into") 3063 if not self.SUPPORTS_SELECT_INTO and into: 3064 into.pop() 3065 3066 hint = self.sql(expression, "hint") 3067 distinct = self.sql(expression, "distinct") 3068 distinct = f" {distinct}" if distinct else "" 3069 kind = self.sql(expression, "kind") 3070 3071 limit = expression.args.get("limit") 3072 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3073 top = self.limit_sql(limit, top=True) 3074 limit.pop() 3075 else: 3076 top = "" 3077 3078 expressions = self.expressions(expression) 3079 3080 if kind: 3081 if kind in self.SELECT_KINDS: 3082 kind = f" AS {kind}" 3083 else: 3084 if kind == "STRUCT": 3085 expressions = self.expressions( 3086 sqls=[ 3087 self.sql( 3088 exp.Struct( 3089 expressions=[ 3090 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3091 if isinstance(e, exp.Alias) 3092 else e 3093 for e in expression.expressions 3094 ] 3095 ) 3096 ) 3097 ] 3098 ) 3099 kind = "" 3100 3101 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3102 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3103 3104 exclude = expression.args.get("exclude") 3105 3106 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3107 exclude_sql = self.expressions(sqls=exclude, flat=True) 3108 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3109 3110 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3111 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3112 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3113 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3114 sql = self.query_modifiers( 3115 expression, 3116 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3117 self.sql(expression, "into", comment=False), 3118 self.sql(expression, "from_", comment=False), 3119 ) 3120 3121 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3122 if expression.args.get("with_"): 3123 sql = self.maybe_comment(sql, expression) 3124 expression.pop_comments() 3125 3126 sql = self.prepend_ctes(expression, sql) 3127 3128 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3129 expression.set("exclude", None) 3130 subquery = expression.subquery(copy=False) 3131 star = exp.Star(except_=exclude) 3132 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3133 3134 if not self.SUPPORTS_SELECT_INTO and into: 3135 if into.args.get("temporary"): 3136 table_kind = " TEMPORARY" 3137 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3138 table_kind = " UNLOGGED" 3139 else: 3140 table_kind = "" 3141 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3142 3143 return sql 3144 3145 def schema_sql(self, expression: exp.Schema) -> str: 3146 this = self.sql(expression, "this") 3147 sql = self.schema_columns_sql(expression) 3148 return f"{this} {sql}" if this and sql else this or sql 3149 3150 def schema_columns_sql(self, expression: exp.Expr) -> str: 3151 if expression.expressions: 3152 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 3153 return "" 3154 3155 def star_sql(self, expression: exp.Star) -> str: 3156 except_ = self.expressions(expression, key="except_", flat=True) 3157 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3158 replace = self.expressions(expression, key="replace", flat=True) 3159 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3160 rename = self.expressions(expression, key="rename", flat=True) 3161 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3162 return f"*{except_}{replace}{rename}" 3163 3164 def parameter_sql(self, expression: exp.Parameter) -> str: 3165 this = self.sql(expression, "this") 3166 return f"{self.PARAMETER_TOKEN}{this}" 3167 3168 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 3169 this = self.sql(expression, "this") 3170 kind = expression.text("kind") 3171 if kind: 3172 kind = f"{kind}." 3173 return f"@@{kind}{this}" 3174 3175 def placeholder_sql(self, expression: exp.Placeholder) -> str: 3176 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 3177 3178 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3179 alias = self.sql(expression, "alias") 3180 alias = f"{sep}{alias}" if alias else "" 3181 sample = self.sql(expression, "sample") 3182 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3183 alias = f"{sample}{alias}" 3184 3185 # Set to None so it's not generated again by self.query_modifiers() 3186 expression.set("sample", None) 3187 3188 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3189 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3190 return self.prepend_ctes(expression, sql) 3191 3192 def qualify_sql(self, expression: exp.Qualify) -> str: 3193 this = self.indent(self.sql(expression, "this")) 3194 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 3195 3196 def unnest_sql(self, expression: exp.Unnest) -> str: 3197 args = self.expressions(expression, flat=True) 3198 3199 alias = expression.args.get("alias") 3200 offset = expression.args.get("offset") 3201 3202 if self.UNNEST_WITH_ORDINALITY: 3203 if alias and isinstance(offset, exp.Expr): 3204 alias.append("columns", offset) 3205 3206 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3207 columns = alias.columns 3208 alias = self.sql(columns[0]) if columns else "" 3209 else: 3210 alias = self.sql(alias) 3211 3212 alias = f" AS {alias}" if alias else alias 3213 if self.UNNEST_WITH_ORDINALITY: 3214 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3215 else: 3216 if isinstance(offset, exp.Expr): 3217 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3218 elif offset: 3219 suffix = f"{alias} WITH OFFSET" 3220 else: 3221 suffix = alias 3222 3223 return f"UNNEST({args}){suffix}" 3224 3225 def prewhere_sql(self, expression: exp.PreWhere) -> str: 3226 return "" 3227 3228 def where_sql(self, expression: exp.Where) -> str: 3229 this = self.indent(self.sql(expression, "this")) 3230 return f"{self.seg('WHERE')}{self.sep()}{this}" 3231 3232 def window_sql(self, expression: exp.Window) -> str: 3233 this = self.sql(expression, "this") 3234 partition = self.partition_by_sql(expression) 3235 order = expression.args.get("order") 3236 order = self.order_sql(order, flat=True) if order else "" 3237 spec = self.sql(expression, "spec") 3238 alias = self.sql(expression, "alias") 3239 over = self.sql(expression, "over") or "OVER" 3240 3241 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3242 3243 first = expression.args.get("first") 3244 if first is None: 3245 first = "" 3246 else: 3247 first = "FIRST" if first else "LAST" 3248 3249 if not partition and not order and not spec and alias: 3250 return f"{this} {alias}" 3251 3252 args = self.format_args( 3253 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3254 ) 3255 return f"{this} ({args})" 3256 3257 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3258 partition = self.expressions(expression, key="partition_by", flat=True) 3259 return f"PARTITION BY {partition}" if partition else "" 3260 3261 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3262 kind = self.sql(expression, "kind") 3263 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3264 end = ( 3265 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3266 or "CURRENT ROW" 3267 ) 3268 3269 window_spec = f"{kind} BETWEEN {start} AND {end}" 3270 3271 exclude = self.sql(expression, "exclude") 3272 if exclude: 3273 if self.SUPPORTS_WINDOW_EXCLUDE: 3274 window_spec += f" EXCLUDE {exclude}" 3275 else: 3276 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3277 3278 return window_spec 3279 3280 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3281 this = self.sql(expression, "this") 3282 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3283 return f"{this} WITHIN GROUP ({expression_sql})" 3284 3285 def between_sql(self, expression: exp.Between) -> str: 3286 this = self.sql(expression, "this") 3287 low = self.sql(expression, "low") 3288 high = self.sql(expression, "high") 3289 symmetric = expression.args.get("symmetric") 3290 3291 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3292 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3293 3294 flag = ( 3295 " SYMMETRIC" 3296 if symmetric 3297 else " ASYMMETRIC" 3298 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3299 else "" # silently drop ASYMMETRIC – semantics identical 3300 ) 3301 return f"{this} BETWEEN{flag} {low} AND {high}" 3302 3303 def bracket_offset_expressions( 3304 self, expression: exp.Bracket, index_offset: int | None = None 3305 ) -> list[exp.Expr]: 3306 if expression.args.get("json_access"): 3307 return expression.expressions 3308 3309 return apply_index_offset( 3310 expression.this, 3311 expression.expressions, 3312 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3313 dialect=self.dialect, 3314 ) 3315 3316 def bracket_sql(self, expression: exp.Bracket) -> str: 3317 expressions = self.bracket_offset_expressions(expression) 3318 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3319 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3320 3321 def all_sql(self, expression: exp.All) -> str: 3322 this = self.sql(expression, "this") 3323 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3324 this = self.wrap(this) 3325 return f"ALL {this}" 3326 3327 def any_sql(self, expression: exp.Any) -> str: 3328 this = self.sql(expression, "this") 3329 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3330 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3331 this = self.wrap(this) 3332 return f"ANY{this}" 3333 return f"ANY {this}" 3334 3335 def exists_sql(self, expression: exp.Exists) -> str: 3336 return f"EXISTS{self.wrap(expression)}" 3337 3338 def case_sql(self, expression: exp.Case) -> str: 3339 this = self.sql(expression, "this") 3340 statements = [f"CASE {this}" if this else "CASE"] 3341 3342 for e in expression.args["ifs"]: 3343 statements.append(f"WHEN {self.sql(e, 'this')}") 3344 statements.append(f"THEN {self.sql(e, 'true')}") 3345 3346 default = self.sql(expression, "default") 3347 3348 if default: 3349 statements.append(f"ELSE {default}") 3350 3351 statements.append("END") 3352 3353 if self.pretty and self.too_wide(statements): 3354 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3355 3356 return " ".join(statements) 3357 3358 def constraint_sql(self, expression: exp.Constraint) -> str: 3359 this = self.sql(expression, "this") 3360 expressions = self.expressions(expression, flat=True) 3361 return f"CONSTRAINT {this} {expressions}" 3362 3363 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3364 order = expression.args.get("order") 3365 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3366 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3367 3368 def extract_sql(self, expression: exp.Extract) -> str: 3369 import sqlglot.dialects.dialect 3370 3371 this = ( 3372 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3373 if self.NORMALIZE_EXTRACT_DATE_PARTS 3374 else expression.this 3375 ) 3376 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3377 expression_sql = self.sql(expression, "expression") 3378 3379 return f"EXTRACT({this_sql} FROM {expression_sql})" 3380 3381 def trim_sql(self, expression: exp.Trim) -> str: 3382 trim_type = self.sql(expression, "position") 3383 3384 if trim_type == "LEADING": 3385 func_name = "LTRIM" 3386 elif trim_type == "TRAILING": 3387 func_name = "RTRIM" 3388 else: 3389 func_name = "TRIM" 3390 3391 return self.func(func_name, expression.this, expression.expression) 3392 3393 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3394 args = expression.expressions 3395 if isinstance(expression, exp.ConcatWs): 3396 args = args[1:] # Skip the delimiter 3397 3398 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3399 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3400 3401 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3402 3403 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3404 if not e.type: 3405 import sqlglot.optimizer.annotate_types 3406 3407 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3408 3409 if e.is_string or e.is_type(exp.DType.ARRAY): 3410 return e 3411 3412 return exp.func("coalesce", e, exp.Literal.string("")) 3413 3414 args = [_wrap_with_coalesce(e) for e in args] 3415 3416 return args 3417 3418 def concat_sql(self, expression: exp.Concat) -> str: 3419 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3420 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3421 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3422 # instead of coalescing them to empty string. 3423 import sqlglot.dialects.dialect 3424 3425 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3426 3427 expressions = self.convert_concat_args(expression) 3428 3429 # Some dialects don't allow a single-argument CONCAT call 3430 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3431 return self.sql(expressions[0]) 3432 3433 return self.func("CONCAT", *expressions) 3434 3435 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3436 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3437 # Dialect's CONCAT_WS function coalesces NULLs to empty strings, but the expression does not. 3438 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3439 all_args = expression.expressions 3440 expression.set("coalesce", True) 3441 return self.sql( 3442 exp.case() 3443 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3444 .else_(expression) 3445 ) 3446 3447 return self.func( 3448 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3449 ) 3450 3451 def check_sql(self, expression: exp.Check) -> str: 3452 this = self.sql(expression, key="this") 3453 return f"CHECK ({this})" 3454 3455 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3456 expressions = self.expressions(expression, flat=True) 3457 expressions = f" ({expressions})" if expressions else "" 3458 reference = self.sql(expression, "reference") 3459 reference = f" {reference}" if reference else "" 3460 delete = self.sql(expression, "delete") 3461 delete = f" ON DELETE {delete}" if delete else "" 3462 update = self.sql(expression, "update") 3463 update = f" ON UPDATE {update}" if update else "" 3464 options = self.expressions(expression, key="options", flat=True, sep=" ") 3465 options = f" {options}" if options else "" 3466 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3467 3468 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3469 this = self.sql(expression, "this") 3470 this = f" {this}" if this else "" 3471 expressions = self.expressions(expression, flat=True) 3472 include = self.sql(expression, "include") 3473 options = self.expressions(expression, key="options", flat=True, sep=" ") 3474 options = f" {options}" if options else "" 3475 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3476 3477 def if_sql(self, expression: exp.If) -> str: 3478 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3479 3480 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3481 if self.MATCH_AGAINST_TABLE_PREFIX: 3482 expressions = [] 3483 for expr in expression.expressions: 3484 if isinstance(expr, exp.Table): 3485 expressions.append(f"TABLE {self.sql(expr)}") 3486 else: 3487 expressions.append(expr) 3488 else: 3489 expressions = expression.expressions 3490 3491 modifier = expression.args.get("modifier") 3492 modifier = f" {modifier}" if modifier else "" 3493 return ( 3494 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3495 ) 3496 3497 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3498 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3499 3500 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3501 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3502 3503 if expression.args.get("escape"): 3504 path = self.escape_str(path) 3505 3506 if self.QUOTE_JSON_PATH: 3507 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3508 3509 return path 3510 3511 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3512 if isinstance(expression, exp.JSONPathPart): 3513 transform = self.TRANSFORMS.get(expression.__class__) 3514 if not callable(transform): 3515 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3516 return "" 3517 3518 return transform(self, expression) 3519 3520 if isinstance(expression, int): 3521 return str(expression) 3522 3523 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3524 escaped = expression.replace("'", "\\'") 3525 escaped = f"\\'{expression}\\'" 3526 else: 3527 escaped = expression.replace('"', '\\"') 3528 escaped = f'"{escaped}"' 3529 3530 return escaped 3531 3532 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3533 return f"{self.sql(expression, 'this')} FORMAT JSON" 3534 3535 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3536 # Output the Teradata column FORMAT override. 3537 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3538 this = self.sql(expression, "this") 3539 fmt = self.sql(expression, "format") 3540 return f"{this} (FORMAT {fmt})" 3541 3542 def _jsonobject_sql( 3543 self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = "" 3544 ) -> str: 3545 null_handling = expression.args.get("null_handling") 3546 null_handling = f" {null_handling}" if null_handling else "" 3547 3548 unique_keys = expression.args.get("unique_keys") 3549 if unique_keys is not None: 3550 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3551 else: 3552 unique_keys = "" 3553 3554 return_type = self.sql(expression, "return_type") 3555 return_type = f" RETURNING {return_type}" if return_type else "" 3556 encoding = self.sql(expression, "encoding") 3557 encoding = f" ENCODING {encoding}" if encoding else "" 3558 3559 if not name: 3560 name = "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG" 3561 3562 return self.func( 3563 name, 3564 *expression.expressions, 3565 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3566 ) 3567 3568 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3569 null_handling = expression.args.get("null_handling") 3570 null_handling = f" {null_handling}" if null_handling else "" 3571 return_type = self.sql(expression, "return_type") 3572 return_type = f" RETURNING {return_type}" if return_type else "" 3573 strict = " STRICT" if expression.args.get("strict") else "" 3574 return self.func( 3575 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3576 ) 3577 3578 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3579 this = self.sql(expression, "this") 3580 order = self.sql(expression, "order") 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_ARRAYAGG", 3588 this, 3589 suffix=f"{order}{null_handling}{return_type}{strict})", 3590 ) 3591 3592 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3593 path = self.sql(expression, "path") 3594 path = f" PATH {path}" if path else "" 3595 nested_schema = self.sql(expression, "nested_schema") 3596 3597 if nested_schema: 3598 return f"NESTED{path} {nested_schema}" 3599 3600 this = self.sql(expression, "this") 3601 kind = self.sql(expression, "kind") 3602 kind = f" {kind}" if kind else "" 3603 3604 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3605 return f"{this}{kind}{path}{ordinality}" 3606 3607 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3608 return self.func("COLUMNS", *expression.expressions) 3609 3610 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3611 this = self.sql(expression, "this") 3612 path = self.sql(expression, "path") 3613 path = f", {path}" if path else "" 3614 error_handling = expression.args.get("error_handling") 3615 error_handling = f" {error_handling}" if error_handling else "" 3616 empty_handling = expression.args.get("empty_handling") 3617 empty_handling = f" {empty_handling}" if empty_handling else "" 3618 schema = self.sql(expression, "schema") 3619 return self.func( 3620 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3621 ) 3622 3623 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3624 this = self.sql(expression, "this") 3625 kind = self.sql(expression, "kind") 3626 path = self.sql(expression, "path") 3627 path = f" {path}" if path else "" 3628 as_json = " AS JSON" if expression.args.get("as_json") else "" 3629 return f"{this} {kind}{path}{as_json}" 3630 3631 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3632 this = self.sql(expression, "this") 3633 path = self.sql(expression, "path") 3634 path = f", {path}" if path else "" 3635 expressions = self.expressions(expression) 3636 with_ = ( 3637 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3638 if expressions 3639 else "" 3640 ) 3641 return f"OPENJSON({this}{path}){with_}" 3642 3643 def in_sql(self, expression: exp.In) -> str: 3644 query = expression.args.get("query") 3645 unnest = expression.args.get("unnest") 3646 field = expression.args.get("field") 3647 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3648 3649 if query: 3650 in_sql = self.sql(query) 3651 elif unnest: 3652 in_sql = self.in_unnest_op(unnest) 3653 elif field: 3654 in_sql = self.sql(field) 3655 else: 3656 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3657 3658 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3659 3660 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3661 return f"(SELECT {self.sql(unnest)})" 3662 3663 def interval_sql(self, expression: exp.Interval) -> str: 3664 unit_expression = expression.args.get("unit") 3665 unit = self.sql(unit_expression) if unit_expression else "" 3666 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3667 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3668 unit = f" {unit}" if unit else "" 3669 3670 if self.SINGLE_STRING_INTERVAL: 3671 this = expression.this.name if expression.this else "" 3672 if this: 3673 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3674 return f"INTERVAL '{this}'{unit}" 3675 return f"INTERVAL '{this}{unit}'" 3676 return f"INTERVAL{unit}" 3677 3678 this = self.sql(expression, "this") 3679 if this: 3680 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3681 this = f" {this}" if unwrapped else f" ({this})" 3682 3683 return f"INTERVAL{this}{unit}" 3684 3685 def return_sql(self, expression: exp.Return) -> str: 3686 return f"RETURN {self.sql(expression, 'this')}" 3687 3688 def reference_sql(self, expression: exp.Reference) -> str: 3689 this = self.sql(expression, "this") 3690 expressions = self.expressions(expression, flat=True) 3691 expressions = f"({expressions})" if expressions else "" 3692 options = self.expressions(expression, key="options", flat=True, sep=" ") 3693 options = f" {options}" if options else "" 3694 return f"REFERENCES {this}{expressions}{options}" 3695 3696 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3697 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3698 parent = expression.parent 3699 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3700 3701 return self.func( 3702 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3703 ) 3704 3705 def paren_sql(self, expression: exp.Paren) -> str: 3706 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3707 return f"({sql}{self.seg(')', sep='')}" 3708 3709 def neg_sql(self, expression: exp.Neg) -> str: 3710 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3711 this_sql = self.sql(expression, "this") 3712 sep = " " if this_sql[0] == "-" else "" 3713 return f"-{sep}{this_sql}" 3714 3715 def not_sql(self, expression: exp.Not) -> str: 3716 return f"NOT {self.sql(expression, 'this')}" 3717 3718 def alias_sql(self, expression: exp.Alias) -> str: 3719 alias = self.sql(expression, "alias") 3720 alias = f" AS {alias}" if alias else "" 3721 return f"{self.sql(expression, 'this')}{alias}" 3722 3723 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3724 alias = expression.args["alias"] 3725 3726 parent = expression.parent 3727 pivot = parent and parent.parent 3728 3729 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3730 identifier_alias = isinstance(alias, exp.Identifier) 3731 literal_alias = isinstance(alias, exp.Literal) 3732 3733 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3734 alias.replace(exp.Literal.string(alias.output_name)) 3735 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3736 alias.replace(exp.to_identifier(alias.output_name)) 3737 3738 return self.alias_sql(expression) 3739 3740 def aliases_sql(self, expression: exp.Aliases) -> str: 3741 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3742 3743 def atindex_sql(self, expression: exp.AtIndex) -> str: 3744 this = self.sql(expression, "this") 3745 index = self.sql(expression, "expression") 3746 return f"{this} AT {index}" 3747 3748 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3749 this = self.sql(expression, "this") 3750 zone = self.sql(expression, "zone") 3751 return f"{this} AT TIME ZONE {zone}" 3752 3753 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3754 this = self.sql(expression, "this") 3755 zone = self.sql(expression, "zone") 3756 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3757 3758 def add_sql(self, expression: exp.Add) -> str: 3759 return self.binary(expression, "+") 3760 3761 def and_sql(self, expression: exp.And, stack: list[str | exp.Expr] | None = None) -> str: 3762 return self.connector_sql(expression, "AND", stack) 3763 3764 def or_sql(self, expression: exp.Or, stack: list[str | exp.Expr] | None = None) -> str: 3765 return self.connector_sql(expression, "OR", stack) 3766 3767 def xor_sql(self, expression: exp.Xor, stack: list[str | exp.Expr] | None = None) -> str: 3768 return self.connector_sql(expression, "XOR", stack) 3769 3770 def connector_sql( 3771 self, 3772 expression: exp.Connector, 3773 op: str, 3774 stack: list[str | exp.Expr] | None = None, 3775 ) -> str: 3776 if stack is not None: 3777 if expression.expressions: 3778 stack.append(self.expressions(expression, sep=f" {op} ")) 3779 else: 3780 stack.append(expression.right) 3781 if expression.comments and self.comments: 3782 for comment in expression.comments: 3783 if comment: 3784 op += f" /*{self.sanitize_comment(comment)}*/" 3785 stack.extend((op, expression.left)) 3786 return op 3787 3788 stack = [expression] 3789 sqls: list[str] = [] 3790 ops = set() 3791 3792 while stack: 3793 node = stack.pop() 3794 if isinstance(node, exp.Connector): 3795 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3796 else: 3797 sql = self.sql(node) 3798 if sqls and sqls[-1] in ops: 3799 sqls[-1] += f" {sql}" 3800 else: 3801 sqls.append(sql) 3802 3803 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3804 return sep.join(sqls) 3805 3806 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3807 return self.binary(expression, "&") 3808 3809 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3810 return self.binary(expression, "<<") 3811 3812 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3813 return f"~{self.sql(expression, 'this')}" 3814 3815 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3816 return self.binary(expression, "|") 3817 3818 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3819 return self.binary(expression, ">>") 3820 3821 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3822 return self.binary(expression, "^") 3823 3824 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 3825 format_sql = self.sql(expression, "format") 3826 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3827 to_sql = self.sql(expression, "to") 3828 to_sql = f" {to_sql}" if to_sql else "" 3829 action = self.sql(expression, "action") 3830 action = f" {action}" if action else "" 3831 default = self.sql(expression, "default") 3832 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3833 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3834 3835 # Base implementation that excludes safe, zone, and target_type metadata args 3836 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3837 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3838 3839 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3840 zone = self.sql(expression, "this") 3841 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3842 3843 def collate_sql(self, expression: exp.Collate) -> str: 3844 if self.COLLATE_IS_FUNC: 3845 return self.function_fallback_sql(expression) 3846 return self.binary(expression, "COLLATE") 3847 3848 def command_sql(self, expression: exp.Command) -> str: 3849 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3850 3851 def comment_sql(self, expression: exp.Comment) -> str: 3852 this = self.sql(expression, "this") 3853 kind = expression.args["kind"] 3854 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3855 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3856 expression_sql = self.sql(expression, "expression") 3857 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3858 3859 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3860 this = self.sql(expression, "this") 3861 delete = " DELETE" if expression.args.get("delete") else "" 3862 recompress = self.sql(expression, "recompress") 3863 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3864 to_disk = self.sql(expression, "to_disk") 3865 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3866 to_volume = self.sql(expression, "to_volume") 3867 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3868 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3869 3870 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3871 where = self.sql(expression, "where") 3872 group = self.sql(expression, "group") 3873 aggregates = self.expressions(expression, key="aggregates") 3874 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3875 3876 if not (where or group or aggregates) and len(expression.expressions) == 1: 3877 return f"TTL {self.expressions(expression, flat=True)}" 3878 3879 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3880 3881 def transaction_sql(self, expression: exp.Transaction) -> str: 3882 modes = self.expressions(expression, key="modes") 3883 modes = f" {modes}" if modes else "" 3884 return f"BEGIN{modes}" 3885 3886 def commit_sql(self, expression: exp.Commit) -> str: 3887 chain = expression.args.get("chain") 3888 if chain is not None: 3889 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3890 3891 return f"COMMIT{chain or ''}" 3892 3893 def rollback_sql(self, expression: exp.Rollback) -> str: 3894 savepoint = expression.args.get("savepoint") 3895 savepoint = f" TO {savepoint}" if savepoint else "" 3896 return f"ROLLBACK{savepoint}" 3897 3898 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3899 this = self.sql(expression, "this") 3900 3901 dtype = self.sql(expression, "dtype") 3902 if dtype: 3903 collate = self.sql(expression, "collate") 3904 collate = f" COLLATE {collate}" if collate else "" 3905 using = self.sql(expression, "using") 3906 using = f" USING {using}" if using else "" 3907 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3908 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3909 3910 default = self.sql(expression, "default") 3911 if default: 3912 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3913 3914 comment = self.sql(expression, "comment") 3915 if comment: 3916 return f"ALTER COLUMN {this} COMMENT {comment}" 3917 3918 visible = expression.args.get("visible") 3919 if visible: 3920 return f"ALTER COLUMN {this} SET {visible}" 3921 3922 allow_null = expression.args.get("allow_null") 3923 drop = expression.args.get("drop") 3924 3925 if not drop and not allow_null: 3926 self.unsupported("Unsupported ALTER COLUMN syntax") 3927 3928 if allow_null is not None: 3929 keyword = "DROP" if drop else "SET" 3930 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3931 3932 return f"ALTER COLUMN {this} DROP DEFAULT" 3933 3934 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3935 this = self.sql(expression, "this") 3936 3937 visible = expression.args.get("visible") 3938 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3939 3940 return f"ALTER INDEX {this} {visible_sql}" 3941 3942 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3943 this = self.sql(expression, "this") 3944 if not isinstance(expression.this, exp.Var): 3945 this = f"KEY DISTKEY {this}" 3946 return f"ALTER DISTSTYLE {this}" 3947 3948 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3949 compound = " COMPOUND" if expression.args.get("compound") else "" 3950 this = self.sql(expression, "this") 3951 expressions = self.expressions(expression, flat=True) 3952 expressions = f"({expressions})" if expressions else "" 3953 return f"ALTER{compound} SORTKEY {this or expressions}" 3954 3955 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3956 if not self.RENAME_TABLE_WITH_DB: 3957 # Remove db from tables 3958 expression = expression.transform( 3959 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3960 ).assert_is(exp.AlterRename) 3961 this = self.sql(expression, "this") 3962 to_kw = " TO" if include_to else "" 3963 return f"RENAME{to_kw} {this}" 3964 3965 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3966 exists = " IF EXISTS" if expression.args.get("exists") else "" 3967 old_column = self.sql(expression, "this") 3968 new_column = self.sql(expression, "to") 3969 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3970 3971 def alterset_sql(self, expression: exp.AlterSet) -> str: 3972 exprs = self.expressions(expression, flat=True) 3973 if self.ALTER_SET_WRAPPED: 3974 exprs = f"({exprs})" 3975 3976 return f"SET {exprs}" 3977 3978 def alter_sql(self, expression: exp.Alter) -> str: 3979 actions = expression.args["actions"] 3980 3981 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3982 actions[0], exp.ColumnDef 3983 ): 3984 actions_sql = self.expressions(expression, key="actions", flat=True) 3985 actions_sql = f"ADD {actions_sql}" 3986 else: 3987 actions_list = [] 3988 for action in actions: 3989 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3990 action_sql = self.add_column_sql(action) 3991 else: 3992 action_sql = self.sql(action) 3993 if isinstance(action, exp.Query): 3994 action_sql = f"AS {action_sql}" 3995 3996 actions_list.append(action_sql) 3997 3998 actions_sql = self.format_args(*actions_list).lstrip("\n") 3999 4000 iceberg = ( 4001 "ICEBERG " 4002 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4003 else "" 4004 ) 4005 exists = " IF EXISTS" if expression.args.get("exists") else "" 4006 on_cluster = self.sql(expression, "cluster") 4007 on_cluster = f" {on_cluster}" if on_cluster else "" 4008 only = " ONLY" if expression.args.get("only") else "" 4009 options = self.expressions(expression, key="options") 4010 options = f", {options}" if options else "" 4011 kind = self.sql(expression, "kind") 4012 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4013 check = " WITH CHECK" if expression.args.get("check") else "" 4014 cascade = ( 4015 " CASCADE" 4016 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4017 else "" 4018 ) 4019 this = self.sql(expression, "this") 4020 this = f" {this}" if this else "" 4021 4022 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 4023 4024 def altersession_sql(self, expression: exp.AlterSession) -> str: 4025 items_sql = self.expressions(expression, flat=True) 4026 keyword = "UNSET" if expression.args.get("unset") else "SET" 4027 return f"{keyword} {items_sql}" 4028 4029 def add_column_sql(self, expression: exp.Expr) -> str: 4030 sql = self.sql(expression) 4031 if isinstance(expression, exp.Schema): 4032 column_text = " COLUMNS" 4033 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4034 column_text = " COLUMN" 4035 else: 4036 column_text = "" 4037 4038 return f"ADD{column_text} {sql}" 4039 4040 def droppartition_sql(self, expression: exp.DropPartition) -> str: 4041 expressions = self.expressions(expression) 4042 exists = " IF EXISTS " if expression.args.get("exists") else " " 4043 return f"DROP{exists}{expressions}" 4044 4045 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 4046 return f"ADD {self.expressions(expression, indent=False)}" 4047 4048 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4049 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4050 location = self.sql(expression, "location") 4051 location = f" {location}" if location else "" 4052 return f"ADD {exists}{self.sql(expression.this)}{location}" 4053 4054 def distinct_sql(self, expression: exp.Distinct) -> str: 4055 this = self.expressions(expression, flat=True) 4056 4057 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4058 case = exp.case() 4059 for arg in expression.expressions: 4060 case = case.when(arg.is_(exp.null()), exp.null()) 4061 this = self.sql(case.else_(f"({this})")) 4062 4063 this = f" {this}" if this else "" 4064 4065 on = self.sql(expression, "on") 4066 on = f" ON {on}" if on else "" 4067 return f"DISTINCT{this}{on}" 4068 4069 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 4070 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 4071 4072 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 4073 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 4074 4075 def havingmax_sql(self, expression: exp.HavingMax) -> str: 4076 this_sql = self.sql(expression, "this") 4077 expression_sql = self.sql(expression, "expression") 4078 kind = "MAX" if expression.args.get("max") else "MIN" 4079 return f"{this_sql} HAVING {kind} {expression_sql}" 4080 4081 def intdiv_sql(self, expression: exp.IntDiv) -> str: 4082 return self.sql( 4083 exp.Cast( 4084 this=exp.Div(this=expression.this, expression=expression.expression), 4085 to=exp.DataType(this=exp.DType.INT), 4086 ) 4087 ) 4088 4089 def dpipe_sql(self, expression: exp.DPipe) -> str: 4090 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 4091 return self.func("CONCAT", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten())) 4092 return self.binary(expression, "||") 4093 4094 def div_sql(self, expression: exp.Div) -> str: 4095 l, r = expression.left, expression.right 4096 4097 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4098 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4099 4100 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4101 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4102 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4103 4104 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4105 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4106 return self.sql( 4107 exp.cast( 4108 l / r, 4109 to=exp.DType.BIGINT, 4110 ) 4111 ) 4112 4113 return self.binary(expression, "/") 4114 4115 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 4116 n = exp._wrap(expression.this, exp.Binary) 4117 d = exp._wrap(expression.expression, exp.Binary) 4118 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 4119 4120 def overlaps_sql(self, expression: exp.Overlaps) -> str: 4121 return self.binary(expression, "OVERLAPS") 4122 4123 def distance_sql(self, expression: exp.Distance) -> str: 4124 return self.binary(expression, "<->") 4125 4126 def dot_sql(self, expression: exp.Dot) -> str: 4127 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 4128 4129 def eq_sql(self, expression: exp.EQ) -> str: 4130 return self.binary(expression, "=") 4131 4132 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 4133 return self.binary(expression, ":=") 4134 4135 def escape_sql(self, expression: exp.Escape) -> str: 4136 this = expression.this 4137 if ( 4138 isinstance(this, (exp.Like, exp.ILike)) 4139 and isinstance(this.expression, (exp.All, exp.Any)) 4140 and not self.SUPPORTS_LIKE_QUANTIFIERS 4141 ): 4142 return self._like_sql(this, escape=expression) 4143 return self.binary(expression, "ESCAPE") 4144 4145 def glob_sql(self, expression: exp.Glob) -> str: 4146 return self.binary(expression, "GLOB") 4147 4148 def gt_sql(self, expression: exp.GT) -> str: 4149 return self.binary(expression, ">") 4150 4151 def gte_sql(self, expression: exp.GTE) -> str: 4152 return self.binary(expression, ">=") 4153 4154 def is_sql(self, expression: exp.Is) -> str: 4155 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 4156 return self.sql( 4157 expression.this if expression.expression.this else exp.not_(expression.this) 4158 ) 4159 return self.binary(expression, "IS") 4160 4161 def _like_sql( 4162 self, 4163 expression: exp.Like | exp.ILike, 4164 escape: exp.Escape | None = None, 4165 ) -> str: 4166 this = expression.this 4167 rhs = expression.expression 4168 4169 if isinstance(expression, exp.Like): 4170 exp_class: type[exp.Like | exp.ILike] = exp.Like 4171 op = "LIKE" 4172 else: 4173 exp_class = exp.ILike 4174 op = "ILIKE" 4175 4176 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 4177 exprs = rhs.this.unnest() 4178 4179 if isinstance(exprs, exp.Tuple): 4180 exprs = exprs.expressions 4181 else: 4182 exprs = [exprs] 4183 4184 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 4185 4186 def _make_like(expr: exp.Expression) -> exp.Expression: 4187 like: exp.Expression = exp_class(this=this, expression=expr) 4188 if escape: 4189 like = exp.Escape(this=like, expression=escape.expression.copy()) 4190 return like 4191 4192 like_expr: exp.Expr = _make_like(exprs[0]) 4193 for expr in exprs[1:]: 4194 like_expr = connective(like_expr, _make_like(expr), copy=False) 4195 4196 parent = escape.parent if escape else expression.parent 4197 if not isinstance(parent, (type(like_expr), exp.Paren)) and isinstance( 4198 parent, exp.Condition 4199 ): 4200 like_expr = exp.paren(like_expr, copy=False) 4201 4202 return self.sql(like_expr) 4203 4204 return self.binary(expression, op) 4205 4206 def like_sql(self, expression: exp.Like) -> str: 4207 return self._like_sql(expression) 4208 4209 def ilike_sql(self, expression: exp.ILike) -> str: 4210 return self._like_sql(expression) 4211 4212 def match_sql(self, expression: exp.Match) -> str: 4213 return self.binary(expression, "MATCH") 4214 4215 def similarto_sql(self, expression: exp.SimilarTo) -> str: 4216 return self.binary(expression, "SIMILAR TO") 4217 4218 def lt_sql(self, expression: exp.LT) -> str: 4219 return self.binary(expression, "<") 4220 4221 def lte_sql(self, expression: exp.LTE) -> str: 4222 return self.binary(expression, "<=") 4223 4224 def mod_sql(self, expression: exp.Mod) -> str: 4225 return self.binary(expression, "%") 4226 4227 def mul_sql(self, expression: exp.Mul) -> str: 4228 return self.binary(expression, "*") 4229 4230 def neq_sql(self, expression: exp.NEQ) -> str: 4231 return self.binary(expression, "<>") 4232 4233 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 4234 return self.binary(expression, "IS NOT DISTINCT FROM") 4235 4236 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 4237 return self.binary(expression, "IS DISTINCT FROM") 4238 4239 def sub_sql(self, expression: exp.Sub) -> str: 4240 return self.binary(expression, "-") 4241 4242 def trycast_sql(self, expression: exp.TryCast) -> str: 4243 return self.cast_sql(expression, safe_prefix="TRY_") 4244 4245 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 4246 return self.cast_sql(expression) 4247 4248 def try_sql(self, expression: exp.Try) -> str: 4249 if not self.TRY_SUPPORTED: 4250 self.unsupported("Unsupported TRY function") 4251 return self.sql(expression, "this") 4252 4253 return self.func("TRY", expression.this) 4254 4255 def log_sql(self, expression: exp.Log) -> str: 4256 this = expression.this 4257 expr = expression.expression 4258 4259 if self.dialect.LOG_BASE_FIRST is False: 4260 this, expr = expr, this 4261 elif self.dialect.LOG_BASE_FIRST is None and expr: 4262 if this.name in ("2", "10"): 4263 return self.func(f"LOG{this.name}", expr) 4264 4265 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4266 4267 return self.func("LOG", this, expr) 4268 4269 def use_sql(self, expression: exp.Use) -> str: 4270 kind = self.sql(expression, "kind") 4271 kind = f" {kind}" if kind else "" 4272 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 4273 this = f" {this}" if this else "" 4274 return f"USE{kind}{this}" 4275 4276 def binary(self, expression: exp.Binary, op: str) -> str: 4277 sqls: list[str] = [] 4278 stack: list[None | str | exp.Expr] = [expression] 4279 binary_type = type(expression) 4280 4281 while stack: 4282 node = stack.pop() 4283 4284 if type(node) is binary_type: 4285 op_func = node.args.get("operator") 4286 if op_func: 4287 op = f"OPERATOR({self.sql(op_func)})" 4288 4289 stack.append(node.args.get("expression")) 4290 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4291 stack.append(node.args.get("this")) 4292 else: 4293 sqls.append(self.sql(node)) 4294 4295 return "".join(sqls) 4296 4297 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4298 to_clause = self.sql(expression, "to") 4299 if to_clause: 4300 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4301 4302 return self.function_fallback_sql(expression) 4303 4304 def function_fallback_sql(self, expression: exp.Func) -> str: 4305 args = [] 4306 4307 for key in expression.arg_types: 4308 arg_value = expression.args.get(key) 4309 4310 if isinstance(arg_value, list): 4311 for value in arg_value: 4312 args.append(value) 4313 elif arg_value is not None: 4314 args.append(arg_value) 4315 4316 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4317 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4318 else: 4319 name = expression.sql_name() 4320 4321 return self.func(name, *args) 4322 4323 def func( 4324 self, 4325 name: str, 4326 *args: t.Any, 4327 prefix: str = "(", 4328 suffix: str = ")", 4329 normalize: bool = True, 4330 ) -> str: 4331 name = self.normalize_func(name) if normalize else name 4332 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4333 4334 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4335 arg_sqls = tuple( 4336 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4337 ) 4338 if self.pretty and self.too_wide(arg_sqls): 4339 return self.indent( 4340 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4341 ) 4342 return sep.join(arg_sqls) 4343 4344 def too_wide(self, args: t.Iterable) -> bool: 4345 return sum(len(arg) for arg in args) > self.max_text_width 4346 4347 def format_time( 4348 self, 4349 expression: exp.Expr, 4350 inverse_time_mapping: dict[str, str] | None = None, 4351 inverse_time_trie: dict | None = None, 4352 ) -> str | None: 4353 return format_time( 4354 self.sql(expression, "format"), 4355 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4356 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4357 ) 4358 4359 def expressions( 4360 self, 4361 expression: exp.Expr | None = None, 4362 key: str | None = None, 4363 sqls: t.Collection[str | exp.Expr] | None = None, 4364 flat: bool = False, 4365 indent: bool = True, 4366 skip_first: bool = False, 4367 skip_last: bool = False, 4368 sep: str = ", ", 4369 prefix: str = "", 4370 dynamic: bool = False, 4371 new_line: bool = False, 4372 ) -> str: 4373 expressions = expression.args.get(key or "expressions") if expression else sqls 4374 4375 if not expressions: 4376 return "" 4377 4378 if flat: 4379 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4380 4381 num_sqls = len(expressions) 4382 result_sqls = [] 4383 4384 for i, e in enumerate(expressions): 4385 sql = self.sql(e, comment=False) 4386 if not sql: 4387 continue 4388 4389 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4390 4391 if self.pretty: 4392 if self.leading_comma: 4393 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4394 else: 4395 result_sqls.append( 4396 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4397 ) 4398 else: 4399 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4400 4401 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4402 if new_line: 4403 result_sqls.insert(0, "") 4404 result_sqls.append("") 4405 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4406 else: 4407 result_sql = "".join(result_sqls) 4408 4409 return ( 4410 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4411 if indent 4412 else result_sql 4413 ) 4414 4415 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4416 flat = flat or isinstance(expression.parent, exp.Properties) 4417 expressions_sql = self.expressions(expression, flat=flat) 4418 if flat: 4419 return f"{op} {expressions_sql}" 4420 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4421 4422 def naked_property(self, expression: exp.Property) -> str: 4423 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4424 if not property_name: 4425 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4426 return f"{property_name} {self.sql(expression, 'this')}" 4427 4428 def tag_sql(self, expression: exp.Tag) -> str: 4429 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4430 4431 def token_sql(self, token_type: TokenType) -> str: 4432 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4433 4434 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4435 this = self.sql(expression, "this") 4436 expressions = self.no_identify(self.expressions, expression) 4437 expressions = ( 4438 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4439 ) 4440 return f"{this}{expressions}" if expressions.strip() != "" else this 4441 4442 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4443 this = self.sql(expression, "this") 4444 expressions = self.expressions(expression, flat=True) 4445 return f"{this}({expressions})" 4446 4447 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4448 return self.binary(expression, "=>") 4449 4450 def when_sql(self, expression: exp.When) -> str: 4451 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4452 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4453 condition = self.sql(expression, "condition") 4454 condition = f" AND {condition}" if condition else "" 4455 4456 then_expression = expression.args.get("then") 4457 if isinstance(then_expression, exp.Insert): 4458 this = self.sql(then_expression, "this") 4459 this = f"INSERT {this}" if this else "INSERT" 4460 then = self.sql(then_expression, "expression") 4461 then = f"{this} VALUES {then}" if then else this 4462 elif isinstance(then_expression, exp.Update): 4463 if isinstance(then_expression.args.get("expressions"), exp.Star): 4464 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4465 else: 4466 expressions_sql = self.expressions(then_expression) 4467 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4468 else: 4469 then = self.sql(then_expression) 4470 4471 if isinstance(then_expression, (exp.Insert, exp.Update)): 4472 where = self.sql(then_expression, "where") 4473 if where and not self.SUPPORTS_MERGE_WHERE: 4474 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4475 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4476 where = "" 4477 then = f"{then}{where}" 4478 return f"WHEN {matched}{source}{condition} THEN {then}" 4479 4480 def whens_sql(self, expression: exp.Whens) -> str: 4481 return self.expressions(expression, sep=" ", indent=False) 4482 4483 def merge_sql(self, expression: exp.Merge) -> str: 4484 table = expression.this 4485 table_alias = "" 4486 4487 hints = table.args.get("hints") 4488 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4489 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4490 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4491 4492 this = self.sql(table) 4493 using = f"USING {self.sql(expression, 'using')}" 4494 whens = self.sql(expression, "whens") 4495 4496 on = self.sql(expression, "on") 4497 on = f"ON {on}" if on else "" 4498 4499 if not on: 4500 on = self.expressions(expression, key="using_cond") 4501 on = f"USING ({on})" if on else "" 4502 4503 returning = self.sql(expression, "returning") 4504 if returning: 4505 whens = f"{whens}{returning}" 4506 4507 sep = self.sep() 4508 4509 return self.prepend_ctes( 4510 expression, 4511 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4512 ) 4513 4514 @unsupported_args("format") 4515 def tochar_sql(self, expression: exp.ToChar) -> str: 4516 return self.sql(exp.cast(expression.this, exp.DType.TEXT)) 4517 4518 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4519 if not self.SUPPORTS_TO_NUMBER: 4520 self.unsupported("Unsupported TO_NUMBER function") 4521 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4522 4523 fmt = expression.args.get("format") 4524 if not fmt: 4525 self.unsupported("Conversion format is required for TO_NUMBER") 4526 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4527 4528 return self.func("TO_NUMBER", expression.this, fmt) 4529 4530 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4531 this = self.sql(expression, "this") 4532 kind = self.sql(expression, "kind") 4533 settings_sql = self.expressions(expression, key="settings", sep=" ") 4534 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4535 return f"{this}({kind}{args})" 4536 4537 def dictrange_sql(self, expression: exp.DictRange) -> str: 4538 this = self.sql(expression, "this") 4539 max = self.sql(expression, "max") 4540 min = self.sql(expression, "min") 4541 return f"{this}(MIN {min} MAX {max})" 4542 4543 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4544 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4545 4546 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4547 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4548 4549 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4550 def uniquekeyproperty_sql( 4551 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4552 ) -> str: 4553 return f"{prefix} ({self.expressions(expression, flat=True)})" 4554 4555 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4556 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4557 expressions = self.expressions(expression, flat=True) 4558 expressions = f" {self.wrap(expressions)}" if expressions else "" 4559 buckets = self.sql(expression, "buckets") 4560 kind = self.sql(expression, "kind") 4561 buckets = f" BUCKETS {buckets}" if buckets else "" 4562 order = self.sql(expression, "order") 4563 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4564 4565 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4566 return "" 4567 4568 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4569 expressions = self.expressions(expression, key="expressions", flat=True) 4570 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4571 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4572 buckets = self.sql(expression, "buckets") 4573 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4574 4575 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4576 this = self.sql(expression, "this") 4577 having = self.sql(expression, "having") 4578 4579 if having: 4580 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4581 4582 return self.func("ANY_VALUE", this) 4583 4584 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4585 transform = self.func("TRANSFORM", *expression.expressions) 4586 row_format_before = self.sql(expression, "row_format_before") 4587 row_format_before = f" {row_format_before}" if row_format_before else "" 4588 record_writer = self.sql(expression, "record_writer") 4589 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4590 using = f" USING {self.sql(expression, 'command_script')}" 4591 schema = self.sql(expression, "schema") 4592 schema = f" AS {schema}" if schema else "" 4593 row_format_after = self.sql(expression, "row_format_after") 4594 row_format_after = f" {row_format_after}" if row_format_after else "" 4595 record_reader = self.sql(expression, "record_reader") 4596 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4597 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4598 4599 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4600 key_block_size = self.sql(expression, "key_block_size") 4601 if key_block_size: 4602 return f"KEY_BLOCK_SIZE = {key_block_size}" 4603 4604 using = self.sql(expression, "using") 4605 if using: 4606 return f"USING {using}" 4607 4608 parser = self.sql(expression, "parser") 4609 if parser: 4610 return f"WITH PARSER {parser}" 4611 4612 comment = self.sql(expression, "comment") 4613 if comment: 4614 return f"COMMENT {comment}" 4615 4616 visible = expression.args.get("visible") 4617 if visible is not None: 4618 return "VISIBLE" if visible else "INVISIBLE" 4619 4620 engine_attr = self.sql(expression, "engine_attr") 4621 if engine_attr: 4622 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4623 4624 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4625 if secondary_engine_attr: 4626 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4627 4628 self.unsupported("Unsupported index constraint option.") 4629 return "" 4630 4631 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4632 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4633 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4634 4635 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4636 kind = self.sql(expression, "kind") 4637 kind = f"{kind} INDEX" if kind else "INDEX" 4638 this = self.sql(expression, "this") 4639 this = f" {this}" if this else "" 4640 index_type = self.sql(expression, "index_type") 4641 index_type = f" USING {index_type}" if index_type else "" 4642 expressions = self.expressions(expression, flat=True) 4643 expressions = f" ({expressions})" if expressions else "" 4644 options = self.expressions(expression, key="options", sep=" ") 4645 options = f" {options}" if options else "" 4646 return f"{kind}{this}{index_type}{expressions}{options}" 4647 4648 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4649 if self.NVL2_SUPPORTED: 4650 return self.function_fallback_sql(expression) 4651 4652 case = exp.Case().when( 4653 expression.this.is_(exp.null()).not_(copy=False), 4654 expression.args["true"], 4655 copy=False, 4656 ) 4657 else_cond = expression.args.get("false") 4658 if else_cond: 4659 case.else_(else_cond, copy=False) 4660 4661 return self.sql(case) 4662 4663 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4664 this = self.sql(expression, "this") 4665 expr = self.sql(expression, "expression") 4666 position = self.sql(expression, "position") 4667 position = f", {position}" if position else "" 4668 iterator = self.sql(expression, "iterator") 4669 condition = self.sql(expression, "condition") 4670 condition = f" IF {condition}" if condition else "" 4671 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4672 4673 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4674 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4675 4676 def opclass_sql(self, expression: exp.Opclass) -> str: 4677 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4678 4679 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4680 model = self.sql(expression, "this") 4681 model = f"MODEL {model}" 4682 expr = expression.expression 4683 if expr: 4684 expr_sql = self.sql(expression, "expression") 4685 expr_sql = f"TABLE {expr_sql}" if isinstance(expr, exp.Table) else expr_sql 4686 else: 4687 expr_sql = None 4688 4689 parameters = self.sql(expression, "params_struct") or None 4690 4691 return self.func(name, model, expr_sql, parameters) 4692 4693 def predict_sql(self, expression: exp.Predict) -> str: 4694 return self._ml_sql(expression, "PREDICT") 4695 4696 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4697 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4698 return self._ml_sql(expression, name) 4699 4700 def generatetext_sql(self, expression: exp.GenerateText) -> str: 4701 return self._ml_sql(expression, "GENERATE_TEXT") 4702 4703 def generatetable_sql(self, expression: exp.GenerateTable) -> str: 4704 return self._ml_sql(expression, "GENERATE_TABLE") 4705 4706 def generatebool_sql(self, expression: exp.GenerateBool) -> str: 4707 return self._ml_sql(expression, "GENERATE_BOOL") 4708 4709 def generateint_sql(self, expression: exp.GenerateInt) -> str: 4710 return self._ml_sql(expression, "GENERATE_INT") 4711 4712 def generatedouble_sql(self, expression: exp.GenerateDouble) -> str: 4713 return self._ml_sql(expression, "GENERATE_DOUBLE") 4714 4715 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4716 return self._ml_sql(expression, "TRANSLATE") 4717 4718 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4719 return self._ml_sql(expression, "FORECAST") 4720 4721 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4722 this_sql = self.sql(expression, "this") 4723 if isinstance(expression.this, exp.Table): 4724 this_sql = f"TABLE {this_sql}" 4725 4726 return self.func( 4727 "FORECAST", 4728 this_sql, 4729 expression.args.get("data_col"), 4730 expression.args.get("timestamp_col"), 4731 expression.args.get("model"), 4732 expression.args.get("id_cols"), 4733 expression.args.get("horizon"), 4734 expression.args.get("forecast_end_timestamp"), 4735 expression.args.get("confidence_level"), 4736 expression.args.get("output_historical_time_series"), 4737 expression.args.get("context_window"), 4738 ) 4739 4740 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4741 this_sql = self.sql(expression, "this") 4742 if isinstance(expression.this, exp.Table): 4743 this_sql = f"TABLE {this_sql}" 4744 4745 return self.func( 4746 "FEATURES_AT_TIME", 4747 this_sql, 4748 expression.args.get("time"), 4749 expression.args.get("num_rows"), 4750 expression.args.get("ignore_feature_nulls"), 4751 ) 4752 4753 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4754 this_sql = self.sql(expression, "this") 4755 if isinstance(expression.this, exp.Table): 4756 this_sql = f"TABLE {this_sql}" 4757 4758 query_table = self.sql(expression, "query_table") 4759 if isinstance(expression.args["query_table"], exp.Table): 4760 query_table = f"TABLE {query_table}" 4761 4762 return self.func( 4763 "VECTOR_SEARCH", 4764 this_sql, 4765 expression.args.get("column_to_search"), 4766 query_table, 4767 expression.args.get("query_column_to_search"), 4768 expression.args.get("top_k"), 4769 expression.args.get("distance_type"), 4770 expression.args.get("options"), 4771 ) 4772 4773 def forin_sql(self, expression: exp.ForIn) -> str: 4774 this = self.sql(expression, "this") 4775 expression_sql = self.sql(expression, "expression") 4776 return f"FOR {this} DO {expression_sql}" 4777 4778 def refresh_sql(self, expression: exp.Refresh) -> str: 4779 this = self.sql(expression, "this") 4780 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4781 return f"REFRESH {kind}{this}" 4782 4783 def toarray_sql(self, expression: exp.ToArray) -> str: 4784 arg = expression.this 4785 if not arg.type: 4786 import sqlglot.optimizer.annotate_types 4787 4788 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 4789 4790 if arg.is_type(exp.DType.ARRAY): 4791 return self.sql(arg) 4792 4793 cond_for_null = arg.is_(exp.null()) 4794 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4795 4796 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4797 this = expression.this 4798 time_format = self.format_time(expression) 4799 4800 if time_format: 4801 return self.sql( 4802 exp.cast( 4803 exp.StrToTime(this=this, format=expression.args["format"]), 4804 exp.DType.TIME, 4805 ) 4806 ) 4807 4808 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 4809 return self.sql(this) 4810 4811 return self.sql(exp.cast(this, exp.DType.TIME)) 4812 4813 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4814 this = expression.this 4815 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 4816 return self.sql(this) 4817 4818 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect)) 4819 4820 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4821 this = expression.this 4822 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 4823 return self.sql(this) 4824 4825 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect)) 4826 4827 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4828 this = expression.this 4829 time_format = self.format_time(expression) 4830 safe = expression.args.get("safe") 4831 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4832 return self.sql( 4833 exp.cast( 4834 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4835 exp.DType.DATE, 4836 ) 4837 ) 4838 4839 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 4840 return self.sql(this) 4841 4842 if safe: 4843 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 4844 4845 return self.sql(exp.cast(this, exp.DType.DATE)) 4846 4847 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4848 return self.sql( 4849 exp.func( 4850 "DATEDIFF", 4851 expression.this, 4852 exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 4853 "day", 4854 ) 4855 ) 4856 4857 def lastday_sql(self, expression: exp.LastDay) -> str: 4858 if self.LAST_DAY_SUPPORTS_DATE_PART: 4859 return self.function_fallback_sql(expression) 4860 4861 unit = expression.text("unit") 4862 if unit and unit != "MONTH": 4863 self.unsupported("Date parts are not supported in LAST_DAY.") 4864 4865 return self.func("LAST_DAY", expression.this) 4866 4867 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4868 import sqlglot.dialects.dialect 4869 4870 return self.func( 4871 "DATE_ADD", 4872 expression.this, 4873 expression.expression, 4874 sqlglot.dialects.dialect.unit_to_str(expression), 4875 ) 4876 4877 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4878 if self.CAN_IMPLEMENT_ARRAY_ANY: 4879 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4880 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4881 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4882 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4883 4884 import sqlglot.dialects.dialect 4885 4886 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4887 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 4888 self.unsupported("ARRAY_ANY is unsupported") 4889 4890 return self.function_fallback_sql(expression) 4891 4892 def struct_sql(self, expression: exp.Struct) -> str: 4893 expression.set( 4894 "expressions", 4895 [ 4896 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4897 if isinstance(e, exp.PropertyEQ) 4898 else e 4899 for e in expression.expressions 4900 ], 4901 ) 4902 4903 return self.function_fallback_sql(expression) 4904 4905 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4906 low = self.sql(expression, "this") 4907 high = self.sql(expression, "expression") 4908 4909 return f"{low} TO {high}" 4910 4911 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4912 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4913 tables = f" {self.expressions(expression)}" 4914 4915 exists = " IF EXISTS" if expression.args.get("exists") else "" 4916 4917 on_cluster = self.sql(expression, "cluster") 4918 on_cluster = f" {on_cluster}" if on_cluster else "" 4919 4920 identity = self.sql(expression, "identity") 4921 identity = f" {identity} IDENTITY" if identity else "" 4922 4923 option = self.sql(expression, "option") 4924 option = f" {option}" if option else "" 4925 4926 partition = self.sql(expression, "partition") 4927 partition = f" {partition}" if partition else "" 4928 4929 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4930 4931 # This transpiles T-SQL's CONVERT function 4932 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4933 def convert_sql(self, expression: exp.Convert) -> str: 4934 to = expression.this 4935 value = expression.expression 4936 style = expression.args.get("style") 4937 safe = expression.args.get("safe") 4938 strict = expression.args.get("strict") 4939 4940 if not to or not value: 4941 return "" 4942 4943 # Retrieve length of datatype and override to default if not specified 4944 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4945 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4946 4947 transformed: exp.Expr | None = None 4948 cast = exp.Cast if strict else exp.TryCast 4949 4950 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4951 if isinstance(style, exp.Literal) and style.is_int: 4952 import sqlglot.dialects.tsql 4953 4954 style_value = style.name 4955 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4956 if not converted_style: 4957 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4958 4959 fmt = exp.Literal.string(converted_style) 4960 4961 if to.this == exp.DType.DATE: 4962 transformed = exp.StrToDate(this=value, format=fmt) 4963 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 4964 transformed = exp.StrToTime(this=value, format=fmt) 4965 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4966 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4967 elif to.this == exp.DType.TEXT: 4968 transformed = exp.TimeToStr(this=value, format=fmt) 4969 4970 if not transformed: 4971 transformed = cast(this=value, to=to, safe=safe) 4972 4973 return self.sql(transformed) 4974 4975 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4976 this = expression.this 4977 if isinstance(this, exp.JSONPathWildcard): 4978 this = self.json_path_part(this) 4979 return f".{this}" if this else "" 4980 4981 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4982 return f".{this}" 4983 4984 this = self.json_path_part(this) 4985 return ( 4986 f"[{this}]" 4987 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4988 else f".{this}" 4989 ) 4990 4991 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4992 this = self.json_path_part(expression.this) 4993 return f"[{this}]" if this else "" 4994 4995 def _simplify_unless_literal(self, expression: E) -> E: 4996 if not isinstance(expression, exp.Literal): 4997 import sqlglot.optimizer.simplify 4998 4999 expression = sqlglot.optimizer.simplify.simplify(expression, dialect=self.dialect) 5000 5001 return expression 5002 5003 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 5004 this = expression.this 5005 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 5006 self.unsupported( 5007 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 5008 ) 5009 return self.sql(this) 5010 5011 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 5012 if self.IGNORE_NULLS_BEFORE_ORDER: 5013 # The first modifier here will be the one closest to the AggFunc's arg 5014 mods = sorted( 5015 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 5016 key=lambda x: ( 5017 0 5018 if isinstance(x, exp.HavingMax) 5019 else (1 if isinstance(x, exp.Order) else 2) 5020 ), 5021 ) 5022 5023 if mods: 5024 mod = mods[0] 5025 this = expression.__class__(this=mod.this.copy()) 5026 this.meta["inline"] = True 5027 mod.this.replace(this) 5028 return self.sql(expression.this) 5029 5030 agg_func = expression.find(exp.AggFunc) 5031 5032 if agg_func: 5033 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 5034 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 5035 5036 return f"{self.sql(expression, 'this')} {text}" 5037 5038 def _replace_line_breaks(self, string: str) -> str: 5039 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 5040 if self.pretty: 5041 return string.replace("\n", self.SENTINEL_LINE_BREAK) 5042 return string 5043 5044 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5045 option = self.sql(expression, "this") 5046 5047 if expression.expressions: 5048 upper = option.upper() 5049 5050 # Snowflake FILE_FORMAT options are separated by whitespace 5051 sep = " " if upper == "FILE_FORMAT" else ", " 5052 5053 # Databricks copy/format options do not set their list of values with EQ 5054 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5055 values = self.expressions(expression, flat=True, sep=sep) 5056 return f"{option}{op}({values})" 5057 5058 value = self.sql(expression, "expression") 5059 5060 if not value: 5061 return option 5062 5063 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5064 5065 return f"{option}{op}{value}" 5066 5067 def credentials_sql(self, expression: exp.Credentials) -> str: 5068 cred_expr = expression.args.get("credentials") 5069 if isinstance(cred_expr, exp.Literal): 5070 # Redshift case: CREDENTIALS <string> 5071 credentials = self.sql(expression, "credentials") 5072 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5073 else: 5074 # Snowflake case: CREDENTIALS = (...) 5075 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5076 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5077 5078 storage = self.sql(expression, "storage") 5079 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5080 5081 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5082 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5083 5084 iam_role = self.sql(expression, "iam_role") 5085 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5086 5087 region = self.sql(expression, "region") 5088 region = f" REGION {region}" if region else "" 5089 5090 return f"{credentials}{storage}{encryption}{iam_role}{region}" 5091 5092 def copy_sql(self, expression: exp.Copy) -> str: 5093 this = self.sql(expression, "this") 5094 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5095 5096 credentials = self.sql(expression, "credentials") 5097 credentials = self.seg(credentials) if credentials else "" 5098 files = self.expressions(expression, key="files", flat=True) 5099 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5100 5101 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5102 params = self.expressions( 5103 expression, 5104 key="params", 5105 sep=sep, 5106 new_line=True, 5107 skip_last=True, 5108 skip_first=True, 5109 indent=self.COPY_PARAMS_ARE_WRAPPED, 5110 ) 5111 5112 if params: 5113 if self.COPY_PARAMS_ARE_WRAPPED: 5114 params = f" WITH ({params})" 5115 elif not self.pretty and (files or credentials): 5116 params = f" {params}" 5117 5118 return f"COPY{this}{kind} {files}{credentials}{params}" 5119 5120 def semicolon_sql(self, expression: exp.Semicolon) -> str: 5121 return "" 5122 5123 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5124 on_sql = "ON" if expression.args.get("on") else "OFF" 5125 filter_col: str | None = self.sql(expression, "filter_column") 5126 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5127 retention_period: str | None = self.sql(expression, "retention_period") 5128 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5129 5130 if filter_col or retention_period: 5131 on_sql = self.func("ON", filter_col, retention_period) 5132 5133 return f"DATA_DELETION={on_sql}" 5134 5135 def maskingpolicycolumnconstraint_sql( 5136 self, expression: exp.MaskingPolicyColumnConstraint 5137 ) -> str: 5138 this = self.sql(expression, "this") 5139 expressions = self.expressions(expression, flat=True) 5140 expressions = f" USING ({expressions})" if expressions else "" 5141 return f"MASKING POLICY {this}{expressions}" 5142 5143 def gapfill_sql(self, expression: exp.GapFill) -> str: 5144 this = self.sql(expression, "this") 5145 this = f"TABLE {this}" 5146 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 5147 5148 def scope_resolution(self, rhs: str, scope_name: str) -> str: 5149 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 5150 5151 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5152 this = self.sql(expression, "this") 5153 expr = expression.expression 5154 5155 if isinstance(expr, exp.Func): 5156 # T-SQL's CLR functions are case sensitive 5157 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5158 else: 5159 expr = self.sql(expression, "expression") 5160 5161 return self.scope_resolution(expr, this) 5162 5163 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 5164 if self.PARSE_JSON_NAME is None: 5165 return self.sql(expression.this) 5166 5167 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 5168 5169 def rand_sql(self, expression: exp.Rand) -> str: 5170 lower = self.sql(expression, "lower") 5171 upper = self.sql(expression, "upper") 5172 5173 if lower and upper: 5174 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5175 return self.func("RAND", expression.this) 5176 5177 def changes_sql(self, expression: exp.Changes) -> str: 5178 information = self.sql(expression, "information") 5179 information = f"INFORMATION => {information}" 5180 at_before = self.sql(expression, "at_before") 5181 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5182 end = self.sql(expression, "end") 5183 end = f"{self.seg('')}{end}" if end else "" 5184 5185 return f"CHANGES ({information}){at_before}{end}" 5186 5187 def pad_sql(self, expression: exp.Pad) -> str: 5188 prefix = "L" if expression.args.get("is_left") else "R" 5189 5190 fill_pattern = self.sql(expression, "fill_pattern") or None 5191 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5192 fill_pattern = "' '" 5193 5194 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 5195 5196 def summarize_sql(self, expression: exp.Summarize) -> str: 5197 table = " TABLE" if expression.args.get("table") else "" 5198 return f"SUMMARIZE{table} {self.sql(expression.this)}" 5199 5200 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5201 generate_series = exp.GenerateSeries(**expression.args) 5202 5203 parent = expression.parent 5204 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5205 parent = parent.parent 5206 5207 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5208 return self.sql(exp.Unnest(expressions=[generate_series])) 5209 5210 if isinstance(parent, exp.Select): 5211 self.unsupported("GenerateSeries projection unnesting is not supported.") 5212 5213 return self.sql(generate_series) 5214 5215 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5216 if self.SUPPORTS_CONVERT_TIMEZONE: 5217 return self.function_fallback_sql(expression) 5218 5219 source_tz = expression.args.get("source_tz") 5220 target_tz = expression.args.get("target_tz") 5221 timestamp = expression.args.get("timestamp") 5222 5223 if source_tz and timestamp: 5224 timestamp = exp.AtTimeZone( 5225 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5226 ) 5227 5228 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5229 5230 return self.sql(expr) 5231 5232 def json_sql(self, expression: exp.JSON) -> str: 5233 this = self.sql(expression, "this") 5234 this = f" {this}" if this else "" 5235 5236 _with = expression.args.get("with_") 5237 5238 if _with is None: 5239 with_sql = "" 5240 elif not _with: 5241 with_sql = " WITHOUT" 5242 else: 5243 with_sql = " WITH" 5244 5245 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5246 5247 return f"JSON{this}{with_sql}{unique_sql}" 5248 5249 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5250 path = self.sql(expression, "path") 5251 returning = self.sql(expression, "returning") 5252 returning = f" RETURNING {returning}" if returning else "" 5253 5254 on_condition = self.sql(expression, "on_condition") 5255 on_condition = f" {on_condition}" if on_condition else "" 5256 5257 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 5258 5259 def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str: 5260 regexp = " REGEXP" if expression.args.get("regexp") else "" 5261 return f"SKIP{regexp} {self.sql(expression.expression)}" 5262 5263 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5264 else_ = "ELSE " if expression.args.get("else_") else "" 5265 condition = self.sql(expression, "expression") 5266 condition = f"WHEN {condition} THEN " if condition else else_ 5267 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5268 return f"{condition}{insert}" 5269 5270 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 5271 kind = self.sql(expression, "kind") 5272 expressions = self.seg(self.expressions(expression, sep=" ")) 5273 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 5274 return res 5275 5276 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5277 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5278 empty = expression.args.get("empty") 5279 empty = ( 5280 f"DEFAULT {empty} ON EMPTY" 5281 if isinstance(empty, exp.Expr) 5282 else self.sql(expression, "empty") 5283 ) 5284 5285 error = expression.args.get("error") 5286 error = ( 5287 f"DEFAULT {error} ON ERROR" 5288 if isinstance(error, exp.Expr) 5289 else self.sql(expression, "error") 5290 ) 5291 5292 if error and empty: 5293 error = ( 5294 f"{empty} {error}" 5295 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5296 else f"{error} {empty}" 5297 ) 5298 empty = "" 5299 5300 null = self.sql(expression, "null") 5301 5302 return f"{empty}{error}{null}" 5303 5304 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 5305 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 5306 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 5307 5308 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5309 this = self.sql(expression, "this") 5310 path = self.sql(expression, "path") 5311 5312 passing = self.expressions(expression, "passing") 5313 passing = f" PASSING {passing}" if passing else "" 5314 5315 on_condition = self.sql(expression, "on_condition") 5316 on_condition = f" {on_condition}" if on_condition else "" 5317 5318 path = f"{path}{passing}{on_condition}" 5319 5320 return self.func("JSON_EXISTS", this, path) 5321 5322 def _add_arrayagg_null_filter( 5323 self, 5324 array_agg_sql: str, 5325 array_agg_expr: exp.ArrayAgg, 5326 column_expr: exp.Expr, 5327 ) -> str: 5328 """ 5329 Add NULL filter to ARRAY_AGG if dialect requires it. 5330 5331 Args: 5332 array_agg_sql: The generated ARRAY_AGG SQL string 5333 array_agg_expr: The ArrayAgg expression node 5334 column_expr: The column/expression to filter (before ORDER BY wrapping) 5335 5336 Returns: 5337 SQL string with FILTER clause added if needed 5338 """ 5339 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5340 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5341 if not ( 5342 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5343 ): 5344 return array_agg_sql 5345 5346 parent = array_agg_expr.parent 5347 if isinstance(parent, exp.Filter): 5348 parent_cond = parent.expression.this 5349 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5350 elif column_expr.find(exp.Column): 5351 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5352 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5353 this_sql = ( 5354 self.expressions(column_expr) 5355 if isinstance(column_expr, exp.Distinct) 5356 else self.sql(column_expr) 5357 ) 5358 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5359 5360 return array_agg_sql 5361 5362 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5363 array_agg = self.function_fallback_sql(expression) 5364 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5365 5366 def slice_sql(self, expression: exp.Slice) -> str: 5367 step = self.sql(expression, "step") 5368 end = self.sql(expression.expression) 5369 begin = self.sql(expression.this) 5370 5371 sql = f"{end}:{step}" if step else end 5372 return f"{begin}:{sql}" if sql else f"{begin}:" 5373 5374 def apply_sql(self, expression: exp.Apply) -> str: 5375 this = self.sql(expression, "this") 5376 expr = self.sql(expression, "expression") 5377 5378 return f"{this} APPLY({expr})" 5379 5380 def _grant_or_revoke_sql( 5381 self, 5382 expression: exp.Grant | exp.Revoke, 5383 keyword: str, 5384 preposition: str, 5385 grant_option_prefix: str = "", 5386 grant_option_suffix: str = "", 5387 ) -> str: 5388 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5389 5390 kind = self.sql(expression, "kind") 5391 kind = f" {kind}" if kind else "" 5392 5393 securable = self.sql(expression, "securable") 5394 securable = f" {securable}" if securable else "" 5395 5396 principals = self.expressions(expression, key="principals", flat=True) 5397 5398 if not expression.args.get("grant_option"): 5399 grant_option_prefix = grant_option_suffix = "" 5400 5401 # cascade for revoke only 5402 cascade = self.sql(expression, "cascade") 5403 cascade = f" {cascade}" if cascade else "" 5404 5405 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5406 5407 def grant_sql(self, expression: exp.Grant) -> str: 5408 return self._grant_or_revoke_sql( 5409 expression, 5410 keyword="GRANT", 5411 preposition="TO", 5412 grant_option_suffix=" WITH GRANT OPTION", 5413 ) 5414 5415 def revoke_sql(self, expression: exp.Revoke) -> str: 5416 return self._grant_or_revoke_sql( 5417 expression, 5418 keyword="REVOKE", 5419 preposition="FROM", 5420 grant_option_prefix="GRANT OPTION FOR ", 5421 ) 5422 5423 def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str: 5424 this = self.sql(expression, "this") 5425 columns = self.expressions(expression, flat=True) 5426 columns = f"({columns})" if columns else "" 5427 5428 return f"{this}{columns}" 5429 5430 def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str: 5431 this = self.sql(expression, "this") 5432 5433 kind = self.sql(expression, "kind") 5434 kind = f"{kind} " if kind else "" 5435 5436 return f"{kind}{this}" 5437 5438 def columns_sql(self, expression: exp.Columns) -> str: 5439 func = self.function_fallback_sql(expression) 5440 if expression.args.get("unpack"): 5441 func = f"*{func}" 5442 5443 return func 5444 5445 def overlay_sql(self, expression: exp.Overlay) -> str: 5446 this = self.sql(expression, "this") 5447 expr = self.sql(expression, "expression") 5448 from_sql = self.sql(expression, "from_") 5449 for_sql = self.sql(expression, "for_") 5450 for_sql = f" FOR {for_sql}" if for_sql else "" 5451 5452 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5453 5454 @unsupported_args("format") 5455 def todouble_sql(self, expression: exp.ToDouble) -> str: 5456 cast = exp.TryCast if expression.args.get("safe") else exp.Cast 5457 return self.sql(cast(this=expression.this, to=exp.DType.DOUBLE.into_expr())) 5458 5459 def string_sql(self, expression: exp.String) -> str: 5460 this = expression.this 5461 zone = expression.args.get("zone") 5462 5463 if zone: 5464 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5465 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5466 # set for source_tz to transpile the time conversion before the STRING cast 5467 this = exp.ConvertTimezone( 5468 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5469 ) 5470 5471 return self.sql(exp.cast(this, exp.DType.VARCHAR)) 5472 5473 def median_sql(self, expression: exp.Median) -> str: 5474 if not self.SUPPORTS_MEDIAN: 5475 return self.sql( 5476 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5477 ) 5478 5479 return self.function_fallback_sql(expression) 5480 5481 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5482 filler = self.sql(expression, "this") 5483 filler = f" {filler}" if filler else "" 5484 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5485 return f"TRUNCATE{filler} {with_count}" 5486 5487 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5488 if self.SUPPORTS_UNIX_SECONDS: 5489 return self.function_fallback_sql(expression) 5490 5491 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5492 5493 return self.sql( 5494 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5495 ) 5496 5497 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5498 dim = expression.expression 5499 5500 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5501 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5502 if not (dim.is_int and dim.name == "1"): 5503 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5504 dim = None 5505 5506 # If dimension is required but not specified, default initialize it 5507 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5508 dim = exp.Literal.number(1) 5509 5510 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5511 5512 def attach_sql(self, expression: exp.Attach) -> str: 5513 this = self.sql(expression, "this") 5514 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5515 expressions = self.expressions(expression) 5516 expressions = f" ({expressions})" if expressions else "" 5517 5518 return f"ATTACH{exists_sql} {this}{expressions}" 5519 5520 def detach_sql(self, expression: exp.Detach) -> str: 5521 kind = self.sql(expression, "kind") 5522 kind = f" {kind}" if kind else "" 5523 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5524 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5525 exists = " IF EXISTS" if expression.args.get("exists") else "" 5526 if exists: 5527 kind = kind or " DATABASE" 5528 5529 this = self.sql(expression, "this") 5530 this = f" {this}" if this else "" 5531 cluster = self.sql(expression, "cluster") 5532 cluster = f" {cluster}" if cluster else "" 5533 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5534 sync = " SYNC" if expression.args.get("sync") else "" 5535 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}" 5536 5537 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5538 this = self.sql(expression, "this") 5539 value = self.sql(expression, "expression") 5540 value = f" {value}" if value else "" 5541 return f"{this}{value}" 5542 5543 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5544 return ( 5545 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5546 ) 5547 5548 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5549 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5550 encode = f"{encode} {self.sql(expression, 'this')}" 5551 5552 properties = expression.args.get("properties") 5553 if properties: 5554 encode = f"{encode} {self.properties(properties)}" 5555 5556 return encode 5557 5558 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5559 this = self.sql(expression, "this") 5560 include = f"INCLUDE {this}" 5561 5562 column_def = self.sql(expression, "column_def") 5563 if column_def: 5564 include = f"{include} {column_def}" 5565 5566 alias = self.sql(expression, "alias") 5567 if alias: 5568 include = f"{include} AS {alias}" 5569 5570 return include 5571 5572 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5573 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5574 name = f"{prefix} {self.sql(expression, 'this')}" 5575 return self.func("XMLELEMENT", name, *expression.expressions) 5576 5577 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5578 this = self.sql(expression, "this") 5579 expr = self.sql(expression, "expression") 5580 expr = f"({expr})" if expr else "" 5581 return f"{this}{expr}" 5582 5583 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5584 partitions = self.expressions(expression, "partition_expressions") 5585 create = self.expressions(expression, "create_expressions") 5586 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5587 5588 def partitionbyrangepropertydynamic_sql( 5589 self, expression: exp.PartitionByRangePropertyDynamic 5590 ) -> str: 5591 start = self.sql(expression, "start") 5592 end = self.sql(expression, "end") 5593 5594 every = expression.args["every"] 5595 if isinstance(every, exp.Interval) and every.this.is_string: 5596 every.this.replace(exp.Literal.number(every.name)) 5597 5598 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5599 5600 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5601 name = self.sql(expression, "this") 5602 values = self.expressions(expression, flat=True) 5603 5604 return f"NAME {name} VALUE {values}" 5605 5606 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5607 kind = self.sql(expression, "kind") 5608 sample = self.sql(expression, "sample") 5609 return f"SAMPLE {sample} {kind}" 5610 5611 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5612 kind = self.sql(expression, "kind") 5613 option = self.sql(expression, "option") 5614 option = f" {option}" if option else "" 5615 this = self.sql(expression, "this") 5616 this = f" {this}" if this else "" 5617 columns = self.expressions(expression) 5618 columns = f" {columns}" if columns else "" 5619 return f"{kind}{option} STATISTICS{this}{columns}" 5620 5621 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5622 this = self.sql(expression, "this") 5623 columns = self.expressions(expression) 5624 inner_expression = self.sql(expression, "expression") 5625 inner_expression = f" {inner_expression}" if inner_expression else "" 5626 update_options = self.sql(expression, "update_options") 5627 update_options = f" {update_options} UPDATE" if update_options else "" 5628 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5629 5630 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5631 kind = self.sql(expression, "kind") 5632 kind = f" {kind}" if kind else "" 5633 return f"DELETE{kind} STATISTICS" 5634 5635 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5636 inner_expression = self.sql(expression, "expression") 5637 return f"LIST CHAINED ROWS{inner_expression}" 5638 5639 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5640 kind = self.sql(expression, "kind") 5641 this = self.sql(expression, "this") 5642 this = f" {this}" if this else "" 5643 inner_expression = self.sql(expression, "expression") 5644 return f"VALIDATE {kind}{this}{inner_expression}" 5645 5646 def analyze_sql(self, expression: exp.Analyze) -> str: 5647 options = self.expressions(expression, key="options", sep=" ") 5648 options = f" {options}" if options else "" 5649 kind = self.sql(expression, "kind") 5650 kind = f" {kind}" if kind else "" 5651 this = self.sql(expression, "this") 5652 this = f" {this}" if this else "" 5653 mode = self.sql(expression, "mode") 5654 mode = f" {mode}" if mode else "" 5655 properties = self.sql(expression, "properties") 5656 properties = f" {properties}" if properties else "" 5657 partition = self.sql(expression, "partition") 5658 partition = f" {partition}" if partition else "" 5659 inner_expression = self.sql(expression, "expression") 5660 inner_expression = f" {inner_expression}" if inner_expression else "" 5661 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5662 5663 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5664 this = self.sql(expression, "this") 5665 namespaces = self.expressions(expression, key="namespaces") 5666 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5667 passing = self.expressions(expression, key="passing") 5668 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5669 columns = self.expressions(expression, key="columns") 5670 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5671 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5672 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5673 5674 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5675 this = self.sql(expression, "this") 5676 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5677 5678 def export_sql(self, expression: exp.Export) -> str: 5679 this = self.sql(expression, "this") 5680 connection = self.sql(expression, "connection") 5681 connection = f"WITH CONNECTION {connection} " if connection else "" 5682 options = self.sql(expression, "options") 5683 return f"EXPORT DATA {connection}{options} AS {this}" 5684 5685 def declare_sql(self, expression: exp.Declare) -> str: 5686 replace = "OR REPLACE " if expression.args.get("replace") else "" 5687 return f"DECLARE {replace}{self.expressions(expression, flat=True)}" 5688 5689 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5690 variables = self.expressions(expression, "this") 5691 default = self.sql(expression, "default") 5692 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5693 5694 kind = self.sql(expression, "kind") 5695 if isinstance(expression.args.get("kind"), exp.Schema): 5696 kind = f"TABLE {kind}" 5697 5698 kind = f" {kind}" if kind else "" 5699 5700 return f"{variables}{kind}{default}" 5701 5702 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5703 kind = self.sql(expression, "kind") 5704 this = self.sql(expression, "this") 5705 set = self.sql(expression, "expression") 5706 using = self.sql(expression, "using") 5707 using = f" USING {using}" if using else "" 5708 5709 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5710 5711 return f"{kind_sql} {this} SET {set}{using}" 5712 5713 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5714 params = self.expressions(expression, key="params", flat=True) 5715 return self.func(expression.name, *expression.expressions) + f"({params})" 5716 5717 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5718 return self.func(expression.name, *expression.expressions) 5719 5720 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5721 return self.anonymousaggfunc_sql(expression) 5722 5723 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5724 return self.parameterizedagg_sql(expression) 5725 5726 def show_sql(self, expression: exp.Show) -> str: 5727 self.unsupported("Unsupported SHOW statement") 5728 return "" 5729 5730 def install_sql(self, expression: exp.Install) -> str: 5731 self.unsupported("Unsupported INSTALL statement") 5732 return "" 5733 5734 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5735 # Snowflake GET/PUT statements: 5736 # PUT <file> <internalStage> <properties> 5737 # GET <internalStage> <file> <properties> 5738 props = expression.args.get("properties") 5739 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5740 this = self.sql(expression, "this") 5741 target = self.sql(expression, "target") 5742 5743 if isinstance(expression, exp.Put): 5744 return f"PUT {this} {target}{props_sql}" 5745 else: 5746 return f"GET {target} {this}{props_sql}" 5747 5748 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5749 this = self.sql(expression, "this") 5750 expr = self.sql(expression, "expression") 5751 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5752 return f"TRANSLATE({this} USING {expr}{with_error})" 5753 5754 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5755 if self.SUPPORTS_DECODE_CASE: 5756 return self.func("DECODE", *expression.expressions) 5757 5758 decode_expr, *expressions = expression.expressions 5759 5760 ifs = [] 5761 for search, result in zip(expressions[::2], expressions[1::2]): 5762 if isinstance(search, exp.Literal): 5763 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 5764 elif isinstance(search, exp.Null): 5765 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 5766 else: 5767 if isinstance(search, exp.Binary): 5768 search = exp.paren(search) 5769 5770 cond = exp.or_( 5771 decode_expr.eq(search), 5772 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5773 copy=False, 5774 ) 5775 ifs.append(exp.If(this=cond, true=result)) 5776 5777 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5778 return self.sql(case) 5779 5780 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5781 this = self.sql(expression, "this") 5782 this = self.seg(this, sep="") 5783 dimensions = self.expressions( 5784 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5785 ) 5786 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5787 metrics = self.expressions( 5788 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5789 ) 5790 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5791 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5792 facts = self.seg(f"FACTS {facts}") if facts else "" 5793 where = self.sql(expression, "where") 5794 where = self.seg(f"WHERE {where}") if where else "" 5795 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5796 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5797 5798 def getextract_sql(self, expression: exp.GetExtract) -> str: 5799 this = expression.this 5800 expr = expression.expression 5801 5802 if not this.type or not expression.type: 5803 import sqlglot.optimizer.annotate_types 5804 5805 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 5806 5807 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 5808 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5809 5810 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5811 5812 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5813 return self.sql( 5814 exp.DateAdd( 5815 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DType.DATE), 5816 expression=expression.this, 5817 unit=exp.var("DAY"), 5818 ) 5819 ) 5820 5821 def space_sql(self: Generator, expression: exp.Space) -> str: 5822 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5823 5824 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5825 return f"BUILD {self.sql(expression, 'this')}" 5826 5827 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5828 method = self.sql(expression, "method") 5829 kind = expression.args.get("kind") 5830 if not kind: 5831 return f"REFRESH {method}" 5832 5833 every = self.sql(expression, "every") 5834 unit = self.sql(expression, "unit") 5835 every = f" EVERY {every} {unit}" if every else "" 5836 starts = self.sql(expression, "starts") 5837 starts = f" STARTS {starts}" if starts else "" 5838 5839 return f"REFRESH {method} ON {kind}{every}{starts}" 5840 5841 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5842 self.unsupported("The model!attribute syntax is not supported") 5843 return "" 5844 5845 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5846 return self.func("DIRECTORY", expression.this) 5847 5848 def uuid_sql(self, expression: exp.Uuid) -> str: 5849 is_string = expression.args.get("is_string", False) 5850 uuid_func_sql = self.func("UUID") 5851 5852 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5853 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 5854 5855 return uuid_func_sql 5856 5857 def initcap_sql(self, expression: exp.Initcap) -> str: 5858 delimiters = expression.expression 5859 5860 if delimiters: 5861 # do not generate delimiters arg if we are round-tripping from default delimiters 5862 if ( 5863 delimiters.is_string 5864 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5865 ): 5866 delimiters = None 5867 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5868 self.unsupported("INITCAP does not support custom delimiters") 5869 delimiters = None 5870 5871 return self.func("INITCAP", expression.this, delimiters) 5872 5873 def localtime_sql(self, expression: exp.Localtime) -> str: 5874 this = expression.this 5875 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5876 5877 def localtimestamp_sql(self, expression: exp.Localtimestamp) -> str: 5878 this = expression.this 5879 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5880 5881 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5882 this = expression.this.name.upper() 5883 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5884 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5885 return "WEEK" 5886 5887 return self.func("WEEK", expression.this) 5888 5889 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5890 this = self.expressions(expression) 5891 charset = self.sql(expression, "charset") 5892 using = f" USING {charset}" if charset else "" 5893 return self.func(name, this + using) 5894 5895 def block_sql(self, expression: exp.Block) -> str: 5896 expressions = self.expressions(expression, sep="; ", flat=True) 5897 return f"{expressions}" if expressions else "" 5898 5899 def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str: 5900 self.unsupported("Unsupported Stored Procedure syntax") 5901 return "" 5902 5903 def ifblock_sql(self, expression: exp.IfBlock) -> str: 5904 self.unsupported("Unsupported If block syntax") 5905 return "" 5906 5907 def whileblock_sql(self, expression: exp.WhileBlock) -> str: 5908 self.unsupported("Unsupported While block syntax") 5909 return "" 5910 5911 def execute_sql(self, expression: exp.Execute) -> str: 5912 self.unsupported("Unsupported Execute syntax") 5913 return "" 5914 5915 def executesql_sql(self, expression: exp.ExecuteSql) -> str: 5916 self.unsupported("Unsupported Execute syntax") 5917 return "" 5918 5919 def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str: 5920 props = self.expressions(expression, sep=" ") 5921 return f"MODIFY {props}" 5922 5923 def usingproperty_sql(self, expression: exp.UsingProperty) -> str: 5924 kind = expression.args.get("kind") 5925 return f"USING {kind} {self.sql(expression, 'this')}" 5926 5927 def renameindex_sql(self, expression: exp.RenameIndex) -> str: 5928 this = self.sql(expression, "this") 5929 to = self.sql(expression, "to") 5930 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)
842 def __init__( 843 self, 844 pretty: bool | int | None = None, 845 identify: str | bool = False, 846 normalize: bool = False, 847 pad: int = 2, 848 indent: int = 2, 849 normalize_functions: str | bool | None = None, 850 unsupported_level: ErrorLevel = ErrorLevel.WARN, 851 max_unsupported: int = 3, 852 leading_comma: bool = False, 853 max_text_width: int = 80, 854 comments: bool = True, 855 dialect: DialectType = None, 856 ): 857 import sqlglot 858 import sqlglot.dialects.dialect 859 860 self.pretty = pretty if pretty is not None else sqlglot.pretty 861 self.identify = identify 862 self.normalize = normalize 863 self.pad = pad 864 self._indent = indent 865 self.unsupported_level = unsupported_level 866 self.max_unsupported = max_unsupported 867 self.leading_comma = leading_comma 868 self.max_text_width = max_text_width 869 self.comments = comments 870 self.dialect = sqlglot.dialects.dialect.Dialect.get_or_raise(dialect) 871 872 # This is both a Dialect property and a Generator argument, so we prioritize the latter 873 self.normalize_functions = ( 874 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 875 ) 876 877 self.unsupported_messages: list[str] = [] 878 self._escaped_quote_end: str = ( 879 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 880 ) 881 self._escaped_byte_quote_end: str = ( 882 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 883 if self.dialect.BYTE_END 884 else "" 885 ) 886 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 887 888 self._next_name = name_sequence("_t") 889 890 self._identifier_start = self.dialect.IDENTIFIER_START 891 self._identifier_end = self.dialect.IDENTIFIER_END 892 893 self._quote_json_path_key_using_brackets = True 894 895 cls = type(self) 896 dispatch = _DISPATCH_CACHE.get(cls) 897 if dispatch is None: 898 dispatch = _build_dispatch(cls) 899 _DISPATCH_CACHE[cls] = dispatch 900 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.JSONPathScript'>, <class 'sqlglot.expressions.query.JSONPathRoot'>, <class 'sqlglot.expressions.query.JSONPathRecursive'>, <class 'sqlglot.expressions.query.JSONPathKey'>, <class 'sqlglot.expressions.query.JSONPathWildcard'>, <class 'sqlglot.expressions.query.JSONPathFilter'>, <class 'sqlglot.expressions.query.JSONPathUnion'>, <class 'sqlglot.expressions.query.JSONPathSubscript'>, <class 'sqlglot.expressions.query.JSONPathSelector'>, <class 'sqlglot.expressions.query.JSONPathSlice'>}
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.NCHAR: 'NCHAR'>, <DType.CHAR: 'CHAR'>, <DType.NVARCHAR: 'NVARCHAR'>}
RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: ClassVar[tuple[type[sqlglot.expressions.core.Expr], ...]] =
()
902 def generate(self, expression: exp.Expr, copy: bool = True) -> str: 903 """ 904 Generates the SQL string corresponding to the given syntax tree. 905 906 Args: 907 expression: The syntax tree. 908 copy: Whether to copy the expression. The generator performs mutations so 909 it is safer to copy. 910 911 Returns: 912 The SQL string corresponding to `expression`. 913 """ 914 if copy: 915 expression = expression.copy() 916 917 expression = self.preprocess(expression) 918 919 self.unsupported_messages = [] 920 sql = self.sql(expression).strip() 921 922 if self.pretty: 923 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 924 925 if self.unsupported_level == ErrorLevel.IGNORE: 926 return sql 927 928 if self.unsupported_level == ErrorLevel.WARN: 929 for msg in self.unsupported_messages: 930 logger.warning(msg) 931 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 932 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 933 934 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.
936 def preprocess(self, expression: exp.Expr) -> exp.Expr: 937 """Apply generic preprocessing transformations to a given expression.""" 938 expression = self._move_ctes_to_top_level(expression) 939 940 if self.ENSURE_BOOLS: 941 import sqlglot.transforms 942 943 expression = sqlglot.transforms.ensure_bools(expression) 944 945 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
969 def sanitize_comment(self, comment: str) -> str: 970 comment = " " + comment if comment[0].strip() else comment 971 comment = comment + " " if comment[-1].strip() else comment 972 973 # Escape block comment markers to prevent premature closure or unintended nesting. 974 # This is necessary because single-line comments (--) are converted to block comments 975 # (/* */) on output, and any */ in the original text would close the comment early. 976 comment = comment.replace("*/", "* /").replace("/*", "/ *") 977 978 return comment
def
maybe_comment( self, sql: str, expression: sqlglot.expressions.core.Expr | None = None, comments: list[str] | None = None, separated: bool = False) -> str:
980 def maybe_comment( 981 self, 982 sql: str, 983 expression: exp.Expr | None = None, 984 comments: list[str] | None = None, 985 separated: bool = False, 986 ) -> str: 987 comments = ( 988 ((expression and expression.comments) if comments is None else comments) # type: ignore 989 if self.comments 990 else None 991 ) 992 993 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 994 return sql 995 996 comments_sql = " ".join( 997 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 998 ) 999 1000 if not comments_sql: 1001 return sql 1002 1003 comments_sql = self._replace_line_breaks(comments_sql) 1004 1005 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 1006 return ( 1007 f"{self.sep()}{comments_sql}{sql}" 1008 if not sql or sql[0].isspace() 1009 else f"{comments_sql}{self.sep()}{sql}" 1010 ) 1011 1012 return f"{sql} {comments_sql}"
1014 def wrap(self, expression: exp.Expr | str) -> str: 1015 this_sql = ( 1016 self.sql(expression) 1017 if isinstance(expression, exp.UNWRAPPED_QUERIES) 1018 else self.sql(expression, "this") 1019 ) 1020 if not this_sql: 1021 return "()" 1022 1023 this_sql = self.indent(this_sql, level=1, pad=0) 1024 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:
1040 def indent( 1041 self, 1042 sql: str, 1043 level: int = 0, 1044 pad: int | None = None, 1045 skip_first: bool = False, 1046 skip_last: bool = False, 1047 ) -> str: 1048 if not self.pretty or not sql: 1049 return sql 1050 1051 pad = self.pad if pad is None else pad 1052 lines = sql.split("\n") 1053 1054 return "\n".join( 1055 ( 1056 line 1057 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 1058 else f"{' ' * (level * self._indent + pad)}{line}" 1059 ) 1060 for i, line in enumerate(lines) 1061 )
def
sql( self, expression: str | sqlglot.expressions.core.Expr | None, key: str | None = None, comment: bool = True) -> str:
1063 def sql( 1064 self, 1065 expression: str | exp.Expr | None, 1066 key: str | None = None, 1067 comment: bool = True, 1068 ) -> str: 1069 if not expression: 1070 return "" 1071 1072 if isinstance(expression, str): 1073 return expression 1074 1075 if key: 1076 value = expression.args.get(key) 1077 if value: 1078 return self.sql(value) 1079 return "" 1080 1081 handler = self._dispatch.get(expression.__class__) 1082 1083 if handler: 1084 sql = handler(self, expression) 1085 elif isinstance(expression, exp.Func): 1086 sql = self.function_fallback_sql(expression) 1087 elif isinstance(expression, exp.Property): 1088 sql = self.property_sql(expression) 1089 else: 1090 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 1091 1092 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1099 def cache_sql(self, expression: exp.Cache) -> str: 1100 lazy = " LAZY" if expression.args.get("lazy") else "" 1101 table = self.sql(expression, "this") 1102 options = expression.args.get("options") 1103 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1104 sql = self.sql(expression, "expression") 1105 sql = f" AS{self.sep()}{sql}" if sql else "" 1106 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1107 return self.prepend_ctes(expression, sql)
1125 def column_sql(self, expression: exp.Column) -> str: 1126 join_mark = " (+)" if expression.args.get("join_mark") else "" 1127 1128 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1129 join_mark = "" 1130 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1131 1132 return f"{self.column_parts(expression)}{join_mark}"
1143 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1144 column = self.sql(expression, "this") 1145 kind = self.sql(expression, "kind") 1146 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1147 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1148 kind = f"{sep}{kind}" if kind else "" 1149 constraints = f" {constraints}" if constraints else "" 1150 position = self.sql(expression, "position") 1151 position = f" {position}" if position else "" 1152 1153 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1154 kind = "" 1155 1156 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:
1163 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1164 this = self.sql(expression, "this") 1165 if expression.args.get("not_null"): 1166 persisted = " PERSISTED NOT NULL" 1167 elif expression.args.get("persisted"): 1168 persisted = " PERSISTED" 1169 else: 1170 persisted = "" 1171 1172 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:
1185 def generatedasidentitycolumnconstraint_sql( 1186 self, expression: exp.GeneratedAsIdentityColumnConstraint 1187 ) -> str: 1188 this = "" 1189 if expression.this is not None: 1190 on_null = " ON NULL" if expression.args.get("on_null") else "" 1191 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1192 1193 start = expression.args.get("start") 1194 start = f"START WITH {start}" if start else "" 1195 increment = expression.args.get("increment") 1196 increment = f" INCREMENT BY {increment}" if increment else "" 1197 minvalue = expression.args.get("minvalue") 1198 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1199 maxvalue = expression.args.get("maxvalue") 1200 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1201 cycle = expression.args.get("cycle") 1202 cycle_sql = "" 1203 1204 if cycle is not None: 1205 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1206 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1207 1208 sequence_opts = "" 1209 if start or increment or cycle_sql: 1210 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1211 sequence_opts = f" ({sequence_opts.strip()})" 1212 1213 expr = self.sql(expression, "expression") 1214 expr = f"({expr})" if expr else "IDENTITY" 1215 1216 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.GeneratedAsRowColumnConstraint) -> str:
1218 def generatedasrowcolumnconstraint_sql( 1219 self, expression: exp.GeneratedAsRowColumnConstraint 1220 ) -> str: 1221 start = "START" if expression.args.get("start") else "END" 1222 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1223 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:
1233 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1234 desc = expression.args.get("desc") 1235 if desc is not None: 1236 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1237 options = self.expressions(expression, key="options", flat=True, sep=" ") 1238 options = f" {options}" if options else "" 1239 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.UniqueColumnConstraint) -> str:
1241 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1242 this = self.sql(expression, "this") 1243 this = f" {this}" if this else "" 1244 index_type = expression.args.get("index_type") 1245 index_type = f" USING {index_type}" if index_type else "" 1246 on_conflict = self.sql(expression, "on_conflict") 1247 on_conflict = f" {on_conflict}" if on_conflict else "" 1248 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1249 options = self.expressions(expression, key="options", flat=True, sep=" ") 1250 options = f" {options}" if options else "" 1251 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
def
inoutcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.InOutColumnConstraint) -> str:
1253 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1254 input_ = expression.args.get("input_") 1255 output = expression.args.get("output") 1256 variadic = expression.args.get("variadic") 1257 1258 # VARIADIC is mutually exclusive with IN/OUT/INOUT 1259 if variadic: 1260 return "VARIADIC" 1261 1262 if input_ and output: 1263 return f"IN{self.INOUT_SEPARATOR}OUT" 1264 if input_: 1265 return "IN" 1266 if output: 1267 return "OUT" 1268 1269 return ""
def
createable_sql( self, expression: sqlglot.expressions.ddl.Create, locations: collections.defaultdict) -> str:
1274 def create_sql(self, expression: exp.Create) -> str: 1275 kind = self.sql(expression, "kind") 1276 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1277 1278 properties = expression.args.get("properties") 1279 1280 if ( 1281 kind == "TRIGGER" 1282 and properties 1283 and properties.expressions 1284 and isinstance(properties.expressions[0], exp.TriggerProperties) 1285 and properties.expressions[0].args.get("constraint") 1286 ): 1287 kind = f"CONSTRAINT {kind}" 1288 1289 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1290 1291 this = self.createable_sql(expression, properties_locs) 1292 1293 properties_sql = "" 1294 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1295 exp.Properties.Location.POST_WITH 1296 ): 1297 props_ast = exp.Properties( 1298 expressions=[ 1299 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1300 *properties_locs[exp.Properties.Location.POST_WITH], 1301 ] 1302 ) 1303 props_ast.parent = expression 1304 properties_sql = self.sql(props_ast) 1305 1306 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1307 properties_sql = self.sep() + properties_sql 1308 elif not self.pretty: 1309 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1310 properties_sql = f" {properties_sql}" 1311 1312 begin = " BEGIN" if expression.args.get("begin") else "" 1313 1314 expression_sql = self.sql(expression, "expression") 1315 if expression_sql: 1316 expression_sql = f"{begin}{self.sep()}{expression_sql}" 1317 1318 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1319 postalias_props_sql = "" 1320 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1321 postalias_props_sql = self.properties( 1322 exp.Properties( 1323 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1324 ), 1325 wrapped=False, 1326 ) 1327 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1328 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1329 1330 postindex_props_sql = "" 1331 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1332 postindex_props_sql = self.properties( 1333 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1334 wrapped=False, 1335 prefix=" ", 1336 ) 1337 1338 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1339 indexes = f" {indexes}" if indexes else "" 1340 index_sql = indexes + postindex_props_sql 1341 1342 replace = " OR REPLACE" if expression.args.get("replace") else "" 1343 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1344 unique = " UNIQUE" if expression.args.get("unique") else "" 1345 1346 clustered = expression.args.get("clustered") 1347 if clustered is None: 1348 clustered_sql = "" 1349 elif clustered: 1350 clustered_sql = " CLUSTERED COLUMNSTORE" 1351 else: 1352 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1353 1354 postcreate_props_sql = "" 1355 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1356 postcreate_props_sql = self.properties( 1357 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1358 sep=" ", 1359 prefix=" ", 1360 wrapped=False, 1361 ) 1362 1363 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1364 1365 postexpression_props_sql = "" 1366 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1367 postexpression_props_sql = self.properties( 1368 exp.Properties( 1369 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1370 ), 1371 sep=" ", 1372 prefix=" ", 1373 wrapped=False, 1374 ) 1375 1376 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1377 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1378 no_schema_binding = ( 1379 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1380 ) 1381 1382 clone = self.sql(expression, "clone") 1383 clone = f" {clone}" if clone else "" 1384 1385 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1386 properties_expression = f"{expression_sql}{properties_sql}" 1387 else: 1388 properties_expression = f"{properties_sql}{expression_sql}" 1389 1390 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1391 return self.prepend_ctes(expression, expression_sql)
1393 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1394 start = self.sql(expression, "start") 1395 start = f"START WITH {start}" if start else "" 1396 increment = self.sql(expression, "increment") 1397 increment = f" INCREMENT BY {increment}" if increment else "" 1398 minvalue = self.sql(expression, "minvalue") 1399 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1400 maxvalue = self.sql(expression, "maxvalue") 1401 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1402 owned = self.sql(expression, "owned") 1403 owned = f" OWNED BY {owned}" if owned else "" 1404 1405 cache = expression.args.get("cache") 1406 if cache is None: 1407 cache_str = "" 1408 elif cache is True: 1409 cache_str = " CACHE" 1410 else: 1411 cache_str = f" CACHE {cache}" 1412 1413 options = self.expressions(expression, key="options", flat=True, sep=" ") 1414 options = f" {options}" if options else "" 1415 1416 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1418 def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str: 1419 timing = expression.args.get("timing", "") 1420 events = " OR ".join(self.sql(event) for event in expression.args.get("events") or []) 1421 timing_events = f"{timing} {events}".strip() if timing or events else "" 1422 1423 parts = [timing_events, "ON", self.sql(expression, "table")] 1424 1425 if referenced_table := expression.args.get("referenced_table"): 1426 parts.extend(["FROM", self.sql(referenced_table)]) 1427 1428 if deferrable := expression.args.get("deferrable"): 1429 parts.append(deferrable) 1430 1431 if initially := expression.args.get("initially"): 1432 parts.append(f"INITIALLY {initially}") 1433 1434 if referencing := expression.args.get("referencing"): 1435 parts.append(self.sql(referencing)) 1436 1437 if for_each := expression.args.get("for_each"): 1438 parts.append(f"FOR EACH {for_each}") 1439 1440 if when := expression.args.get("when"): 1441 parts.append(f"WHEN ({self.sql(when)})") 1442 1443 parts.append(self.sql(expression, "execute")) 1444 1445 return self.sep().join(parts)
1447 def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str: 1448 parts = [] 1449 1450 if old_alias := expression.args.get("old"): 1451 parts.append(f"OLD TABLE AS {self.sql(old_alias)}") 1452 1453 if new_alias := expression.args.get("new"): 1454 parts.append(f"NEW TABLE AS {self.sql(new_alias)}") 1455 1456 return f"REFERENCING {' '.join(parts)}"
1465 def clone_sql(self, expression: exp.Clone) -> str: 1466 this = self.sql(expression, "this") 1467 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1468 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1469 return f"{shallow}{keyword} {this}"
1471 def describe_sql(self, expression: exp.Describe) -> str: 1472 style = expression.args.get("style") 1473 style = f" {style}" if style else "" 1474 partition = self.sql(expression, "partition") 1475 partition = f" {partition}" if partition else "" 1476 format = self.sql(expression, "format") 1477 format = f" {format}" if format else "" 1478 as_json = " AS JSON" if expression.args.get("as_json") else "" 1479 1480 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}"
1492 def with_sql(self, expression: exp.With) -> str: 1493 sql = self.expressions(expression, flat=True) 1494 recursive = ( 1495 "RECURSIVE " 1496 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1497 else "" 1498 ) 1499 search = self.sql(expression, "search") 1500 search = f" {search}" if search else "" 1501 1502 return f"WITH {recursive}{sql}{search}"
1504 def cte_sql(self, expression: exp.CTE) -> str: 1505 alias = expression.args.get("alias") 1506 if alias: 1507 alias.add_comments(expression.pop_comments()) 1508 1509 alias_sql = self.sql(expression, "alias") 1510 1511 materialized = expression.args.get("materialized") 1512 if materialized is False: 1513 materialized = "NOT MATERIALIZED " 1514 elif materialized: 1515 materialized = "MATERIALIZED " 1516 1517 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1518 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1519 1520 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1522 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1523 alias = self.sql(expression, "this") 1524 columns = self.expressions(expression, key="columns", flat=True) 1525 columns = f"({columns})" if columns else "" 1526 1527 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1528 columns = "" 1529 self.unsupported("Named columns are not supported in table alias.") 1530 1531 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1532 alias = self._next_name() 1533 1534 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.query.HexString, binary_function_repr: str | None = None) -> str:
1542 def hexstring_sql( 1543 self, expression: exp.HexString, binary_function_repr: str | None = None 1544 ) -> str: 1545 this = self.sql(expression, "this") 1546 is_integer_type = expression.args.get("is_integer") 1547 1548 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1549 not self.dialect.HEX_START and not binary_function_repr 1550 ): 1551 # Integer representation will be returned if: 1552 # - The read dialect treats the hex value as integer literal but not the write 1553 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1554 return f"{int(this, 16)}" 1555 1556 if not is_integer_type: 1557 # Read dialect treats the hex value as BINARY/BLOB 1558 if binary_function_repr: 1559 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1560 return self.func(binary_function_repr, exp.Literal.string(this)) 1561 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1562 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1563 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1564 1565 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1567 def bytestring_sql(self, expression: exp.ByteString) -> str: 1568 this = self.sql(expression, "this") 1569 if self.dialect.BYTE_START: 1570 escaped_byte_string = self.escape_str( 1571 this, 1572 escape_backslash=False, 1573 delimiter=self.dialect.BYTE_END, 1574 escaped_delimiter=self._escaped_byte_quote_end, 1575 is_byte_string=True, 1576 ) 1577 is_bytes = expression.args.get("is_bytes", False) 1578 delimited_byte_string = ( 1579 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1580 ) 1581 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1582 return self.sql( 1583 exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect) 1584 ) 1585 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1586 return self.sql( 1587 exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect) 1588 ) 1589 1590 return delimited_byte_string 1591 return this
1593 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1594 this = self.sql(expression, "this") 1595 escape = expression.args.get("escape") 1596 1597 if self.dialect.UNICODE_START: 1598 escape_substitute = r"\\\1" 1599 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1600 else: 1601 escape_substitute = r"\\u\1" 1602 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1603 1604 if escape: 1605 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1606 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1607 else: 1608 escape_pattern = ESCAPED_UNICODE_RE 1609 escape_sql = "" 1610 1611 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1612 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1613 1614 return f"{left_quote}{this}{right_quote}{escape_sql}"
1616 def rawstring_sql(self, expression: exp.RawString) -> str: 1617 string = expression.this 1618 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1619 string = string.replace("\\", "\\\\") 1620 1621 string = self.escape_str(string, escape_backslash=False) 1622 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1630 def datatype_sql(self, expression: exp.DataType) -> str: 1631 nested = "" 1632 values = "" 1633 1634 expr_nested = expression.args.get("nested") 1635 interior = ( 1636 self.expressions( 1637 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1638 ) 1639 if expr_nested and self.pretty 1640 else self.expressions(expression, flat=True) 1641 ) 1642 1643 type_value = expression.this 1644 if type_value in self.UNSUPPORTED_TYPES: 1645 self.unsupported( 1646 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1647 ) 1648 1649 type_sql: t.Any = "" 1650 if type_value == exp.DType.USERDEFINED and expression.args.get("kind"): 1651 type_sql = self.sql(expression, "kind") 1652 elif type_value == exp.DType.CHARACTER_SET: 1653 return f"CHAR CHARACTER SET {self.sql(expression, 'kind')}" 1654 else: 1655 type_sql = ( 1656 self.TYPE_MAPPING.get(type_value, type_value.value) 1657 if isinstance(type_value, exp.DType) 1658 else type_value 1659 ) 1660 1661 if interior: 1662 if expr_nested: 1663 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1664 if expression.args.get("values") is not None: 1665 delimiters = ("[", "]") if type_value == exp.DType.ARRAY else ("(", ")") 1666 values = self.expressions(expression, key="values", flat=True) 1667 values = f"{delimiters[0]}{values}{delimiters[1]}" 1668 elif type_value == exp.DType.INTERVAL: 1669 nested = f" {interior}" 1670 else: 1671 nested = f"({interior})" 1672 1673 type_sql = f"{type_sql}{nested}{values}" 1674 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1675 exp.DType.TIMETZ, 1676 exp.DType.TIMESTAMPTZ, 1677 ): 1678 type_sql = f"{type_sql} WITH TIME ZONE" 1679 1680 return type_sql
1682 def directory_sql(self, expression: exp.Directory) -> str: 1683 local = "LOCAL " if expression.args.get("local") else "" 1684 row_format = self.sql(expression, "row_format") 1685 row_format = f" {row_format}" if row_format else "" 1686 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1688 def delete_sql(self, expression: exp.Delete) -> str: 1689 hint = self.sql(expression, "hint") 1690 this = self.sql(expression, "this") 1691 this = f" FROM {this}" if this else "" 1692 using = self.expressions(expression, key="using") 1693 using = f" USING {using}" if using else "" 1694 cluster = self.sql(expression, "cluster") 1695 cluster = f" {cluster}" if cluster else "" 1696 where = self.sql(expression, "where") 1697 returning = self.sql(expression, "returning") 1698 order = self.sql(expression, "order") 1699 limit = self.sql(expression, "limit") 1700 tables = self.expressions(expression, key="tables") 1701 tables = f" {tables}" if tables else "" 1702 if self.RETURNING_END: 1703 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1704 else: 1705 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1706 return self.prepend_ctes(expression, f"DELETE{hint}{tables}{expression_sql}")
1708 def drop_sql(self, expression: exp.Drop) -> str: 1709 this = self.sql(expression, "this") 1710 expressions = self.expressions(expression, flat=True) 1711 expressions = f" ({expressions})" if expressions else "" 1712 kind = expression.args["kind"] 1713 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1714 iceberg = ( 1715 " ICEBERG" 1716 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 1717 else "" 1718 ) 1719 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1720 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1721 on_cluster = self.sql(expression, "cluster") 1722 on_cluster = f" {on_cluster}" if on_cluster else "" 1723 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1724 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1725 cascade = " CASCADE" if expression.args.get("cascade") else "" 1726 restrict = " RESTRICT" if expression.args.get("restrict") else "" 1727 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1728 purge = " PURGE" if expression.args.get("purge") else "" 1729 sync = " SYNC" if expression.args.get("sync") else "" 1730 return f"DROP{temporary}{materialized}{iceberg} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{restrict}{constraints}{purge}{sync}"
1732 def set_operation(self, expression: exp.SetOperation) -> str: 1733 op_type = type(expression) 1734 op_name = op_type.key.upper() 1735 1736 distinct = expression.args.get("distinct") 1737 if ( 1738 distinct is False 1739 and op_type in (exp.Except, exp.Intersect) 1740 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1741 ): 1742 self.unsupported(f"{op_name} ALL is not supported") 1743 1744 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1745 1746 if distinct is None: 1747 distinct = default_distinct 1748 if distinct is None: 1749 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1750 1751 if distinct is default_distinct: 1752 distinct_or_all = "" 1753 else: 1754 distinct_or_all = " DISTINCT" if distinct else " ALL" 1755 1756 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1757 side_kind = f"{side_kind} " if side_kind else "" 1758 1759 by_name = " BY NAME" if expression.args.get("by_name") else "" 1760 on = self.expressions(expression, key="on", flat=True) 1761 on = f" ON ({on})" if on else "" 1762 1763 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1765 def set_operations(self, expression: exp.SetOperation) -> str: 1766 if not self.SET_OP_MODIFIERS: 1767 limit = expression.args.get("limit") 1768 order = expression.args.get("order") 1769 1770 if limit or order: 1771 select = self._move_ctes_to_top_level( 1772 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1773 ) 1774 1775 if limit: 1776 select = select.limit(limit.pop(), copy=False) 1777 if order: 1778 select = select.order_by(order.pop(), copy=False) 1779 return self.sql(select) 1780 1781 sqls: list[str] = [] 1782 stack: list[str | exp.Expr] = [expression] 1783 1784 while stack: 1785 node = stack.pop() 1786 1787 if isinstance(node, exp.SetOperation): 1788 stack.append(node.expression) 1789 stack.append( 1790 self.maybe_comment( 1791 self.set_operation(node), comments=node.comments, separated=True 1792 ) 1793 ) 1794 stack.append(node.this) 1795 else: 1796 sqls.append(self.sql(node)) 1797 1798 this = self.sep().join(sqls) 1799 this = self.query_modifiers(expression, this) 1800 return self.prepend_ctes(expression, this)
1802 def fetch_sql(self, expression: exp.Fetch) -> str: 1803 direction = expression.args.get("direction") 1804 direction = f" {direction}" if direction else "" 1805 count = self.sql(expression, "count") 1806 count = f" {count}" if count else "" 1807 limit_options = self.sql(expression, "limit_options") 1808 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1809 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1811 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1812 percent = " PERCENT" if expression.args.get("percent") else "" 1813 rows = " ROWS" if expression.args.get("rows") else "" 1814 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1815 if not with_ties and rows: 1816 with_ties = " ONLY" 1817 return f"{percent}{rows}{with_ties}"
1819 def filter_sql(self, expression: exp.Filter) -> str: 1820 if self.AGGREGATE_FILTER_SUPPORTED: 1821 this = self.sql(expression, "this") 1822 where = self.sql(expression, "expression").strip() 1823 return f"{this} FILTER({where})" 1824 1825 agg = expression.this 1826 agg_arg = agg.this 1827 cond = expression.expression.this 1828 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1829 return self.sql(agg)
1838 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1839 using = self.sql(expression, "using") 1840 using = f" USING {using}" if using else "" 1841 columns = self.expressions(expression, key="columns", flat=True) 1842 columns = f"({columns})" if columns else "" 1843 partition_by = self.expressions(expression, key="partition_by", flat=True) 1844 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1845 where = self.sql(expression, "where") 1846 include = self.expressions(expression, key="include", flat=True) 1847 if include: 1848 include = f" INCLUDE ({include})" 1849 with_storage = self.expressions(expression, key="with_storage", flat=True) 1850 with_storage = f" WITH ({with_storage})" if with_storage else "" 1851 tablespace = self.sql(expression, "tablespace") 1852 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1853 on = self.sql(expression, "on") 1854 on = f" ON {on}" if on else "" 1855 1856 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1858 def index_sql(self, expression: exp.Index) -> str: 1859 unique = "UNIQUE " if expression.args.get("unique") else "" 1860 primary = "PRIMARY " if expression.args.get("primary") else "" 1861 amp = "AMP " if expression.args.get("amp") else "" 1862 name = self.sql(expression, "this") 1863 name = f"{name} " if name else "" 1864 table = self.sql(expression, "table") 1865 table = f"{self.INDEX_ON} {table}" if table else "" 1866 1867 index = "INDEX " if not table else "" 1868 1869 params = self.sql(expression, "params") 1870 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1872 def identifier_sql(self, expression: exp.Identifier) -> str: 1873 text = expression.name 1874 lower = text.lower() 1875 quoted = expression.quoted 1876 text = lower if self.normalize and not quoted else text 1877 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1878 if ( 1879 quoted 1880 or self.dialect.can_quote(expression, self.identify) 1881 or lower in self.RESERVED_KEYWORDS 1882 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1883 ): 1884 text = f"{self._identifier_start}{text}{self._identifier_end}" 1885 return text
1900 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1901 input_format = self.sql(expression, "input_format") 1902 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1903 output_format = self.sql(expression, "output_format") 1904 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1905 return self.sep().join((input_format, output_format))
1915 def properties_sql(self, expression: exp.Properties) -> str: 1916 root_properties = [] 1917 with_properties = [] 1918 1919 for p in expression.expressions: 1920 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1921 if p_loc == exp.Properties.Location.POST_WITH: 1922 with_properties.append(p) 1923 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1924 root_properties.append(p) 1925 1926 root_props_ast = exp.Properties(expressions=root_properties) 1927 root_props_ast.parent = expression.parent 1928 1929 with_props_ast = exp.Properties(expressions=with_properties) 1930 with_props_ast.parent = expression.parent 1931 1932 root_props = self.root_properties(root_props_ast) 1933 with_props = self.with_properties(with_props_ast) 1934 1935 if root_props and with_props and not self.pretty: 1936 with_props = " " + with_props 1937 1938 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.properties.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1945 def properties( 1946 self, 1947 properties: exp.Properties, 1948 prefix: str = "", 1949 sep: str = ", ", 1950 suffix: str = "", 1951 wrapped: bool = True, 1952 ) -> str: 1953 if properties.expressions: 1954 expressions = self.expressions(properties, sep=sep, indent=False) 1955 if expressions: 1956 expressions = self.wrap(expressions) if wrapped else expressions 1957 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1958 return ""
def
locate_properties( self, properties: sqlglot.expressions.properties.Properties) -> collections.defaultdict:
1963 def locate_properties(self, properties: exp.Properties) -> defaultdict: 1964 properties_locs = defaultdict(list) 1965 for p in properties.expressions: 1966 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1967 if p_loc != exp.Properties.Location.UNSUPPORTED: 1968 properties_locs[p_loc].append(p) 1969 else: 1970 self.unsupported(f"Unsupported property {p.key}") 1971 1972 return properties_locs
def
property_name( self, expression: sqlglot.expressions.properties.Property, string_key: bool = False) -> str:
1979 def property_sql(self, expression: exp.Property) -> str: 1980 property_cls = expression.__class__ 1981 if property_cls == exp.Property: 1982 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1983 1984 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1985 if not property_name: 1986 self.unsupported(f"Unsupported property {expression.key}") 1987 1988 return f"{property_name}={self.sql(expression, 'this')}"
1993 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1994 if self.SUPPORTS_CREATE_TABLE_LIKE: 1995 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1996 options = f" {options}" if options else "" 1997 1998 like = f"LIKE {self.sql(expression, 'this')}{options}" 1999 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 2000 like = f"({like})" 2001 2002 return like 2003 2004 if expression.expressions: 2005 self.unsupported("Transpilation of LIKE property options is unsupported") 2006 2007 select = exp.select("*").from_(expression.this).limit(0) 2008 return f"AS {self.sql(select)}"
2015 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 2016 no = "NO " if expression.args.get("no") else "" 2017 local = expression.args.get("local") 2018 local = f"{local} " if local else "" 2019 dual = "DUAL " if expression.args.get("dual") else "" 2020 before = "BEFORE " if expression.args.get("before") else "" 2021 after = "AFTER " if expression.args.get("after") else "" 2022 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:
2038 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 2039 if expression.args.get("no"): 2040 return "NO MERGEBLOCKRATIO" 2041 if expression.args.get("default"): 2042 return "DEFAULT MERGEBLOCKRATIO" 2043 2044 percent = " PERCENT" if expression.args.get("percent") else "" 2045 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
def
datablocksizeproperty_sql( self, expression: sqlglot.expressions.properties.DataBlocksizeProperty) -> str:
2052 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 2053 default = expression.args.get("default") 2054 minimum = expression.args.get("minimum") 2055 maximum = expression.args.get("maximum") 2056 if default or minimum or maximum: 2057 if default: 2058 prop = "DEFAULT" 2059 elif minimum: 2060 prop = "MINIMUM" 2061 else: 2062 prop = "MAXIMUM" 2063 return f"{prop} DATABLOCKSIZE" 2064 units = expression.args.get("units") 2065 units = f" {units}" if units else "" 2066 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql( self, expression: sqlglot.expressions.properties.BlockCompressionProperty) -> str:
2068 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 2069 autotemp = expression.args.get("autotemp") 2070 always = expression.args.get("always") 2071 default = expression.args.get("default") 2072 manual = expression.args.get("manual") 2073 never = expression.args.get("never") 2074 2075 if autotemp is not None: 2076 prop = f"AUTOTEMP({self.expressions(autotemp)})" 2077 elif always: 2078 prop = "ALWAYS" 2079 elif default: 2080 prop = "DEFAULT" 2081 elif manual: 2082 prop = "MANUAL" 2083 elif never: 2084 prop = "NEVER" 2085 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql( self, expression: sqlglot.expressions.properties.IsolatedLoadingProperty) -> str:
2087 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 2088 no = expression.args.get("no") 2089 no = " NO" if no else "" 2090 concurrent = expression.args.get("concurrent") 2091 concurrent = " CONCURRENT" if concurrent else "" 2092 target = self.sql(expression, "target") 2093 target = f" {target}" if target else "" 2094 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
def
partitionboundspec_sql( self, expression: sqlglot.expressions.properties.PartitionBoundSpec) -> str:
2096 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 2097 if isinstance(expression.this, list): 2098 return f"IN ({self.expressions(expression, key='this', flat=True)})" 2099 if expression.this: 2100 modulus = self.sql(expression, "this") 2101 remainder = self.sql(expression, "expression") 2102 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 2103 2104 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 2105 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 2106 return f"FROM ({from_expressions}) TO ({to_expressions})"
def
partitionedofproperty_sql( self, expression: sqlglot.expressions.properties.PartitionedOfProperty) -> str:
2108 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 2109 this = self.sql(expression, "this") 2110 2111 for_values_or_default = expression.expression 2112 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 2113 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 2114 else: 2115 for_values_or_default = " DEFAULT" 2116 2117 return f"PARTITION OF {this}{for_values_or_default}"
2119 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 2120 kind = expression.args.get("kind") 2121 this = f" {self.sql(expression, 'this')}" if expression.this else "" 2122 for_or_in = expression.args.get("for_or_in") 2123 for_or_in = f" {for_or_in}" if for_or_in else "" 2124 lock_type = expression.args.get("lock_type") 2125 override = " OVERRIDE" if expression.args.get("override") else "" 2126 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
2128 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 2129 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 2130 statistics = expression.args.get("statistics") 2131 statistics_sql = "" 2132 if statistics is not None: 2133 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 2134 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.properties.WithSystemVersioningProperty) -> str:
2136 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 2137 this = self.sql(expression, "this") 2138 this = f"HISTORY_TABLE={this}" if this else "" 2139 data_consistency: str | None = self.sql(expression, "data_consistency") 2140 data_consistency = ( 2141 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 2142 ) 2143 retention_period: str | None = self.sql(expression, "retention_period") 2144 retention_period = ( 2145 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 2146 ) 2147 2148 if this: 2149 on_sql = self.func("ON", this, data_consistency, retention_period) 2150 else: 2151 on_sql = "ON" if expression.args.get("on") else "OFF" 2152 2153 sql = f"SYSTEM_VERSIONING={on_sql}" 2154 2155 return f"WITH({sql})" if expression.args.get("with_") else sql
2157 def insert_sql(self, expression: exp.Insert) -> str: 2158 hint = self.sql(expression, "hint") 2159 overwrite = expression.args.get("overwrite") 2160 2161 if isinstance(expression.this, exp.Directory): 2162 this = " OVERWRITE" if overwrite else " INTO" 2163 else: 2164 this = self.INSERT_OVERWRITE if overwrite else " INTO" 2165 2166 stored = self.sql(expression, "stored") 2167 stored = f" {stored}" if stored else "" 2168 alternative = expression.args.get("alternative") 2169 alternative = f" OR {alternative}" if alternative else "" 2170 ignore = " IGNORE" if expression.args.get("ignore") else "" 2171 is_function = expression.args.get("is_function") 2172 if is_function: 2173 this = f"{this} FUNCTION" 2174 this = f"{this} {self.sql(expression, 'this')}" 2175 2176 exists = " IF EXISTS" if expression.args.get("exists") else "" 2177 where = self.sql(expression, "where") 2178 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2179 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2180 on_conflict = self.sql(expression, "conflict") 2181 on_conflict = f" {on_conflict}" if on_conflict else "" 2182 by_name = " BY NAME" if expression.args.get("by_name") else "" 2183 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2184 returning = self.sql(expression, "returning") 2185 2186 if self.RETURNING_END: 2187 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2188 else: 2189 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2190 2191 partition_by = self.sql(expression, "partition") 2192 partition_by = f" {partition_by}" if partition_by else "" 2193 settings = self.sql(expression, "settings") 2194 settings = f" {settings}" if settings else "" 2195 2196 source = self.sql(expression, "source") 2197 source = f"TABLE {source}" if source else "" 2198 2199 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2200 return self.prepend_ctes(expression, sql)
2218 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2219 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2220 2221 constraint = self.sql(expression, "constraint") 2222 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2223 2224 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2225 if conflict_keys: 2226 conflict_keys = f"({conflict_keys})" 2227 2228 index_predicate = self.sql(expression, "index_predicate") 2229 conflict_keys = f"{conflict_keys}{index_predicate} " 2230 2231 action = self.sql(expression, "action") 2232 2233 expressions = self.expressions(expression, flat=True) 2234 if expressions: 2235 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2236 expressions = f" {set_keyword}{expressions}" 2237 2238 where = self.sql(expression, "where") 2239 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql( self, expression: sqlglot.expressions.properties.RowFormatDelimitedProperty) -> str:
2244 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2245 fields = self.sql(expression, "fields") 2246 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2247 escaped = self.sql(expression, "escaped") 2248 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2249 items = self.sql(expression, "collection_items") 2250 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2251 keys = self.sql(expression, "map_keys") 2252 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2253 lines = self.sql(expression, "lines") 2254 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2255 null = self.sql(expression, "null") 2256 null = f" NULL DEFINED AS {null}" if null else "" 2257 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2285 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2286 table = self.table_parts(expression) 2287 only = "ONLY " if expression.args.get("only") else "" 2288 partition = self.sql(expression, "partition") 2289 partition = f" {partition}" if partition else "" 2290 version = self.sql(expression, "version") 2291 version = f" {version}" if version else "" 2292 alias = self.sql(expression, "alias") 2293 alias = f"{sep}{alias}" if alias else "" 2294 2295 sample = self.sql(expression, "sample") 2296 post_alias = "" 2297 pre_alias = "" 2298 2299 if self.dialect.ALIAS_POST_TABLESAMPLE: 2300 pre_alias = sample 2301 else: 2302 post_alias = sample 2303 2304 if self.dialect.ALIAS_POST_VERSION: 2305 pre_alias = f"{pre_alias}{version}" 2306 else: 2307 post_alias = f"{post_alias}{version}" 2308 2309 hints = self.expressions(expression, key="hints", sep=" ") 2310 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2311 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2312 joins = self.indent( 2313 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2314 ) 2315 laterals = self.expressions(expression, key="laterals", sep="") 2316 2317 file_format = self.sql(expression, "format") 2318 if file_format: 2319 pattern = self.sql(expression, "pattern") 2320 pattern = f", PATTERN => {pattern}" if pattern else "" 2321 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2322 2323 ordinality = expression.args.get("ordinality") or "" 2324 if ordinality: 2325 ordinality = f" WITH ORDINALITY{alias}" 2326 alias = "" 2327 2328 when = self.sql(expression, "when") 2329 if when: 2330 table = f"{table} {when}" 2331 2332 changes = self.sql(expression, "changes") 2333 changes = f" {changes}" if changes else "" 2334 2335 rows_from = self.expressions(expression, key="rows_from") 2336 if rows_from: 2337 table = f"ROWS FROM {self.wrap(rows_from)}" 2338 2339 indexed = expression.args.get("indexed") 2340 if indexed is not None: 2341 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2342 else: 2343 indexed = "" 2344 2345 return f"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}"
2347 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2348 table = self.func("TABLE", expression.this) 2349 alias = self.sql(expression, "alias") 2350 alias = f" AS {alias}" if alias else "" 2351 sample = self.sql(expression, "sample") 2352 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2353 joins = self.indent( 2354 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2355 ) 2356 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.query.TableSample, tablesample_keyword: str | None = None) -> str:
2358 def tablesample_sql( 2359 self, 2360 expression: exp.TableSample, 2361 tablesample_keyword: str | None = None, 2362 ) -> str: 2363 method = self.sql(expression, "method") 2364 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2365 numerator = self.sql(expression, "bucket_numerator") 2366 denominator = self.sql(expression, "bucket_denominator") 2367 field = self.sql(expression, "bucket_field") 2368 field = f" ON {field}" if field else "" 2369 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2370 seed = self.sql(expression, "seed") 2371 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2372 2373 size = self.sql(expression, "size") 2374 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2375 size = f"{size} ROWS" 2376 2377 percent = self.sql(expression, "percent") 2378 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2379 percent = f"{percent} PERCENT" 2380 2381 expr = f"{bucket}{percent}{size}" 2382 if self.TABLESAMPLE_REQUIRES_PARENS: 2383 expr = f"({expr})" 2384 2385 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2387 def pivot_sql(self, expression: exp.Pivot) -> str: 2388 expressions = self.expressions(expression, flat=True) 2389 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2390 2391 group = self.sql(expression, "group") 2392 2393 if expression.this: 2394 this = self.sql(expression, "this") 2395 if not expressions: 2396 sql = f"UNPIVOT {this}" 2397 else: 2398 on = f"{self.seg('ON')} {expressions}" 2399 into = self.sql(expression, "into") 2400 into = f"{self.seg('INTO')} {into}" if into else "" 2401 using = self.expressions(expression, key="using", flat=True) 2402 using = f"{self.seg('USING')} {using}" if using else "" 2403 sql = f"{direction} {this}{on}{into}{using}{group}" 2404 return self.prepend_ctes(expression, sql) 2405 2406 alias = self.sql(expression, "alias") 2407 alias = f" AS {alias}" if alias else "" 2408 2409 fields = self.expressions( 2410 expression, 2411 "fields", 2412 sep=" ", 2413 dynamic=True, 2414 new_line=True, 2415 skip_first=True, 2416 skip_last=True, 2417 ) 2418 2419 include_nulls = expression.args.get("include_nulls") 2420 if include_nulls is not None: 2421 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2422 else: 2423 nulls = "" 2424 2425 default_on_null = self.sql(expression, "default_on_null") 2426 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2427 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2428 return self.prepend_ctes(expression, sql)
2471 def update_sql(self, expression: exp.Update) -> str: 2472 hint = self.sql(expression, "hint") 2473 this = self.sql(expression, "this") 2474 join_sql, from_sql = self._update_from_joins_sql(expression) 2475 set_sql = self.expressions(expression, flat=True) 2476 where_sql = self.sql(expression, "where") 2477 returning = self.sql(expression, "returning") 2478 order = self.sql(expression, "order") 2479 limit = self.sql(expression, "limit") 2480 if self.RETURNING_END: 2481 expression_sql = f"{from_sql}{where_sql}{returning}" 2482 else: 2483 expression_sql = f"{returning}{from_sql}{where_sql}" 2484 options = self.expressions(expression, key="options") 2485 options = f" OPTION({options})" if options else "" 2486 sql = f"UPDATE{hint} {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2487 return self.prepend_ctes(expression, sql)
def
values_sql( self, expression: sqlglot.expressions.query.Values, values_as_table: bool = True) -> str:
2489 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2490 values_as_table = values_as_table and self.VALUES_AS_TABLE 2491 2492 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2493 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2494 args = self.expressions(expression) 2495 alias = self.sql(expression, "alias") 2496 values = f"VALUES{self.seg('')}{args}" 2497 values = ( 2498 f"({values})" 2499 if self.WRAP_DERIVED_VALUES 2500 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2501 else values 2502 ) 2503 values = self.query_modifiers(expression, values) 2504 return f"{values} AS {alias}" if alias else values 2505 2506 # Converts `VALUES...` expression into a series of select unions. 2507 alias_node = expression.args.get("alias") 2508 column_names = alias_node and alias_node.columns 2509 2510 selects: list[exp.Query] = [] 2511 2512 for i, tup in enumerate(expression.expressions): 2513 row = tup.expressions 2514 2515 if i == 0 and column_names: 2516 row = [ 2517 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2518 ] 2519 2520 selects.append(exp.Select(expressions=row)) 2521 2522 if self.pretty: 2523 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2524 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2525 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2526 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2527 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2528 2529 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2530 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2531 return f"({unions}){alias}"
@unsupported_args('expressions')
def
into_sql(self, expression: sqlglot.expressions.query.Into) -> str:
2536 @unsupported_args("expressions") 2537 def into_sql(self, expression: exp.Into) -> str: 2538 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2539 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2540 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2553 def rollupindex_sql(self, expression: exp.RollupIndex) -> str: 2554 this = self.sql(expression, "this") 2555 2556 columns = self.expressions(expression, flat=True) 2557 2558 from_sql = self.sql(expression, "from_index") 2559 from_sql = f" FROM {from_sql}" if from_sql else "" 2560 2561 properties = expression.args.get("properties") 2562 properties_sql = ( 2563 f" {self.properties(properties, prefix='PROPERTIES')}" if properties else "" 2564 ) 2565 2566 return f"{this}({columns}){from_sql}{properties_sql}"
2575 def group_sql(self, expression: exp.Group) -> str: 2576 group_by_all = expression.args.get("all") 2577 if group_by_all is True: 2578 modifier = " ALL" 2579 elif group_by_all is False: 2580 modifier = " DISTINCT" 2581 else: 2582 modifier = "" 2583 2584 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2585 2586 grouping_sets = self.expressions(expression, key="grouping_sets") 2587 cube = self.expressions(expression, key="cube") 2588 rollup = self.expressions(expression, key="rollup") 2589 2590 groupings = csv( 2591 self.seg(grouping_sets) if grouping_sets else "", 2592 self.seg(cube) if cube else "", 2593 self.seg(rollup) if rollup else "", 2594 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2595 sep=self.GROUPINGS_SEP, 2596 ) 2597 2598 if ( 2599 expression.expressions 2600 and groupings 2601 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2602 ): 2603 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2604 2605 return f"{group_by}{groupings}"
2611 def connect_sql(self, expression: exp.Connect) -> str: 2612 start = self.sql(expression, "start") 2613 start = self.seg(f"START WITH {start}") if start else "" 2614 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2615 connect = self.sql(expression, "connect") 2616 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2617 return start + connect
2622 def join_sql(self, expression: exp.Join) -> str: 2623 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2624 side = None 2625 else: 2626 side = expression.side 2627 2628 op_sql = " ".join( 2629 op 2630 for op in ( 2631 expression.method, 2632 "GLOBAL" if expression.args.get("global_") else None, 2633 side, 2634 expression.kind, 2635 expression.hint if self.JOIN_HINTS else None, 2636 "DIRECTED" if expression.args.get("directed") and self.DIRECTED_JOINS else None, 2637 ) 2638 if op 2639 ) 2640 match_cond = self.sql(expression, "match_condition") 2641 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2642 on_sql = self.sql(expression, "on") 2643 using = expression.args.get("using") 2644 2645 if not on_sql and using: 2646 on_sql = csv(*(self.sql(column) for column in using)) 2647 2648 this = expression.this 2649 this_sql = self.sql(this) 2650 2651 exprs = self.expressions(expression) 2652 if exprs: 2653 this_sql = f"{this_sql},{self.seg(exprs)}" 2654 2655 if on_sql: 2656 on_sql = self.indent(on_sql, skip_first=True) 2657 space = self.seg(" " * self.pad) if self.pretty else " " 2658 if using: 2659 on_sql = f"{space}USING ({on_sql})" 2660 else: 2661 on_sql = f"{space}ON {on_sql}" 2662 elif not op_sql: 2663 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2664 return f" {this_sql}" 2665 2666 return f", {this_sql}" 2667 2668 if op_sql != "STRAIGHT_JOIN": 2669 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2670 2671 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2672 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:
2679 def lateral_op(self, expression: exp.Lateral) -> str: 2680 cross_apply = expression.args.get("cross_apply") 2681 2682 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2683 if cross_apply is True: 2684 op = "INNER JOIN " 2685 elif cross_apply is False: 2686 op = "LEFT JOIN " 2687 else: 2688 op = "" 2689 2690 return f"{op}LATERAL"
2692 def lateral_sql(self, expression: exp.Lateral) -> str: 2693 this = self.sql(expression, "this") 2694 2695 if expression.args.get("view"): 2696 alias = expression.args["alias"] 2697 columns = self.expressions(alias, key="columns", flat=True) 2698 table = f" {alias.name}" if alias.name else "" 2699 columns = f" AS {columns}" if columns else "" 2700 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2701 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2702 2703 alias = self.sql(expression, "alias") 2704 alias = f" AS {alias}" if alias else "" 2705 2706 ordinality = expression.args.get("ordinality") or "" 2707 if ordinality: 2708 ordinality = f" WITH ORDINALITY{alias}" 2709 alias = "" 2710 2711 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2713 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2714 this = self.sql(expression, "this") 2715 2716 args = [ 2717 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2718 for e in (expression.args.get(k) for k in ("offset", "expression")) 2719 if e 2720 ] 2721 2722 args_sql = ", ".join(self.sql(e) for e in args) 2723 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2724 expressions = self.expressions(expression, flat=True) 2725 limit_options = self.sql(expression, "limit_options") 2726 expressions = f" BY {expressions}" if expressions else "" 2727 2728 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2730 def offset_sql(self, expression: exp.Offset) -> str: 2731 this = self.sql(expression, "this") 2732 value = expression.expression 2733 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2734 expressions = self.expressions(expression, flat=True) 2735 expressions = f" BY {expressions}" if expressions else "" 2736 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2738 def setitem_sql(self, expression: exp.SetItem) -> str: 2739 kind = self.sql(expression, "kind") 2740 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2741 kind = "" 2742 else: 2743 kind = f"{kind} " if kind else "" 2744 this = self.sql(expression, "this") 2745 expressions = self.expressions(expression) 2746 collate = self.sql(expression, "collate") 2747 collate = f" COLLATE {collate}" if collate else "" 2748 global_ = "GLOBAL " if expression.args.get("global_") else "" 2749 return f"{global_}{kind}{this}{expressions}{collate}"
2756 def queryband_sql(self, expression: exp.QueryBand) -> str: 2757 this = self.sql(expression, "this") 2758 update = " UPDATE" if expression.args.get("update") else "" 2759 scope = self.sql(expression, "scope") 2760 scope = f" FOR {scope}" if scope else "" 2761 2762 return f"QUERY_BAND = {this}{update}{scope}"
2767 def lock_sql(self, expression: exp.Lock) -> str: 2768 if not self.LOCKING_READS_SUPPORTED: 2769 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2770 return "" 2771 2772 update = expression.args["update"] 2773 key = expression.args.get("key") 2774 if update: 2775 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2776 else: 2777 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2778 expressions = self.expressions(expression, flat=True) 2779 expressions = f" OF {expressions}" if expressions else "" 2780 wait = expression.args.get("wait") 2781 2782 if wait is not None: 2783 if isinstance(wait, exp.Literal): 2784 wait = f" WAIT {self.sql(wait)}" 2785 else: 2786 wait = " NOWAIT" if wait else " SKIP LOCKED" 2787 2788 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:
2796 def escape_str( 2797 self, 2798 text: str, 2799 escape_backslash: bool = True, 2800 delimiter: str | None = None, 2801 escaped_delimiter: str | None = None, 2802 is_byte_string: bool = False, 2803 ) -> str: 2804 if is_byte_string: 2805 supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES 2806 else: 2807 supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES 2808 2809 if supports_escape_sequences: 2810 text = "".join( 2811 self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != "\\" else ch 2812 for ch in text 2813 ) 2814 2815 delimiter = delimiter or self.dialect.QUOTE_END 2816 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2817 2818 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2820 def loaddata_sql(self, expression: exp.LoadData) -> str: 2821 is_overwrite = expression.args.get("overwrite") 2822 overwrite = " OVERWRITE" if is_overwrite else "" 2823 this = self.sql(expression, "this") 2824 2825 files = expression.args.get("files") 2826 if files: 2827 files_sql = self.expressions(files, flat=True) 2828 files_sql = f"FILES{self.wrap(files_sql)}" 2829 this = f" {this}" if is_overwrite else f" INTO TABLE {this}" 2830 return f"LOAD DATA{overwrite}{this} FROM {files_sql}" 2831 2832 local = " LOCAL" if expression.args.get("local") else "" 2833 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2834 this = f" INTO TABLE {this}" 2835 partition = self.sql(expression, "partition") 2836 partition = f" {partition}" if partition else "" 2837 input_format = self.sql(expression, "input_format") 2838 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2839 serde = self.sql(expression, "serde") 2840 serde = f" SERDE {serde}" if serde else "" 2841 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2855 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2856 this = self.sql(expression, "this") 2857 this = f"{this} " if this else this 2858 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2859 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=bool(this) or flat)
2861 def withfill_sql(self, expression: exp.WithFill) -> str: 2862 from_sql = self.sql(expression, "from_") 2863 from_sql = f" FROM {from_sql}" if from_sql else "" 2864 to_sql = self.sql(expression, "to") 2865 to_sql = f" TO {to_sql}" if to_sql else "" 2866 step_sql = self.sql(expression, "step") 2867 step_sql = f" STEP {step_sql}" if step_sql else "" 2868 interpolated_values = [ 2869 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2870 if isinstance(e, exp.Alias) 2871 else self.sql(e, "this") 2872 for e in expression.args.get("interpolate") or [] 2873 ] 2874 interpolate = ( 2875 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2876 ) 2877 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2888 def ordered_sql(self, expression: exp.Ordered) -> str: 2889 desc = expression.args.get("desc") 2890 asc = not desc 2891 2892 nulls_first = expression.args.get("nulls_first") 2893 nulls_last = not nulls_first 2894 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2895 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2896 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2897 2898 this = self.sql(expression, "this") 2899 2900 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2901 nulls_sort_change = "" 2902 if nulls_first and ( 2903 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2904 ): 2905 nulls_sort_change = " NULLS FIRST" 2906 elif ( 2907 nulls_last 2908 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2909 and not nulls_are_last 2910 ): 2911 nulls_sort_change = " NULLS LAST" 2912 2913 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2914 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2915 window = expression.find_ancestor(exp.Window, exp.Select) 2916 2917 if isinstance(window, exp.Window): 2918 window_this = window.this 2919 if isinstance(window_this, (exp.IgnoreNulls, exp.RespectNulls)): 2920 window_this = window_this.this 2921 spec = window.args.get("spec") 2922 else: 2923 window_this = None 2924 spec = None 2925 2926 # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST 2927 # without a spec or with a ROWS spec, but not with RANGE 2928 if not ( 2929 isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING) 2930 and (not spec or spec.text("kind").upper() == "ROWS") 2931 ): 2932 if window_this and spec: 2933 self.unsupported( 2934 f"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}" 2935 ) 2936 nulls_sort_change = "" 2937 elif self.NULL_ORDERING_SUPPORTED is False and ( 2938 (asc and nulls_sort_change == " NULLS LAST") 2939 or (desc and nulls_sort_change == " NULLS FIRST") 2940 ): 2941 # BigQuery does not allow these ordering/nulls combinations when used under 2942 # an aggregation func or under a window containing one 2943 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2944 2945 if isinstance(ancestor, exp.Window): 2946 ancestor = ancestor.this 2947 if isinstance(ancestor, exp.AggFunc): 2948 self.unsupported( 2949 f"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order" 2950 ) 2951 nulls_sort_change = "" 2952 elif self.NULL_ORDERING_SUPPORTED is None: 2953 if expression.this.is_int: 2954 self.unsupported( 2955 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2956 ) 2957 elif not isinstance(expression.this, exp.Rand): 2958 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2959 this = ( 2960 f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2961 ) 2962 nulls_sort_change = "" 2963 2964 with_fill = self.sql(expression, "with_fill") 2965 with_fill = f" {with_fill}" if with_fill else "" 2966 2967 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
def
matchrecognizemeasure_sql(self, expression: sqlglot.expressions.query.MatchRecognizeMeasure) -> str:
2977 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2978 partition = self.partition_by_sql(expression) 2979 order = self.sql(expression, "order") 2980 measures = self.expressions(expression, key="measures") 2981 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2982 rows = self.sql(expression, "rows") 2983 rows = self.seg(rows) if rows else "" 2984 after = self.sql(expression, "after") 2985 after = self.seg(after) if after else "" 2986 pattern = self.sql(expression, "pattern") 2987 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2988 definition_sqls = [ 2989 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2990 for definition in expression.args.get("define", []) 2991 ] 2992 definitions = self.expressions(sqls=definition_sqls) 2993 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2994 body = "".join( 2995 ( 2996 partition, 2997 order, 2998 measures, 2999 rows, 3000 after, 3001 pattern, 3002 define, 3003 ) 3004 ) 3005 alias = self.sql(expression, "alias") 3006 alias = f" {alias}" if alias else "" 3007 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
3009 def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str: 3010 limit = expression.args.get("limit") 3011 3012 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 3013 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 3014 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 3015 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 3016 3017 return csv( 3018 *sqls, 3019 *[self.sql(join) for join in expression.args.get("joins") or []], 3020 self.sql(expression, "match"), 3021 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 3022 self.sql(expression, "prewhere"), 3023 self.sql(expression, "where"), 3024 self.sql(expression, "connect"), 3025 self.sql(expression, "group"), 3026 self.sql(expression, "having"), 3027 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 3028 self.sql(expression, "order"), 3029 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 3030 *self.after_limit_modifiers(expression), 3031 self.options_modifier(expression), 3032 self.for_modifiers(expression), 3033 sep="", 3034 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.core.Expr, fetch: bool, limit: sqlglot.expressions.query.Fetch | sqlglot.expressions.query.Limit | None) -> list[str]:
3061 def select_sql(self, expression: exp.Select) -> str: 3062 into = expression.args.get("into") 3063 if not self.SUPPORTS_SELECT_INTO and into: 3064 into.pop() 3065 3066 hint = self.sql(expression, "hint") 3067 distinct = self.sql(expression, "distinct") 3068 distinct = f" {distinct}" if distinct else "" 3069 kind = self.sql(expression, "kind") 3070 3071 limit = expression.args.get("limit") 3072 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 3073 top = self.limit_sql(limit, top=True) 3074 limit.pop() 3075 else: 3076 top = "" 3077 3078 expressions = self.expressions(expression) 3079 3080 if kind: 3081 if kind in self.SELECT_KINDS: 3082 kind = f" AS {kind}" 3083 else: 3084 if kind == "STRUCT": 3085 expressions = self.expressions( 3086 sqls=[ 3087 self.sql( 3088 exp.Struct( 3089 expressions=[ 3090 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 3091 if isinstance(e, exp.Alias) 3092 else e 3093 for e in expression.expressions 3094 ] 3095 ) 3096 ) 3097 ] 3098 ) 3099 kind = "" 3100 3101 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 3102 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 3103 3104 exclude = expression.args.get("exclude") 3105 3106 if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3107 exclude_sql = self.expressions(sqls=exclude, flat=True) 3108 expressions = f"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})" 3109 3110 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 3111 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 3112 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 3113 expressions = f"{self.sep()}{expressions}" if expressions else expressions 3114 sql = self.query_modifiers( 3115 expression, 3116 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 3117 self.sql(expression, "into", comment=False), 3118 self.sql(expression, "from_", comment=False), 3119 ) 3120 3121 # If both the CTE and SELECT clauses have comments, generate the latter earlier 3122 if expression.args.get("with_"): 3123 sql = self.maybe_comment(sql, expression) 3124 expression.pop_comments() 3125 3126 sql = self.prepend_ctes(expression, sql) 3127 3128 if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude: 3129 expression.set("exclude", None) 3130 subquery = expression.subquery(copy=False) 3131 star = exp.Star(except_=exclude) 3132 sql = self.sql(exp.select(star).from_(subquery, copy=False)) 3133 3134 if not self.SUPPORTS_SELECT_INTO and into: 3135 if into.args.get("temporary"): 3136 table_kind = " TEMPORARY" 3137 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 3138 table_kind = " UNLOGGED" 3139 else: 3140 table_kind = "" 3141 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 3142 3143 return sql
3155 def star_sql(self, expression: exp.Star) -> str: 3156 except_ = self.expressions(expression, key="except_", flat=True) 3157 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 3158 replace = self.expressions(expression, key="replace", flat=True) 3159 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 3160 rename = self.expressions(expression, key="rename", flat=True) 3161 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 3162 return f"*{except_}{replace}{rename}"
3178 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 3179 alias = self.sql(expression, "alias") 3180 alias = f"{sep}{alias}" if alias else "" 3181 sample = self.sql(expression, "sample") 3182 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 3183 alias = f"{sample}{alias}" 3184 3185 # Set to None so it's not generated again by self.query_modifiers() 3186 expression.set("sample", None) 3187 3188 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 3189 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 3190 return self.prepend_ctes(expression, sql)
3196 def unnest_sql(self, expression: exp.Unnest) -> str: 3197 args = self.expressions(expression, flat=True) 3198 3199 alias = expression.args.get("alias") 3200 offset = expression.args.get("offset") 3201 3202 if self.UNNEST_WITH_ORDINALITY: 3203 if alias and isinstance(offset, exp.Expr): 3204 alias.append("columns", offset) 3205 3206 if alias and self.dialect.UNNEST_COLUMN_ONLY: 3207 columns = alias.columns 3208 alias = self.sql(columns[0]) if columns else "" 3209 else: 3210 alias = self.sql(alias) 3211 3212 alias = f" AS {alias}" if alias else alias 3213 if self.UNNEST_WITH_ORDINALITY: 3214 suffix = f" WITH ORDINALITY{alias}" if offset else alias 3215 else: 3216 if isinstance(offset, exp.Expr): 3217 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 3218 elif offset: 3219 suffix = f"{alias} WITH OFFSET" 3220 else: 3221 suffix = alias 3222 3223 return f"UNNEST({args}){suffix}"
3232 def window_sql(self, expression: exp.Window) -> str: 3233 this = self.sql(expression, "this") 3234 partition = self.partition_by_sql(expression) 3235 order = expression.args.get("order") 3236 order = self.order_sql(order, flat=True) if order else "" 3237 spec = self.sql(expression, "spec") 3238 alias = self.sql(expression, "alias") 3239 over = self.sql(expression, "over") or "OVER" 3240 3241 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 3242 3243 first = expression.args.get("first") 3244 if first is None: 3245 first = "" 3246 else: 3247 first = "FIRST" if first else "LAST" 3248 3249 if not partition and not order and not spec and alias: 3250 return f"{this} {alias}" 3251 3252 args = self.format_args( 3253 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3254 ) 3255 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.query.Window | sqlglot.expressions.query.MatchRecognize) -> str:
3261 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3262 kind = self.sql(expression, "kind") 3263 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3264 end = ( 3265 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3266 or "CURRENT ROW" 3267 ) 3268 3269 window_spec = f"{kind} BETWEEN {start} AND {end}" 3270 3271 exclude = self.sql(expression, "exclude") 3272 if exclude: 3273 if self.SUPPORTS_WINDOW_EXCLUDE: 3274 window_spec += f" EXCLUDE {exclude}" 3275 else: 3276 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3277 3278 return window_spec
3285 def between_sql(self, expression: exp.Between) -> str: 3286 this = self.sql(expression, "this") 3287 low = self.sql(expression, "low") 3288 high = self.sql(expression, "high") 3289 symmetric = expression.args.get("symmetric") 3290 3291 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3292 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3293 3294 flag = ( 3295 " SYMMETRIC" 3296 if symmetric 3297 else " ASYMMETRIC" 3298 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3299 else "" # silently drop ASYMMETRIC – semantics identical 3300 ) 3301 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]:
3303 def bracket_offset_expressions( 3304 self, expression: exp.Bracket, index_offset: int | None = None 3305 ) -> list[exp.Expr]: 3306 if expression.args.get("json_access"): 3307 return expression.expressions 3308 3309 return apply_index_offset( 3310 expression.this, 3311 expression.expressions, 3312 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3313 dialect=self.dialect, 3314 )
3327 def any_sql(self, expression: exp.Any) -> str: 3328 this = self.sql(expression, "this") 3329 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3330 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3331 this = self.wrap(this) 3332 return f"ANY{this}" 3333 return f"ANY {this}"
3338 def case_sql(self, expression: exp.Case) -> str: 3339 this = self.sql(expression, "this") 3340 statements = [f"CASE {this}" if this else "CASE"] 3341 3342 for e in expression.args["ifs"]: 3343 statements.append(f"WHEN {self.sql(e, 'this')}") 3344 statements.append(f"THEN {self.sql(e, 'true')}") 3345 3346 default = self.sql(expression, "default") 3347 3348 if default: 3349 statements.append(f"ELSE {default}") 3350 3351 statements.append("END") 3352 3353 if self.pretty and self.too_wide(statements): 3354 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3355 3356 return " ".join(statements)
3368 def extract_sql(self, expression: exp.Extract) -> str: 3369 import sqlglot.dialects.dialect 3370 3371 this = ( 3372 sqlglot.dialects.dialect.map_date_part(expression.this, self.dialect) 3373 if self.NORMALIZE_EXTRACT_DATE_PARTS 3374 else expression.this 3375 ) 3376 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3377 expression_sql = self.sql(expression, "expression") 3378 3379 return f"EXTRACT({this_sql} FROM {expression_sql})"
3381 def trim_sql(self, expression: exp.Trim) -> str: 3382 trim_type = self.sql(expression, "position") 3383 3384 if trim_type == "LEADING": 3385 func_name = "LTRIM" 3386 elif trim_type == "TRAILING": 3387 func_name = "RTRIM" 3388 else: 3389 func_name = "TRIM" 3390 3391 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.core.Func) -> list[sqlglot.expressions.core.Expr]:
3393 def convert_concat_args(self, expression: exp.Func) -> list[exp.Expr]: 3394 args = expression.expressions 3395 if isinstance(expression, exp.ConcatWs): 3396 args = args[1:] # Skip the delimiter 3397 3398 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3399 args = [exp.cast(e, exp.DType.TEXT) for e in args] 3400 3401 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3402 3403 def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr: 3404 if not e.type: 3405 import sqlglot.optimizer.annotate_types 3406 3407 e = sqlglot.optimizer.annotate_types.annotate_types(e, dialect=self.dialect) 3408 3409 if e.is_string or e.is_type(exp.DType.ARRAY): 3410 return e 3411 3412 return exp.func("coalesce", e, exp.Literal.string("")) 3413 3414 args = [_wrap_with_coalesce(e) for e in args] 3415 3416 return args
3418 def concat_sql(self, expression: exp.Concat) -> str: 3419 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3420 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3421 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3422 # instead of coalescing them to empty string. 3423 import sqlglot.dialects.dialect 3424 3425 return sqlglot.dialects.dialect.concat_to_dpipe_sql(self, expression) 3426 3427 expressions = self.convert_concat_args(expression) 3428 3429 # Some dialects don't allow a single-argument CONCAT call 3430 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3431 return self.sql(expressions[0]) 3432 3433 return self.func("CONCAT", *expressions)
3435 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3436 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3437 # Dialect's CONCAT_WS function coalesces NULLs to empty strings, but the expression does not. 3438 # Wrap the entire call in a CASE expression that returns NULL if any input IS NULL. 3439 all_args = expression.expressions 3440 expression.set("coalesce", True) 3441 return self.sql( 3442 exp.case() 3443 .when(exp.or_(*(arg.is_(exp.null()) for arg in all_args)), exp.null()) 3444 .else_(expression) 3445 ) 3446 3447 return self.func( 3448 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3449 )
3455 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3456 expressions = self.expressions(expression, flat=True) 3457 expressions = f" ({expressions})" if expressions else "" 3458 reference = self.sql(expression, "reference") 3459 reference = f" {reference}" if reference else "" 3460 delete = self.sql(expression, "delete") 3461 delete = f" ON DELETE {delete}" if delete else "" 3462 update = self.sql(expression, "update") 3463 update = f" ON UPDATE {update}" if update else "" 3464 options = self.expressions(expression, key="options", flat=True, sep=" ") 3465 options = f" {options}" if options else "" 3466 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3468 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3469 this = self.sql(expression, "this") 3470 this = f" {this}" if this else "" 3471 expressions = self.expressions(expression, flat=True) 3472 include = self.sql(expression, "include") 3473 options = self.expressions(expression, key="options", flat=True, sep=" ") 3474 options = f" {options}" if options else "" 3475 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3480 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3481 if self.MATCH_AGAINST_TABLE_PREFIX: 3482 expressions = [] 3483 for expr in expression.expressions: 3484 if isinstance(expr, exp.Table): 3485 expressions.append(f"TABLE {self.sql(expr)}") 3486 else: 3487 expressions.append(expr) 3488 else: 3489 expressions = expression.expressions 3490 3491 modifier = expression.args.get("modifier") 3492 modifier = f" {modifier}" if modifier else "" 3493 return ( 3494 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3495 )
3500 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3501 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3502 3503 if expression.args.get("escape"): 3504 path = self.escape_str(path) 3505 3506 if self.QUOTE_JSON_PATH: 3507 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3508 3509 return path
3511 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3512 if isinstance(expression, exp.JSONPathPart): 3513 transform = self.TRANSFORMS.get(expression.__class__) 3514 if not callable(transform): 3515 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3516 return "" 3517 3518 return transform(self, expression) 3519 3520 if isinstance(expression, int): 3521 return str(expression) 3522 3523 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3524 escaped = expression.replace("'", "\\'") 3525 escaped = f"\\'{expression}\\'" 3526 else: 3527 escaped = expression.replace('"', '\\"') 3528 escaped = f'"{escaped}"' 3529 3530 return escaped
3535 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3536 # Output the Teradata column FORMAT override. 3537 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3538 this = self.sql(expression, "this") 3539 fmt = self.sql(expression, "format") 3540 return f"{this} (FORMAT {fmt})"
3568 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3569 null_handling = expression.args.get("null_handling") 3570 null_handling = f" {null_handling}" if null_handling else "" 3571 return_type = self.sql(expression, "return_type") 3572 return_type = f" RETURNING {return_type}" if return_type else "" 3573 strict = " STRICT" if expression.args.get("strict") else "" 3574 return self.func( 3575 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3576 )
3578 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3579 this = self.sql(expression, "this") 3580 order = self.sql(expression, "order") 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_ARRAYAGG", 3588 this, 3589 suffix=f"{order}{null_handling}{return_type}{strict})", 3590 )
3592 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3593 path = self.sql(expression, "path") 3594 path = f" PATH {path}" if path else "" 3595 nested_schema = self.sql(expression, "nested_schema") 3596 3597 if nested_schema: 3598 return f"NESTED{path} {nested_schema}" 3599 3600 this = self.sql(expression, "this") 3601 kind = self.sql(expression, "kind") 3602 kind = f" {kind}" if kind else "" 3603 3604 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3605 return f"{this}{kind}{path}{ordinality}"
3610 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3611 this = self.sql(expression, "this") 3612 path = self.sql(expression, "path") 3613 path = f", {path}" if path else "" 3614 error_handling = expression.args.get("error_handling") 3615 error_handling = f" {error_handling}" if error_handling else "" 3616 empty_handling = expression.args.get("empty_handling") 3617 empty_handling = f" {empty_handling}" if empty_handling else "" 3618 schema = self.sql(expression, "schema") 3619 return self.func( 3620 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3621 )
3623 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3624 this = self.sql(expression, "this") 3625 kind = self.sql(expression, "kind") 3626 path = self.sql(expression, "path") 3627 path = f" {path}" if path else "" 3628 as_json = " AS JSON" if expression.args.get("as_json") else "" 3629 return f"{this} {kind}{path}{as_json}"
3631 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3632 this = self.sql(expression, "this") 3633 path = self.sql(expression, "path") 3634 path = f", {path}" if path else "" 3635 expressions = self.expressions(expression) 3636 with_ = ( 3637 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3638 if expressions 3639 else "" 3640 ) 3641 return f"OPENJSON({this}{path}){with_}"
3643 def in_sql(self, expression: exp.In) -> str: 3644 query = expression.args.get("query") 3645 unnest = expression.args.get("unnest") 3646 field = expression.args.get("field") 3647 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3648 3649 if query: 3650 in_sql = self.sql(query) 3651 elif unnest: 3652 in_sql = self.in_unnest_op(unnest) 3653 elif field: 3654 in_sql = self.sql(field) 3655 else: 3656 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3657 3658 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3663 def interval_sql(self, expression: exp.Interval) -> str: 3664 unit_expression = expression.args.get("unit") 3665 unit = self.sql(unit_expression) if unit_expression else "" 3666 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3667 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3668 unit = f" {unit}" if unit else "" 3669 3670 if self.SINGLE_STRING_INTERVAL: 3671 this = expression.this.name if expression.this else "" 3672 if this: 3673 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3674 return f"INTERVAL '{this}'{unit}" 3675 return f"INTERVAL '{this}{unit}'" 3676 return f"INTERVAL{unit}" 3677 3678 this = self.sql(expression, "this") 3679 if this: 3680 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3681 this = f" {this}" if unwrapped else f" ({this})" 3682 3683 return f"INTERVAL{this}{unit}"
3688 def reference_sql(self, expression: exp.Reference) -> str: 3689 this = self.sql(expression, "this") 3690 expressions = self.expressions(expression, flat=True) 3691 expressions = f"({expressions})" if expressions else "" 3692 options = self.expressions(expression, key="options", flat=True, sep=" ") 3693 options = f" {options}" if options else "" 3694 return f"REFERENCES {this}{expressions}{options}"
3696 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3697 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3698 parent = expression.parent 3699 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3700 3701 return self.func( 3702 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3703 )
3723 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3724 alias = expression.args["alias"] 3725 3726 parent = expression.parent 3727 pivot = parent and parent.parent 3728 3729 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3730 identifier_alias = isinstance(alias, exp.Identifier) 3731 literal_alias = isinstance(alias, exp.Literal) 3732 3733 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3734 alias.replace(exp.Literal.string(alias.output_name)) 3735 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3736 alias.replace(exp.to_identifier(alias.output_name)) 3737 3738 return self.alias_sql(expression)
def
and_sql( self, expression: sqlglot.expressions.core.And, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
def
or_sql( self, expression: sqlglot.expressions.core.Or, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
def
xor_sql( self, expression: sqlglot.expressions.core.Xor, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
def
connector_sql( self, expression: sqlglot.expressions.core.Connector, op: str, stack: list[str | sqlglot.expressions.core.Expr] | None = None) -> str:
3770 def connector_sql( 3771 self, 3772 expression: exp.Connector, 3773 op: str, 3774 stack: list[str | exp.Expr] | None = None, 3775 ) -> str: 3776 if stack is not None: 3777 if expression.expressions: 3778 stack.append(self.expressions(expression, sep=f" {op} ")) 3779 else: 3780 stack.append(expression.right) 3781 if expression.comments and self.comments: 3782 for comment in expression.comments: 3783 if comment: 3784 op += f" /*{self.sanitize_comment(comment)}*/" 3785 stack.extend((op, expression.left)) 3786 return op 3787 3788 stack = [expression] 3789 sqls: list[str] = [] 3790 ops = set() 3791 3792 while stack: 3793 node = stack.pop() 3794 if isinstance(node, exp.Connector): 3795 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3796 else: 3797 sql = self.sql(node) 3798 if sqls and sqls[-1] in ops: 3799 sqls[-1] += f" {sql}" 3800 else: 3801 sqls.append(sql) 3802 3803 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3804 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.functions.Cast, safe_prefix: str | None = None) -> str:
3824 def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str: 3825 format_sql = self.sql(expression, "format") 3826 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3827 to_sql = self.sql(expression, "to") 3828 to_sql = f" {to_sql}" if to_sql else "" 3829 action = self.sql(expression, "action") 3830 action = f" {action}" if action else "" 3831 default = self.sql(expression, "default") 3832 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3833 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3851 def comment_sql(self, expression: exp.Comment) -> str: 3852 this = self.sql(expression, "this") 3853 kind = expression.args["kind"] 3854 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3855 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3856 expression_sql = self.sql(expression, "expression") 3857 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3859 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3860 this = self.sql(expression, "this") 3861 delete = " DELETE" if expression.args.get("delete") else "" 3862 recompress = self.sql(expression, "recompress") 3863 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3864 to_disk = self.sql(expression, "to_disk") 3865 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3866 to_volume = self.sql(expression, "to_volume") 3867 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3868 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3870 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3871 where = self.sql(expression, "where") 3872 group = self.sql(expression, "group") 3873 aggregates = self.expressions(expression, key="aggregates") 3874 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3875 3876 if not (where or group or aggregates) and len(expression.expressions) == 1: 3877 return f"TTL {self.expressions(expression, flat=True)}" 3878 3879 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3898 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3899 this = self.sql(expression, "this") 3900 3901 dtype = self.sql(expression, "dtype") 3902 if dtype: 3903 collate = self.sql(expression, "collate") 3904 collate = f" COLLATE {collate}" if collate else "" 3905 using = self.sql(expression, "using") 3906 using = f" USING {using}" if using else "" 3907 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3908 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3909 3910 default = self.sql(expression, "default") 3911 if default: 3912 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3913 3914 comment = self.sql(expression, "comment") 3915 if comment: 3916 return f"ALTER COLUMN {this} COMMENT {comment}" 3917 3918 visible = expression.args.get("visible") 3919 if visible: 3920 return f"ALTER COLUMN {this} SET {visible}" 3921 3922 allow_null = expression.args.get("allow_null") 3923 drop = expression.args.get("drop") 3924 3925 if not drop and not allow_null: 3926 self.unsupported("Unsupported ALTER COLUMN syntax") 3927 3928 if allow_null is not None: 3929 keyword = "DROP" if drop else "SET" 3930 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3931 3932 return f"ALTER COLUMN {this} DROP DEFAULT"
3948 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3949 compound = " COMPOUND" if expression.args.get("compound") else "" 3950 this = self.sql(expression, "this") 3951 expressions = self.expressions(expression, flat=True) 3952 expressions = f"({expressions})" if expressions else "" 3953 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.ddl.AlterRename, include_to: bool = True) -> str:
3955 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3956 if not self.RENAME_TABLE_WITH_DB: 3957 # Remove db from tables 3958 expression = expression.transform( 3959 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3960 ).assert_is(exp.AlterRename) 3961 this = self.sql(expression, "this") 3962 to_kw = " TO" if include_to else "" 3963 return f"RENAME{to_kw} {this}"
3978 def alter_sql(self, expression: exp.Alter) -> str: 3979 actions = expression.args["actions"] 3980 3981 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3982 actions[0], exp.ColumnDef 3983 ): 3984 actions_sql = self.expressions(expression, key="actions", flat=True) 3985 actions_sql = f"ADD {actions_sql}" 3986 else: 3987 actions_list = [] 3988 for action in actions: 3989 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3990 action_sql = self.add_column_sql(action) 3991 else: 3992 action_sql = self.sql(action) 3993 if isinstance(action, exp.Query): 3994 action_sql = f"AS {action_sql}" 3995 3996 actions_list.append(action_sql) 3997 3998 actions_sql = self.format_args(*actions_list).lstrip("\n") 3999 4000 iceberg = ( 4001 "ICEBERG " 4002 if expression.args.get("iceberg") and self.SUPPORTS_DROP_ALTER_ICEBERG_PROPERTY 4003 else "" 4004 ) 4005 exists = " IF EXISTS" if expression.args.get("exists") else "" 4006 on_cluster = self.sql(expression, "cluster") 4007 on_cluster = f" {on_cluster}" if on_cluster else "" 4008 only = " ONLY" if expression.args.get("only") else "" 4009 options = self.expressions(expression, key="options") 4010 options = f", {options}" if options else "" 4011 kind = self.sql(expression, "kind") 4012 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 4013 check = " WITH CHECK" if expression.args.get("check") else "" 4014 cascade = ( 4015 " CASCADE" 4016 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 4017 else "" 4018 ) 4019 this = self.sql(expression, "this") 4020 this = f" {this}" if this else "" 4021 4022 return f"ALTER {iceberg}{kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
4029 def add_column_sql(self, expression: exp.Expr) -> str: 4030 sql = self.sql(expression) 4031 if isinstance(expression, exp.Schema): 4032 column_text = " COLUMNS" 4033 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 4034 column_text = " COLUMN" 4035 else: 4036 column_text = "" 4037 4038 return f"ADD{column_text} {sql}"
4048 def addpartition_sql(self, expression: exp.AddPartition) -> str: 4049 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 4050 location = self.sql(expression, "location") 4051 location = f" {location}" if location else "" 4052 return f"ADD {exists}{self.sql(expression.this)}{location}"
4054 def distinct_sql(self, expression: exp.Distinct) -> str: 4055 this = self.expressions(expression, flat=True) 4056 4057 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 4058 case = exp.case() 4059 for arg in expression.expressions: 4060 case = case.when(arg.is_(exp.null()), exp.null()) 4061 this = self.sql(case.else_(f"({this})")) 4062 4063 this = f" {this}" if this else "" 4064 4065 on = self.sql(expression, "on") 4066 on = f" ON {on}" if on else "" 4067 return f"DISTINCT{this}{on}"
4094 def div_sql(self, expression: exp.Div) -> str: 4095 l, r = expression.left, expression.right 4096 4097 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 4098 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 4099 4100 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 4101 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 4102 l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE)) 4103 4104 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 4105 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 4106 return self.sql( 4107 exp.cast( 4108 l / r, 4109 to=exp.DType.BIGINT, 4110 ) 4111 ) 4112 4113 return self.binary(expression, "/")
4135 def escape_sql(self, expression: exp.Escape) -> str: 4136 this = expression.this 4137 if ( 4138 isinstance(this, (exp.Like, exp.ILike)) 4139 and isinstance(this.expression, (exp.All, exp.Any)) 4140 and not self.SUPPORTS_LIKE_QUANTIFIERS 4141 ): 4142 return self._like_sql(this, escape=expression) 4143 return self.binary(expression, "ESCAPE")
4255 def log_sql(self, expression: exp.Log) -> str: 4256 this = expression.this 4257 expr = expression.expression 4258 4259 if self.dialect.LOG_BASE_FIRST is False: 4260 this, expr = expr, this 4261 elif self.dialect.LOG_BASE_FIRST is None and expr: 4262 if this.name in ("2", "10"): 4263 return self.func(f"LOG{this.name}", expr) 4264 4265 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 4266 4267 return self.func("LOG", this, expr)
4276 def binary(self, expression: exp.Binary, op: str) -> str: 4277 sqls: list[str] = [] 4278 stack: list[None | str | exp.Expr] = [expression] 4279 binary_type = type(expression) 4280 4281 while stack: 4282 node = stack.pop() 4283 4284 if type(node) is binary_type: 4285 op_func = node.args.get("operator") 4286 if op_func: 4287 op = f"OPERATOR({self.sql(op_func)})" 4288 4289 stack.append(node.args.get("expression")) 4290 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4291 stack.append(node.args.get("this")) 4292 else: 4293 sqls.append(self.sql(node)) 4294 4295 return "".join(sqls)
def
ceil_floor( self, expression: sqlglot.expressions.math.Ceil | sqlglot.expressions.math.Floor) -> str:
4304 def function_fallback_sql(self, expression: exp.Func) -> str: 4305 args = [] 4306 4307 for key in expression.arg_types: 4308 arg_value = expression.args.get(key) 4309 4310 if isinstance(arg_value, list): 4311 for value in arg_value: 4312 args.append(value) 4313 elif arg_value is not None: 4314 args.append(arg_value) 4315 4316 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4317 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4318 else: 4319 name = expression.sql_name() 4320 4321 return self.func(name, *args)
def
func( self, name: str, *args: Any, prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
def
format_args(self, *args: Any, sep: str = ', ') -> str:
4334 def format_args(self, *args: t.Any, sep: str = ", ") -> str: 4335 arg_sqls = tuple( 4336 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4337 ) 4338 if self.pretty and self.too_wide(arg_sqls): 4339 return self.indent( 4340 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4341 ) 4342 return sep.join(arg_sqls)
def
format_time( self, expression: sqlglot.expressions.core.Expr, inverse_time_mapping: dict[str, str] | None = None, inverse_time_trie: dict | None = None) -> str | None:
4347 def format_time( 4348 self, 4349 expression: exp.Expr, 4350 inverse_time_mapping: dict[str, str] | None = None, 4351 inverse_time_trie: dict | None = None, 4352 ) -> str | None: 4353 return format_time( 4354 self.sql(expression, "format"), 4355 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4356 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4357 )
def
expressions( self, expression: sqlglot.expressions.core.Expr | None = None, key: str | None = None, sqls: Optional[Collection[str | sqlglot.expressions.core.Expr]] = None, flat: bool = False, indent: bool = True, skip_first: bool = False, skip_last: bool = False, sep: str = ', ', prefix: str = '', dynamic: bool = False, new_line: bool = False) -> str:
4359 def expressions( 4360 self, 4361 expression: exp.Expr | None = None, 4362 key: str | None = None, 4363 sqls: t.Collection[str | exp.Expr] | None = None, 4364 flat: bool = False, 4365 indent: bool = True, 4366 skip_first: bool = False, 4367 skip_last: bool = False, 4368 sep: str = ", ", 4369 prefix: str = "", 4370 dynamic: bool = False, 4371 new_line: bool = False, 4372 ) -> str: 4373 expressions = expression.args.get(key or "expressions") if expression else sqls 4374 4375 if not expressions: 4376 return "" 4377 4378 if flat: 4379 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4380 4381 num_sqls = len(expressions) 4382 result_sqls = [] 4383 4384 for i, e in enumerate(expressions): 4385 sql = self.sql(e, comment=False) 4386 if not sql: 4387 continue 4388 4389 comments = self.maybe_comment("", e) if isinstance(e, exp.Expr) else "" 4390 4391 if self.pretty: 4392 if self.leading_comma: 4393 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4394 else: 4395 result_sqls.append( 4396 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4397 ) 4398 else: 4399 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4400 4401 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4402 if new_line: 4403 result_sqls.insert(0, "") 4404 result_sqls.append("") 4405 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4406 else: 4407 result_sql = "".join(result_sqls) 4408 4409 return ( 4410 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4411 if indent 4412 else result_sql 4413 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.core.Expr, flat: bool = False) -> str:
4415 def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str: 4416 flat = flat or isinstance(expression.parent, exp.Properties) 4417 expressions_sql = self.expressions(expression, flat=flat) 4418 if flat: 4419 return f"{op} {expressions_sql}" 4420 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4422 def naked_property(self, expression: exp.Property) -> str: 4423 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4424 if not property_name: 4425 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4426 return f"{property_name} {self.sql(expression, 'this')}"
4434 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4435 this = self.sql(expression, "this") 4436 expressions = self.no_identify(self.expressions, expression) 4437 expressions = ( 4438 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4439 ) 4440 return f"{this}{expressions}" if expressions.strip() != "" else this
4450 def when_sql(self, expression: exp.When) -> str: 4451 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4452 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4453 condition = self.sql(expression, "condition") 4454 condition = f" AND {condition}" if condition else "" 4455 4456 then_expression = expression.args.get("then") 4457 if isinstance(then_expression, exp.Insert): 4458 this = self.sql(then_expression, "this") 4459 this = f"INSERT {this}" if this else "INSERT" 4460 then = self.sql(then_expression, "expression") 4461 then = f"{this} VALUES {then}" if then else this 4462 elif isinstance(then_expression, exp.Update): 4463 if isinstance(then_expression.args.get("expressions"), exp.Star): 4464 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4465 else: 4466 expressions_sql = self.expressions(then_expression) 4467 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4468 else: 4469 then = self.sql(then_expression) 4470 4471 if isinstance(then_expression, (exp.Insert, exp.Update)): 4472 where = self.sql(then_expression, "where") 4473 if where and not self.SUPPORTS_MERGE_WHERE: 4474 kind = "INSERT" if isinstance(then_expression, exp.Insert) else "UPDATE" 4475 self.unsupported(f"WHERE clause in MERGE {kind} is not supported") 4476 where = "" 4477 then = f"{then}{where}" 4478 return f"WHEN {matched}{source}{condition} THEN {then}"
4483 def merge_sql(self, expression: exp.Merge) -> str: 4484 table = expression.this 4485 table_alias = "" 4486 4487 hints = table.args.get("hints") 4488 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4489 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4490 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4491 4492 this = self.sql(table) 4493 using = f"USING {self.sql(expression, 'using')}" 4494 whens = self.sql(expression, "whens") 4495 4496 on = self.sql(expression, "on") 4497 on = f"ON {on}" if on else "" 4498 4499 if not on: 4500 on = self.expressions(expression, key="using_cond") 4501 on = f"USING ({on})" if on else "" 4502 4503 returning = self.sql(expression, "returning") 4504 if returning: 4505 whens = f"{whens}{returning}" 4506 4507 sep = self.sep() 4508 4509 return self.prepend_ctes( 4510 expression, 4511 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4512 )
@unsupported_args('format')
def
tochar_sql(self, expression: sqlglot.expressions.string.ToChar) -> str:
4518 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4519 if not self.SUPPORTS_TO_NUMBER: 4520 self.unsupported("Unsupported TO_NUMBER function") 4521 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4522 4523 fmt = expression.args.get("format") 4524 if not fmt: 4525 self.unsupported("Conversion format is required for TO_NUMBER") 4526 return self.sql(exp.cast(expression.this, exp.DType.DOUBLE)) 4527 4528 return self.func("TO_NUMBER", expression.this, fmt)
4530 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4531 this = self.sql(expression, "this") 4532 kind = self.sql(expression, "kind") 4533 settings_sql = self.expressions(expression, key="settings", sep=" ") 4534 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4535 return f"{this}({kind}{args})"
def
duplicatekeyproperty_sql( self, expression: sqlglot.expressions.properties.DuplicateKeyProperty) -> str:
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.properties.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
def
distributedbyproperty_sql( self, expression: sqlglot.expressions.properties.DistributedByProperty) -> str:
4556 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4557 expressions = self.expressions(expression, flat=True) 4558 expressions = f" {self.wrap(expressions)}" if expressions else "" 4559 buckets = self.sql(expression, "buckets") 4560 kind = self.sql(expression, "kind") 4561 buckets = f" BUCKETS {buckets}" if buckets else "" 4562 order = self.sql(expression, "order") 4563 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
def
clusteredbyproperty_sql( self, expression: sqlglot.expressions.properties.ClusteredByProperty) -> str:
4568 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4569 expressions = self.expressions(expression, key="expressions", flat=True) 4570 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4571 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4572 buckets = self.sql(expression, "buckets") 4573 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4575 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4576 this = self.sql(expression, "this") 4577 having = self.sql(expression, "having") 4578 4579 if having: 4580 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4581 4582 return self.func("ANY_VALUE", this)
4584 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4585 transform = self.func("TRANSFORM", *expression.expressions) 4586 row_format_before = self.sql(expression, "row_format_before") 4587 row_format_before = f" {row_format_before}" if row_format_before else "" 4588 record_writer = self.sql(expression, "record_writer") 4589 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4590 using = f" USING {self.sql(expression, 'command_script')}" 4591 schema = self.sql(expression, "schema") 4592 schema = f" AS {schema}" if schema else "" 4593 row_format_after = self.sql(expression, "row_format_after") 4594 row_format_after = f" {row_format_after}" if row_format_after else "" 4595 record_reader = self.sql(expression, "record_reader") 4596 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4597 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
def
indexconstraintoption_sql( self, expression: sqlglot.expressions.constraints.IndexConstraintOption) -> str:
4599 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4600 key_block_size = self.sql(expression, "key_block_size") 4601 if key_block_size: 4602 return f"KEY_BLOCK_SIZE = {key_block_size}" 4603 4604 using = self.sql(expression, "using") 4605 if using: 4606 return f"USING {using}" 4607 4608 parser = self.sql(expression, "parser") 4609 if parser: 4610 return f"WITH PARSER {parser}" 4611 4612 comment = self.sql(expression, "comment") 4613 if comment: 4614 return f"COMMENT {comment}" 4615 4616 visible = expression.args.get("visible") 4617 if visible is not None: 4618 return "VISIBLE" if visible else "INVISIBLE" 4619 4620 engine_attr = self.sql(expression, "engine_attr") 4621 if engine_attr: 4622 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4623 4624 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4625 if secondary_engine_attr: 4626 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4627 4628 self.unsupported("Unsupported index constraint option.") 4629 return ""
def
checkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.CheckColumnConstraint) -> str:
def
indexcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.IndexColumnConstraint) -> str:
4635 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4636 kind = self.sql(expression, "kind") 4637 kind = f"{kind} INDEX" if kind else "INDEX" 4638 this = self.sql(expression, "this") 4639 this = f" {this}" if this else "" 4640 index_type = self.sql(expression, "index_type") 4641 index_type = f" USING {index_type}" if index_type else "" 4642 expressions = self.expressions(expression, flat=True) 4643 expressions = f" ({expressions})" if expressions else "" 4644 options = self.expressions(expression, key="options", sep=" ") 4645 options = f" {options}" if options else "" 4646 return f"{kind}{this}{index_type}{expressions}{options}"
4648 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4649 if self.NVL2_SUPPORTED: 4650 return self.function_fallback_sql(expression) 4651 4652 case = exp.Case().when( 4653 expression.this.is_(exp.null()).not_(copy=False), 4654 expression.args["true"], 4655 copy=False, 4656 ) 4657 else_cond = expression.args.get("false") 4658 if else_cond: 4659 case.else_(else_cond, copy=False) 4660 4661 return self.sql(case)
4663 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4664 this = self.sql(expression, "this") 4665 expr = self.sql(expression, "expression") 4666 position = self.sql(expression, "position") 4667 position = f", {position}" if position else "" 4668 iterator = self.sql(expression, "iterator") 4669 condition = self.sql(expression, "condition") 4670 condition = f" IF {condition}" if condition else "" 4671 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
def
generateembedding_sql(self, expression: sqlglot.expressions.functions.GenerateEmbedding) -> str:
4721 def aiforecast_sql(self, expression: exp.AIForecast) -> str: 4722 this_sql = self.sql(expression, "this") 4723 if isinstance(expression.this, exp.Table): 4724 this_sql = f"TABLE {this_sql}" 4725 4726 return self.func( 4727 "FORECAST", 4728 this_sql, 4729 expression.args.get("data_col"), 4730 expression.args.get("timestamp_col"), 4731 expression.args.get("model"), 4732 expression.args.get("id_cols"), 4733 expression.args.get("horizon"), 4734 expression.args.get("forecast_end_timestamp"), 4735 expression.args.get("confidence_level"), 4736 expression.args.get("output_historical_time_series"), 4737 expression.args.get("context_window"), 4738 )
4740 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4741 this_sql = self.sql(expression, "this") 4742 if isinstance(expression.this, exp.Table): 4743 this_sql = f"TABLE {this_sql}" 4744 4745 return self.func( 4746 "FEATURES_AT_TIME", 4747 this_sql, 4748 expression.args.get("time"), 4749 expression.args.get("num_rows"), 4750 expression.args.get("ignore_feature_nulls"), 4751 )
4753 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4754 this_sql = self.sql(expression, "this") 4755 if isinstance(expression.this, exp.Table): 4756 this_sql = f"TABLE {this_sql}" 4757 4758 query_table = self.sql(expression, "query_table") 4759 if isinstance(expression.args["query_table"], exp.Table): 4760 query_table = f"TABLE {query_table}" 4761 4762 return self.func( 4763 "VECTOR_SEARCH", 4764 this_sql, 4765 expression.args.get("column_to_search"), 4766 query_table, 4767 expression.args.get("query_column_to_search"), 4768 expression.args.get("top_k"), 4769 expression.args.get("distance_type"), 4770 expression.args.get("options"), 4771 )
4783 def toarray_sql(self, expression: exp.ToArray) -> str: 4784 arg = expression.this 4785 if not arg.type: 4786 import sqlglot.optimizer.annotate_types 4787 4788 arg = sqlglot.optimizer.annotate_types.annotate_types(arg, dialect=self.dialect) 4789 4790 if arg.is_type(exp.DType.ARRAY): 4791 return self.sql(arg) 4792 4793 cond_for_null = arg.is_(exp.null()) 4794 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4796 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4797 this = expression.this 4798 time_format = self.format_time(expression) 4799 4800 if time_format: 4801 return self.sql( 4802 exp.cast( 4803 exp.StrToTime(this=this, format=expression.args["format"]), 4804 exp.DType.TIME, 4805 ) 4806 ) 4807 4808 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME): 4809 return self.sql(this) 4810 4811 return self.sql(exp.cast(this, exp.DType.TIME))
4813 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4814 this = expression.this 4815 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP): 4816 return self.sql(this) 4817 4818 return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))
4820 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4821 this = expression.this 4822 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME): 4823 return self.sql(this) 4824 4825 return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))
4827 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4828 this = expression.this 4829 time_format = self.format_time(expression) 4830 safe = expression.args.get("safe") 4831 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4832 return self.sql( 4833 exp.cast( 4834 exp.StrToTime(this=this, format=expression.args["format"], safe=safe), 4835 exp.DType.DATE, 4836 ) 4837 ) 4838 4839 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE): 4840 return self.sql(this) 4841 4842 if safe: 4843 return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE))) 4844 4845 return self.sql(exp.cast(this, exp.DType.DATE))
4857 def lastday_sql(self, expression: exp.LastDay) -> str: 4858 if self.LAST_DAY_SUPPORTS_DATE_PART: 4859 return self.function_fallback_sql(expression) 4860 4861 unit = expression.text("unit") 4862 if unit and unit != "MONTH": 4863 self.unsupported("Date parts are not supported in LAST_DAY.") 4864 4865 return self.func("LAST_DAY", expression.this)
4877 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4878 if self.CAN_IMPLEMENT_ARRAY_ANY: 4879 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4880 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4881 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4882 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4883 4884 import sqlglot.dialects.dialect 4885 4886 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4887 if self.dialect.__class__ != sqlglot.dialects.dialect.Dialect: 4888 self.unsupported("ARRAY_ANY is unsupported") 4889 4890 return self.function_fallback_sql(expression)
4892 def struct_sql(self, expression: exp.Struct) -> str: 4893 expression.set( 4894 "expressions", 4895 [ 4896 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4897 if isinstance(e, exp.PropertyEQ) 4898 else e 4899 for e in expression.expressions 4900 ], 4901 ) 4902 4903 return self.function_fallback_sql(expression)
4911 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4912 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4913 tables = f" {self.expressions(expression)}" 4914 4915 exists = " IF EXISTS" if expression.args.get("exists") else "" 4916 4917 on_cluster = self.sql(expression, "cluster") 4918 on_cluster = f" {on_cluster}" if on_cluster else "" 4919 4920 identity = self.sql(expression, "identity") 4921 identity = f" {identity} IDENTITY" if identity else "" 4922 4923 option = self.sql(expression, "option") 4924 option = f" {option}" if option else "" 4925 4926 partition = self.sql(expression, "partition") 4927 partition = f" {partition}" if partition else "" 4928 4929 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4933 def convert_sql(self, expression: exp.Convert) -> str: 4934 to = expression.this 4935 value = expression.expression 4936 style = expression.args.get("style") 4937 safe = expression.args.get("safe") 4938 strict = expression.args.get("strict") 4939 4940 if not to or not value: 4941 return "" 4942 4943 # Retrieve length of datatype and override to default if not specified 4944 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4945 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4946 4947 transformed: exp.Expr | None = None 4948 cast = exp.Cast if strict else exp.TryCast 4949 4950 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4951 if isinstance(style, exp.Literal) and style.is_int: 4952 import sqlglot.dialects.tsql 4953 4954 style_value = style.name 4955 converted_style = sqlglot.dialects.tsql.TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4956 if not converted_style: 4957 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4958 4959 fmt = exp.Literal.string(converted_style) 4960 4961 if to.this == exp.DType.DATE: 4962 transformed = exp.StrToDate(this=value, format=fmt) 4963 elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2): 4964 transformed = exp.StrToTime(this=value, format=fmt) 4965 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4966 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4967 elif to.this == exp.DType.TEXT: 4968 transformed = exp.TimeToStr(this=value, format=fmt) 4969 4970 if not transformed: 4971 transformed = cast(this=value, to=to, safe=safe) 4972 4973 return self.sql(transformed)
5044 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 5045 option = self.sql(expression, "this") 5046 5047 if expression.expressions: 5048 upper = option.upper() 5049 5050 # Snowflake FILE_FORMAT options are separated by whitespace 5051 sep = " " if upper == "FILE_FORMAT" else ", " 5052 5053 # Databricks copy/format options do not set their list of values with EQ 5054 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 5055 values = self.expressions(expression, flat=True, sep=sep) 5056 return f"{option}{op}({values})" 5057 5058 value = self.sql(expression, "expression") 5059 5060 if not value: 5061 return option 5062 5063 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 5064 5065 return f"{option}{op}{value}"
5067 def credentials_sql(self, expression: exp.Credentials) -> str: 5068 cred_expr = expression.args.get("credentials") 5069 if isinstance(cred_expr, exp.Literal): 5070 # Redshift case: CREDENTIALS <string> 5071 credentials = self.sql(expression, "credentials") 5072 credentials = f"CREDENTIALS {credentials}" if credentials else "" 5073 else: 5074 # Snowflake case: CREDENTIALS = (...) 5075 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 5076 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 5077 5078 storage = self.sql(expression, "storage") 5079 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 5080 5081 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 5082 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 5083 5084 iam_role = self.sql(expression, "iam_role") 5085 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 5086 5087 region = self.sql(expression, "region") 5088 region = f" REGION {region}" if region else "" 5089 5090 return f"{credentials}{storage}{encryption}{iam_role}{region}"
5092 def copy_sql(self, expression: exp.Copy) -> str: 5093 this = self.sql(expression, "this") 5094 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 5095 5096 credentials = self.sql(expression, "credentials") 5097 credentials = self.seg(credentials) if credentials else "" 5098 files = self.expressions(expression, key="files", flat=True) 5099 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 5100 5101 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 5102 params = self.expressions( 5103 expression, 5104 key="params", 5105 sep=sep, 5106 new_line=True, 5107 skip_last=True, 5108 skip_first=True, 5109 indent=self.COPY_PARAMS_ARE_WRAPPED, 5110 ) 5111 5112 if params: 5113 if self.COPY_PARAMS_ARE_WRAPPED: 5114 params = f" WITH ({params})" 5115 elif not self.pretty and (files or credentials): 5116 params = f" {params}" 5117 5118 return f"COPY{this}{kind} {files}{credentials}{params}"
def
datadeletionproperty_sql( self, expression: sqlglot.expressions.properties.DataDeletionProperty) -> str:
5123 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 5124 on_sql = "ON" if expression.args.get("on") else "OFF" 5125 filter_col: str | None = self.sql(expression, "filter_column") 5126 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 5127 retention_period: str | None = self.sql(expression, "retention_period") 5128 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 5129 5130 if filter_col or retention_period: 5131 on_sql = self.func("ON", filter_col, retention_period) 5132 5133 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.MaskingPolicyColumnConstraint) -> str:
5135 def maskingpolicycolumnconstraint_sql( 5136 self, expression: exp.MaskingPolicyColumnConstraint 5137 ) -> str: 5138 this = self.sql(expression, "this") 5139 expressions = self.expressions(expression, flat=True) 5140 expressions = f" USING ({expressions})" if expressions else "" 5141 return f"MASKING POLICY {this}{expressions}"
5151 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 5152 this = self.sql(expression, "this") 5153 expr = expression.expression 5154 5155 if isinstance(expr, exp.Func): 5156 # T-SQL's CLR functions are case sensitive 5157 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 5158 else: 5159 expr = self.sql(expression, "expression") 5160 5161 return self.scope_resolution(expr, this)
5169 def rand_sql(self, expression: exp.Rand) -> str: 5170 lower = self.sql(expression, "lower") 5171 upper = self.sql(expression, "upper") 5172 5173 if lower and upper: 5174 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 5175 return self.func("RAND", expression.this)
5177 def changes_sql(self, expression: exp.Changes) -> str: 5178 information = self.sql(expression, "information") 5179 information = f"INFORMATION => {information}" 5180 at_before = self.sql(expression, "at_before") 5181 at_before = f"{self.seg('')}{at_before}" if at_before else "" 5182 end = self.sql(expression, "end") 5183 end = f"{self.seg('')}{end}" if end else "" 5184 5185 return f"CHANGES ({information}){at_before}{end}"
5187 def pad_sql(self, expression: exp.Pad) -> str: 5188 prefix = "L" if expression.args.get("is_left") else "R" 5189 5190 fill_pattern = self.sql(expression, "fill_pattern") or None 5191 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 5192 fill_pattern = "' '" 5193 5194 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql( self, expression: sqlglot.expressions.array.ExplodingGenerateSeries) -> str:
5200 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 5201 generate_series = exp.GenerateSeries(**expression.args) 5202 5203 parent = expression.parent 5204 if isinstance(parent, (exp.Alias, exp.TableAlias)): 5205 parent = parent.parent 5206 5207 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 5208 return self.sql(exp.Unnest(expressions=[generate_series])) 5209 5210 if isinstance(parent, exp.Select): 5211 self.unsupported("GenerateSeries projection unnesting is not supported.") 5212 5213 return self.sql(generate_series)
5215 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 5216 if self.SUPPORTS_CONVERT_TIMEZONE: 5217 return self.function_fallback_sql(expression) 5218 5219 source_tz = expression.args.get("source_tz") 5220 target_tz = expression.args.get("target_tz") 5221 timestamp = expression.args.get("timestamp") 5222 5223 if source_tz and timestamp: 5224 timestamp = exp.AtTimeZone( 5225 this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz 5226 ) 5227 5228 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 5229 5230 return self.sql(expr)
5232 def json_sql(self, expression: exp.JSON) -> str: 5233 this = self.sql(expression, "this") 5234 this = f" {this}" if this else "" 5235 5236 _with = expression.args.get("with_") 5237 5238 if _with is None: 5239 with_sql = "" 5240 elif not _with: 5241 with_sql = " WITHOUT" 5242 else: 5243 with_sql = " WITH" 5244 5245 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 5246 5247 return f"JSON{this}{with_sql}{unique_sql}"
5249 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 5250 path = self.sql(expression, "path") 5251 returning = self.sql(expression, "returning") 5252 returning = f" RETURNING {returning}" if returning else "" 5253 5254 on_condition = self.sql(expression, "on_condition") 5255 on_condition = f" {on_condition}" if on_condition else "" 5256 5257 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
5263 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 5264 else_ = "ELSE " if expression.args.get("else_") else "" 5265 condition = self.sql(expression, "expression") 5266 condition = f"WHEN {condition} THEN " if condition else else_ 5267 insert = self.sql(expression, "this")[len("INSERT") :].strip() 5268 return f"{condition}{insert}"
5276 def oncondition_sql(self, expression: exp.OnCondition) -> str: 5277 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 5278 empty = expression.args.get("empty") 5279 empty = ( 5280 f"DEFAULT {empty} ON EMPTY" 5281 if isinstance(empty, exp.Expr) 5282 else self.sql(expression, "empty") 5283 ) 5284 5285 error = expression.args.get("error") 5286 error = ( 5287 f"DEFAULT {error} ON ERROR" 5288 if isinstance(error, exp.Expr) 5289 else self.sql(expression, "error") 5290 ) 5291 5292 if error and empty: 5293 error = ( 5294 f"{empty} {error}" 5295 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 5296 else f"{error} {empty}" 5297 ) 5298 empty = "" 5299 5300 null = self.sql(expression, "null") 5301 5302 return f"{empty}{error}{null}"
5308 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 5309 this = self.sql(expression, "this") 5310 path = self.sql(expression, "path") 5311 5312 passing = self.expressions(expression, "passing") 5313 passing = f" PASSING {passing}" if passing else "" 5314 5315 on_condition = self.sql(expression, "on_condition") 5316 on_condition = f" {on_condition}" if on_condition else "" 5317 5318 path = f"{path}{passing}{on_condition}" 5319 5320 return self.func("JSON_EXISTS", this, path)
5445 def overlay_sql(self, expression: exp.Overlay) -> str: 5446 this = self.sql(expression, "this") 5447 expr = self.sql(expression, "expression") 5448 from_sql = self.sql(expression, "from_") 5449 for_sql = self.sql(expression, "for_") 5450 for_sql = f" FOR {for_sql}" if for_sql else "" 5451 5452 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.string.ToDouble) -> str:
5459 def string_sql(self, expression: exp.String) -> str: 5460 this = expression.this 5461 zone = expression.args.get("zone") 5462 5463 if zone: 5464 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5465 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5466 # set for source_tz to transpile the time conversion before the STRING cast 5467 this = exp.ConvertTimezone( 5468 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5469 ) 5470 5471 return self.sql(exp.cast(this, exp.DType.VARCHAR))
def
overflowtruncatebehavior_sql( self, expression: sqlglot.expressions.query.OverflowTruncateBehavior) -> str:
5481 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5482 filler = self.sql(expression, "this") 5483 filler = f" {filler}" if filler else "" 5484 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5485 return f"TRUNCATE{filler} {with_count}"
5487 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5488 if self.SUPPORTS_UNIX_SECONDS: 5489 return self.function_fallback_sql(expression) 5490 5491 start_ts = exp.cast(exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DType.TIMESTAMPTZ) 5492 5493 return self.sql( 5494 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5495 )
5497 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5498 dim = expression.expression 5499 5500 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5501 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5502 if not (dim.is_int and dim.name == "1"): 5503 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5504 dim = None 5505 5506 # If dimension is required but not specified, default initialize it 5507 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5508 dim = exp.Literal.number(1) 5509 5510 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5512 def attach_sql(self, expression: exp.Attach) -> str: 5513 this = self.sql(expression, "this") 5514 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5515 expressions = self.expressions(expression) 5516 expressions = f" ({expressions})" if expressions else "" 5517 5518 return f"ATTACH{exists_sql} {this}{expressions}"
5520 def detach_sql(self, expression: exp.Detach) -> str: 5521 kind = self.sql(expression, "kind") 5522 kind = f" {kind}" if kind else "" 5523 # the DATABASE keyword is required if IF EXISTS is set for DuckDB 5524 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5525 exists = " IF EXISTS" if expression.args.get("exists") else "" 5526 if exists: 5527 kind = kind or " DATABASE" 5528 5529 this = self.sql(expression, "this") 5530 this = f" {this}" if this else "" 5531 cluster = self.sql(expression, "cluster") 5532 cluster = f" {cluster}" if cluster else "" 5533 permanent = " PERMANENTLY" if expression.args.get("permanent") else "" 5534 sync = " SYNC" if expression.args.get("sync") else "" 5535 return f"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}"
def
watermarkcolumnconstraint_sql( self, expression: sqlglot.expressions.constraints.WatermarkColumnConstraint) -> str:
5548 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5549 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5550 encode = f"{encode} {self.sql(expression, 'this')}" 5551 5552 properties = expression.args.get("properties") 5553 if properties: 5554 encode = f"{encode} {self.properties(properties)}" 5555 5556 return encode
5558 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5559 this = self.sql(expression, "this") 5560 include = f"INCLUDE {this}" 5561 5562 column_def = self.sql(expression, "column_def") 5563 if column_def: 5564 include = f"{include} {column_def}" 5565 5566 alias = self.sql(expression, "alias") 5567 if alias: 5568 include = f"{include} AS {alias}" 5569 5570 return include
def
partitionbyrangeproperty_sql( self, expression: sqlglot.expressions.properties.PartitionByRangeProperty) -> str:
5583 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5584 partitions = self.expressions(expression, "partition_expressions") 5585 create = self.expressions(expression, "create_expressions") 5586 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.properties.PartitionByRangePropertyDynamic) -> str:
5588 def partitionbyrangepropertydynamic_sql( 5589 self, expression: exp.PartitionByRangePropertyDynamic 5590 ) -> str: 5591 start = self.sql(expression, "start") 5592 end = self.sql(expression, "end") 5593 5594 every = expression.args["every"] 5595 if isinstance(every, exp.Interval) and every.this.is_string: 5596 every.this.replace(exp.Literal.number(every.name)) 5597 5598 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5611 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5612 kind = self.sql(expression, "kind") 5613 option = self.sql(expression, "option") 5614 option = f" {option}" if option else "" 5615 this = self.sql(expression, "this") 5616 this = f" {this}" if this else "" 5617 columns = self.expressions(expression) 5618 columns = f" {columns}" if columns else "" 5619 return f"{kind}{option} STATISTICS{this}{columns}"
5621 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5622 this = self.sql(expression, "this") 5623 columns = self.expressions(expression) 5624 inner_expression = self.sql(expression, "expression") 5625 inner_expression = f" {inner_expression}" if inner_expression else "" 5626 update_options = self.sql(expression, "update_options") 5627 update_options = f" {update_options} UPDATE" if update_options else "" 5628 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql( self, expression: sqlglot.expressions.query.AnalyzeListChainedRows) -> str:
5639 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5640 kind = self.sql(expression, "kind") 5641 this = self.sql(expression, "this") 5642 this = f" {this}" if this else "" 5643 inner_expression = self.sql(expression, "expression") 5644 return f"VALIDATE {kind}{this}{inner_expression}"
5646 def analyze_sql(self, expression: exp.Analyze) -> str: 5647 options = self.expressions(expression, key="options", sep=" ") 5648 options = f" {options}" if options else "" 5649 kind = self.sql(expression, "kind") 5650 kind = f" {kind}" if kind else "" 5651 this = self.sql(expression, "this") 5652 this = f" {this}" if this else "" 5653 mode = self.sql(expression, "mode") 5654 mode = f" {mode}" if mode else "" 5655 properties = self.sql(expression, "properties") 5656 properties = f" {properties}" if properties else "" 5657 partition = self.sql(expression, "partition") 5658 partition = f" {partition}" if partition else "" 5659 inner_expression = self.sql(expression, "expression") 5660 inner_expression = f" {inner_expression}" if inner_expression else "" 5661 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5663 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5664 this = self.sql(expression, "this") 5665 namespaces = self.expressions(expression, key="namespaces") 5666 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5667 passing = self.expressions(expression, key="passing") 5668 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5669 columns = self.expressions(expression, key="columns") 5670 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5671 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5672 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5678 def export_sql(self, expression: exp.Export) -> str: 5679 this = self.sql(expression, "this") 5680 connection = self.sql(expression, "connection") 5681 connection = f"WITH CONNECTION {connection} " if connection else "" 5682 options = self.sql(expression, "options") 5683 return f"EXPORT DATA {connection}{options} AS {this}"
5689 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5690 variables = self.expressions(expression, "this") 5691 default = self.sql(expression, "default") 5692 default = f" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}" if default else "" 5693 5694 kind = self.sql(expression, "kind") 5695 if isinstance(expression.args.get("kind"), exp.Schema): 5696 kind = f"TABLE {kind}" 5697 5698 kind = f" {kind}" if kind else "" 5699 5700 return f"{variables}{kind}{default}"
def
recursivewithsearch_sql(self, expression: sqlglot.expressions.query.RecursiveWithSearch) -> str:
5702 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5703 kind = self.sql(expression, "kind") 5704 this = self.sql(expression, "this") 5705 set = self.sql(expression, "expression") 5706 using = self.sql(expression, "using") 5707 using = f" USING {using}" if using else "" 5708 5709 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5710 5711 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql( self, expression: sqlglot.expressions.core.CombinedParameterizedAgg) -> str:
def
get_put_sql( self, expression: sqlglot.expressions.query.Put | sqlglot.expressions.query.Get) -> str:
5734 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5735 # Snowflake GET/PUT statements: 5736 # PUT <file> <internalStage> <properties> 5737 # GET <internalStage> <file> <properties> 5738 props = expression.args.get("properties") 5739 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5740 this = self.sql(expression, "this") 5741 target = self.sql(expression, "target") 5742 5743 if isinstance(expression, exp.Put): 5744 return f"PUT {this} {target}{props_sql}" 5745 else: 5746 return f"GET {target} {this}{props_sql}"
def
translatecharacters_sql(self, expression: sqlglot.expressions.query.TranslateCharacters) -> str:
5748 def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str: 5749 this = self.sql(expression, "this") 5750 expr = self.sql(expression, "expression") 5751 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5752 return f"TRANSLATE({this} USING {expr}{with_error})"
5754 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5755 if self.SUPPORTS_DECODE_CASE: 5756 return self.func("DECODE", *expression.expressions) 5757 5758 decode_expr, *expressions = expression.expressions 5759 5760 ifs = [] 5761 for search, result in zip(expressions[::2], expressions[1::2]): 5762 if isinstance(search, exp.Literal): 5763 ifs.append(exp.If(this=decode_expr.eq(search), true=result)) 5764 elif isinstance(search, exp.Null): 5765 ifs.append(exp.If(this=decode_expr.is_(exp.Null()), true=result)) 5766 else: 5767 if isinstance(search, exp.Binary): 5768 search = exp.paren(search) 5769 5770 cond = exp.or_( 5771 decode_expr.eq(search), 5772 exp.and_(decode_expr.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5773 copy=False, 5774 ) 5775 ifs.append(exp.If(this=cond, true=result)) 5776 5777 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5778 return self.sql(case)
5780 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5781 this = self.sql(expression, "this") 5782 this = self.seg(this, sep="") 5783 dimensions = self.expressions( 5784 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5785 ) 5786 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5787 metrics = self.expressions( 5788 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5789 ) 5790 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5791 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5792 facts = self.seg(f"FACTS {facts}") if facts else "" 5793 where = self.sql(expression, "where") 5794 where = self.seg(f"WHERE {where}") if where else "" 5795 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5796 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5798 def getextract_sql(self, expression: exp.GetExtract) -> str: 5799 this = expression.this 5800 expr = expression.expression 5801 5802 if not this.type or not expression.type: 5803 import sqlglot.optimizer.annotate_types 5804 5805 this = sqlglot.optimizer.annotate_types.annotate_types(this, dialect=self.dialect) 5806 5807 if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)): 5808 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5809 5810 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql( self, expression: sqlglot.expressions.properties.RefreshTriggerProperty) -> str:
5827 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5828 method = self.sql(expression, "method") 5829 kind = expression.args.get("kind") 5830 if not kind: 5831 return f"REFRESH {method}" 5832 5833 every = self.sql(expression, "every") 5834 unit = self.sql(expression, "unit") 5835 every = f" EVERY {every} {unit}" if every else "" 5836 starts = self.sql(expression, "starts") 5837 starts = f" STARTS {starts}" if starts else "" 5838 5839 return f"REFRESH {method} ON {kind}{every}{starts}"
5848 def uuid_sql(self, expression: exp.Uuid) -> str: 5849 is_string = expression.args.get("is_string", False) 5850 uuid_func_sql = self.func("UUID") 5851 5852 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5853 return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect)) 5854 5855 return uuid_func_sql
5857 def initcap_sql(self, expression: exp.Initcap) -> str: 5858 delimiters = expression.expression 5859 5860 if delimiters: 5861 # do not generate delimiters arg if we are round-tripping from default delimiters 5862 if ( 5863 delimiters.is_string 5864 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5865 ): 5866 delimiters = None 5867 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5868 self.unsupported("INITCAP does not support custom delimiters") 5869 delimiters = None 5870 5871 return self.func("INITCAP", expression.this, delimiters)
5881 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5882 this = expression.this.name.upper() 5883 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5884 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5885 return "WEEK" 5886 5887 return self.func("WEEK", expression.this)
def
altermodifysqlsecurity_sql(self, expression: sqlglot.expressions.ddl.AlterModifySqlSecurity) -> str: