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.helper import apply_index_offset, csv, name_sequence, seq_get 12from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 13from sqlglot.time import format_time 14from sqlglot.tokens import TokenType 15 16if t.TYPE_CHECKING: 17 from sqlglot._typing import E 18 from sqlglot.dialects.dialect import DialectType 19 20 G = t.TypeVar("G", bound="Generator") 21 GeneratorMethod = t.Callable[[G, E], str] 22 23logger = logging.getLogger("sqlglot") 24 25ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 26UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 27 28 29def unsupported_args( 30 *args: t.Union[str, t.Tuple[str, str]], 31) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 32 """ 33 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 34 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 35 """ 36 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 37 for arg in args: 38 if isinstance(arg, str): 39 diagnostic_by_arg[arg] = None 40 else: 41 diagnostic_by_arg[arg[0]] = arg[1] 42 43 def decorator(func: GeneratorMethod) -> GeneratorMethod: 44 @wraps(func) 45 def _func(generator: G, expression: E) -> str: 46 expression_name = expression.__class__.__name__ 47 dialect_name = generator.dialect.__class__.__name__ 48 49 for arg_name, diagnostic in diagnostic_by_arg.items(): 50 if expression.args.get(arg_name): 51 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 52 arg_name, expression_name, dialect_name 53 ) 54 generator.unsupported(diagnostic) 55 56 return func(generator, expression) 57 58 return _func 59 60 return decorator 61 62 63class _Generator(type): 64 def __new__(cls, clsname, bases, attrs): 65 klass = super().__new__(cls, clsname, bases, attrs) 66 67 # Remove transforms that correspond to unsupported JSONPathPart expressions 68 for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: 69 klass.TRANSFORMS.pop(part, None) 70 71 return klass 72 73 74class Generator(metaclass=_Generator): 75 """ 76 Generator converts a given syntax tree to the corresponding SQL string. 77 78 Args: 79 pretty: Whether to format the produced SQL string. 80 Default: False. 81 identify: Determines when an identifier should be quoted. Possible values are: 82 False (default): Never quote, except in cases where it's mandatory by the dialect. 83 True: Always quote except for specials cases. 84 'safe': Only quote identifiers that are case insensitive. 85 normalize: Whether to normalize identifiers to lowercase. 86 Default: False. 87 pad: The pad size in a formatted string. For example, this affects the indentation of 88 a projection in a query, relative to its nesting level. 89 Default: 2. 90 indent: The indentation size in a formatted string. For example, this affects the 91 indentation of subqueries and filters under a `WHERE` clause. 92 Default: 2. 93 normalize_functions: How to normalize function names. Possible values are: 94 "upper" or True (default): Convert names to uppercase. 95 "lower": Convert names to lowercase. 96 False: Disables function name normalization. 97 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 98 Default ErrorLevel.WARN. 99 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 100 This is only relevant if unsupported_level is ErrorLevel.RAISE. 101 Default: 3 102 leading_comma: Whether the comma is leading or trailing in select expressions. 103 This is only relevant when generating in pretty mode. 104 Default: False 105 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 106 The default is on the smaller end because the length only represents a segment and not the true 107 line length. 108 Default: 80 109 comments: Whether to preserve comments in the output SQL code. 110 Default: True 111 """ 112 113 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 114 **JSON_PATH_PART_TRANSFORMS, 115 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 116 exp.AllowedValuesProperty: lambda self, 117 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 118 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 119 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 120 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 121 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 122 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 123 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 124 exp.CaseSpecificColumnConstraint: lambda _, 125 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 126 exp.Ceil: lambda self, e: self.ceil_floor(e), 127 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 128 exp.CharacterSetProperty: lambda self, 129 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 130 exp.ClusteredColumnConstraint: lambda self, 131 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 132 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 133 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 134 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 135 exp.ConvertToCharset: lambda self, e: self.func( 136 "CONVERT", e.this, e.args["dest"], e.args.get("source") 137 ), 138 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 139 exp.CredentialsProperty: lambda self, 140 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 141 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 142 exp.SessionUser: lambda *_: "SESSION_USER", 143 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 144 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 145 exp.DynamicProperty: lambda *_: "DYNAMIC", 146 exp.EmptyProperty: lambda *_: "EMPTY", 147 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 148 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 149 exp.EphemeralColumnConstraint: lambda self, 150 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 151 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 152 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 153 exp.Except: lambda self, e: self.set_operations(e), 154 exp.ExternalProperty: lambda *_: "EXTERNAL", 155 exp.Floor: lambda self, e: self.ceil_floor(e), 156 exp.Get: lambda self, e: self.get_put_sql(e), 157 exp.GlobalProperty: lambda *_: "GLOBAL", 158 exp.HeapProperty: lambda *_: "HEAP", 159 exp.IcebergProperty: lambda *_: "ICEBERG", 160 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 161 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 162 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 163 exp.Intersect: lambda self, e: self.set_operations(e), 164 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 165 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 166 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 167 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 168 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 169 exp.LanguageProperty: lambda self, e: self.naked_property(e), 170 exp.LocationProperty: lambda self, e: self.naked_property(e), 171 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 172 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 173 exp.NonClusteredColumnConstraint: lambda self, 174 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 175 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 176 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 177 exp.OnCommitProperty: lambda _, 178 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 179 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 180 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 181 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 182 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 183 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 184 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 185 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 186 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 187 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 188 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 189 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 190 exp.ProjectionPolicyColumnConstraint: lambda self, 191 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 192 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 193 exp.Put: lambda self, e: self.get_put_sql(e), 194 exp.RemoteWithConnectionModelProperty: lambda self, 195 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 196 exp.ReturnsProperty: lambda self, e: ( 197 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 198 ), 199 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 200 exp.SecureProperty: lambda *_: "SECURE", 201 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 202 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 203 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 204 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 205 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 206 exp.SqlReadWriteProperty: lambda _, e: e.name, 207 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 208 exp.StabilityProperty: lambda _, e: e.name, 209 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 210 exp.StreamingTableProperty: lambda *_: "STREAMING", 211 exp.StrictProperty: lambda *_: "STRICT", 212 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 213 exp.TableColumn: lambda self, e: self.sql(e.this), 214 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 215 exp.TemporaryProperty: lambda *_: "TEMPORARY", 216 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 217 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 218 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 219 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 220 exp.TransientProperty: lambda *_: "TRANSIENT", 221 exp.Union: lambda self, e: self.set_operations(e), 222 exp.UnloggedProperty: lambda *_: "UNLOGGED", 223 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 224 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 225 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 226 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 227 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 228 exp.UtcTimestamp: lambda self, e: self.sql( 229 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 230 ), 231 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 232 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 233 exp.VolatileProperty: lambda *_: "VOLATILE", 234 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 235 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 236 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 237 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 238 exp.ForceProperty: lambda *_: "FORCE", 239 } 240 241 # Whether null ordering is supported in order by 242 # True: Full Support, None: No support, False: No support for certain cases 243 # such as window specifications, aggregate functions etc 244 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 245 246 # Whether ignore nulls is inside the agg or outside. 247 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 248 IGNORE_NULLS_IN_FUNC = False 249 250 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 251 LOCKING_READS_SUPPORTED = False 252 253 # Whether the EXCEPT and INTERSECT operations can return duplicates 254 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 255 256 # Wrap derived values in parens, usually standard but spark doesn't support it 257 WRAP_DERIVED_VALUES = True 258 259 # Whether create function uses an AS before the RETURN 260 CREATE_FUNCTION_RETURN_AS = True 261 262 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 263 MATCHED_BY_SOURCE = True 264 265 # Whether the INTERVAL expression works only with values like '1 day' 266 SINGLE_STRING_INTERVAL = False 267 268 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 269 INTERVAL_ALLOWS_PLURAL_FORM = True 270 271 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 272 LIMIT_FETCH = "ALL" 273 274 # Whether limit and fetch allows expresions or just limits 275 LIMIT_ONLY_LITERALS = False 276 277 # Whether a table is allowed to be renamed with a db 278 RENAME_TABLE_WITH_DB = True 279 280 # The separator for grouping sets and rollups 281 GROUPINGS_SEP = "," 282 283 # The string used for creating an index on a table 284 INDEX_ON = "ON" 285 286 # Whether join hints should be generated 287 JOIN_HINTS = True 288 289 # Whether table hints should be generated 290 TABLE_HINTS = True 291 292 # Whether query hints should be generated 293 QUERY_HINTS = True 294 295 # What kind of separator to use for query hints 296 QUERY_HINT_SEP = ", " 297 298 # Whether comparing against booleans (e.g. x IS TRUE) is supported 299 IS_BOOL_ALLOWED = True 300 301 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 302 DUPLICATE_KEY_UPDATE_WITH_SET = True 303 304 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 305 LIMIT_IS_TOP = False 306 307 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 308 RETURNING_END = True 309 310 # Whether to generate an unquoted value for EXTRACT's date part argument 311 EXTRACT_ALLOWS_QUOTES = True 312 313 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 314 TZ_TO_WITH_TIME_ZONE = False 315 316 # Whether the NVL2 function is supported 317 NVL2_SUPPORTED = True 318 319 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 320 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 321 322 # Whether VALUES statements can be used as derived tables. 323 # MySQL 5 and Redshift do not allow this, so when False, it will convert 324 # SELECT * VALUES into SELECT UNION 325 VALUES_AS_TABLE = True 326 327 # Whether the word COLUMN is included when adding a column with ALTER TABLE 328 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 329 330 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 331 UNNEST_WITH_ORDINALITY = True 332 333 # Whether FILTER (WHERE cond) can be used for conditional aggregation 334 AGGREGATE_FILTER_SUPPORTED = True 335 336 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 337 SEMI_ANTI_JOIN_WITH_SIDE = True 338 339 # Whether to include the type of a computed column in the CREATE DDL 340 COMPUTED_COLUMN_WITH_TYPE = True 341 342 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 343 SUPPORTS_TABLE_COPY = True 344 345 # Whether parentheses are required around the table sample's expression 346 TABLESAMPLE_REQUIRES_PARENS = True 347 348 # Whether a table sample clause's size needs to be followed by the ROWS keyword 349 TABLESAMPLE_SIZE_IS_ROWS = True 350 351 # The keyword(s) to use when generating a sample clause 352 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 353 354 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 355 TABLESAMPLE_WITH_METHOD = True 356 357 # The keyword to use when specifying the seed of a sample clause 358 TABLESAMPLE_SEED_KEYWORD = "SEED" 359 360 # Whether COLLATE is a function instead of a binary operator 361 COLLATE_IS_FUNC = False 362 363 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 364 DATA_TYPE_SPECIFIERS_ALLOWED = False 365 366 # Whether conditions require booleans WHERE x = 0 vs WHERE x 367 ENSURE_BOOLS = False 368 369 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 370 CTE_RECURSIVE_KEYWORD_REQUIRED = True 371 372 # Whether CONCAT requires >1 arguments 373 SUPPORTS_SINGLE_ARG_CONCAT = True 374 375 # Whether LAST_DAY function supports a date part argument 376 LAST_DAY_SUPPORTS_DATE_PART = True 377 378 # Whether named columns are allowed in table aliases 379 SUPPORTS_TABLE_ALIAS_COLUMNS = True 380 381 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 382 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 383 384 # What delimiter to use for separating JSON key/value pairs 385 JSON_KEY_VALUE_PAIR_SEP = ":" 386 387 # INSERT OVERWRITE TABLE x override 388 INSERT_OVERWRITE = " OVERWRITE TABLE" 389 390 # Whether the SELECT .. INTO syntax is used instead of CTAS 391 SUPPORTS_SELECT_INTO = False 392 393 # Whether UNLOGGED tables can be created 394 SUPPORTS_UNLOGGED_TABLES = False 395 396 # Whether the CREATE TABLE LIKE statement is supported 397 SUPPORTS_CREATE_TABLE_LIKE = True 398 399 # Whether the LikeProperty needs to be specified inside of the schema clause 400 LIKE_PROPERTY_INSIDE_SCHEMA = False 401 402 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 403 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 404 MULTI_ARG_DISTINCT = True 405 406 # Whether the JSON extraction operators expect a value of type JSON 407 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 408 409 # Whether bracketed keys like ["foo"] are supported in JSON paths 410 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 411 412 # Whether to escape keys using single quotes in JSON paths 413 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 414 415 # The JSONPathPart expressions supported by this dialect 416 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 417 418 # Whether any(f(x) for x in array) can be implemented by this dialect 419 CAN_IMPLEMENT_ARRAY_ANY = False 420 421 # Whether the function TO_NUMBER is supported 422 SUPPORTS_TO_NUMBER = True 423 424 # Whether EXCLUDE in window specification is supported 425 SUPPORTS_WINDOW_EXCLUDE = False 426 427 # Whether or not set op modifiers apply to the outer set op or select. 428 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 429 # True means limit 1 happens after the set op, False means it it happens on y. 430 SET_OP_MODIFIERS = True 431 432 # Whether parameters from COPY statement are wrapped in parentheses 433 COPY_PARAMS_ARE_WRAPPED = True 434 435 # Whether values of params are set with "=" token or empty space 436 COPY_PARAMS_EQ_REQUIRED = False 437 438 # Whether COPY statement has INTO keyword 439 COPY_HAS_INTO_KEYWORD = True 440 441 # Whether the conditional TRY(expression) function is supported 442 TRY_SUPPORTED = True 443 444 # Whether the UESCAPE syntax in unicode strings is supported 445 SUPPORTS_UESCAPE = True 446 447 # Function used to replace escaped unicode codes in unicode strings 448 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 449 450 # The keyword to use when generating a star projection with excluded columns 451 STAR_EXCEPT = "EXCEPT" 452 453 # The HEX function name 454 HEX_FUNC = "HEX" 455 456 # The keywords to use when prefixing & separating WITH based properties 457 WITH_PROPERTIES_PREFIX = "WITH" 458 459 # Whether to quote the generated expression of exp.JsonPath 460 QUOTE_JSON_PATH = True 461 462 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 463 PAD_FILL_PATTERN_IS_REQUIRED = False 464 465 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 466 SUPPORTS_EXPLODING_PROJECTIONS = True 467 468 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 469 ARRAY_CONCAT_IS_VAR_LEN = True 470 471 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 472 SUPPORTS_CONVERT_TIMEZONE = False 473 474 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 475 SUPPORTS_MEDIAN = True 476 477 # Whether UNIX_SECONDS(timestamp) is supported 478 SUPPORTS_UNIX_SECONDS = False 479 480 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 481 ALTER_SET_WRAPPED = False 482 483 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 484 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 485 # TODO: The normalization should be done by default once we've tested it across all dialects. 486 NORMALIZE_EXTRACT_DATE_PARTS = False 487 488 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 489 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 490 491 # The function name of the exp.ArraySize expression 492 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 493 494 # The syntax to use when altering the type of a column 495 ALTER_SET_TYPE = "SET DATA TYPE" 496 497 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 498 # None -> Doesn't support it at all 499 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 500 # True (Postgres) -> Explicitly requires it 501 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 502 503 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 504 SUPPORTS_DECODE_CASE = True 505 506 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 507 SUPPORTS_BETWEEN_FLAGS = False 508 509 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 510 SUPPORTS_LIKE_QUANTIFIERS = True 511 512 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 513 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 514 515 # Whether to include the VARIABLE keyword for SET assignments 516 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 517 518 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 519 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 520 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 521 UPDATE_STATEMENT_SUPPORTS_FROM = True 522 523 TYPE_MAPPING = { 524 exp.DataType.Type.DATETIME2: "TIMESTAMP", 525 exp.DataType.Type.NCHAR: "CHAR", 526 exp.DataType.Type.NVARCHAR: "VARCHAR", 527 exp.DataType.Type.MEDIUMTEXT: "TEXT", 528 exp.DataType.Type.LONGTEXT: "TEXT", 529 exp.DataType.Type.TINYTEXT: "TEXT", 530 exp.DataType.Type.BLOB: "VARBINARY", 531 exp.DataType.Type.MEDIUMBLOB: "BLOB", 532 exp.DataType.Type.LONGBLOB: "BLOB", 533 exp.DataType.Type.TINYBLOB: "BLOB", 534 exp.DataType.Type.INET: "INET", 535 exp.DataType.Type.ROWVERSION: "VARBINARY", 536 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 537 } 538 539 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 540 541 TIME_PART_SINGULARS = { 542 "MICROSECONDS": "MICROSECOND", 543 "SECONDS": "SECOND", 544 "MINUTES": "MINUTE", 545 "HOURS": "HOUR", 546 "DAYS": "DAY", 547 "WEEKS": "WEEK", 548 "MONTHS": "MONTH", 549 "QUARTERS": "QUARTER", 550 "YEARS": "YEAR", 551 } 552 553 AFTER_HAVING_MODIFIER_TRANSFORMS = { 554 "cluster": lambda self, e: self.sql(e, "cluster"), 555 "distribute": lambda self, e: self.sql(e, "distribute"), 556 "sort": lambda self, e: self.sql(e, "sort"), 557 "windows": lambda self, e: ( 558 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 559 if e.args.get("windows") 560 else "" 561 ), 562 "qualify": lambda self, e: self.sql(e, "qualify"), 563 } 564 565 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 566 567 STRUCT_DELIMITER = ("<", ">") 568 569 PARAMETER_TOKEN = "@" 570 NAMED_PLACEHOLDER_TOKEN = ":" 571 572 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 573 574 PROPERTIES_LOCATION = { 575 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 577 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 579 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 581 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 583 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 586 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 590 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 592 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 593 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 594 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 595 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 596 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 599 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 600 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 603 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 604 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 605 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 606 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 607 exp.HeapProperty: exp.Properties.Location.POST_WITH, 608 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 610 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 611 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 613 exp.JournalProperty: exp.Properties.Location.POST_NAME, 614 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 619 exp.LogProperty: exp.Properties.Location.POST_NAME, 620 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 621 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 622 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 623 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 625 exp.Order: exp.Properties.Location.POST_SCHEMA, 626 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 628 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 630 exp.Property: exp.Properties.Location.POST_WITH, 631 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 636 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 639 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 640 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 641 exp.Set: exp.Properties.Location.POST_SCHEMA, 642 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.SetProperty: exp.Properties.Location.POST_CREATE, 644 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 645 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 646 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 647 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 648 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 649 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 650 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 651 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 652 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 653 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 654 exp.Tags: exp.Properties.Location.POST_WITH, 655 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 656 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 657 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 658 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 659 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 660 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 661 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 662 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 663 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 664 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 665 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 666 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 667 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 668 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 669 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 670 } 671 672 # Keywords that can't be used as unquoted identifier names 673 RESERVED_KEYWORDS: t.Set[str] = set() 674 675 # Expressions whose comments are separated from them for better formatting 676 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 677 exp.Command, 678 exp.Create, 679 exp.Describe, 680 exp.Delete, 681 exp.Drop, 682 exp.From, 683 exp.Insert, 684 exp.Join, 685 exp.MultitableInserts, 686 exp.Order, 687 exp.Group, 688 exp.Having, 689 exp.Select, 690 exp.SetOperation, 691 exp.Update, 692 exp.Where, 693 exp.With, 694 ) 695 696 # Expressions that should not have their comments generated in maybe_comment 697 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 698 exp.Binary, 699 exp.SetOperation, 700 ) 701 702 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 703 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 704 exp.Column, 705 exp.Literal, 706 exp.Neg, 707 exp.Paren, 708 ) 709 710 PARAMETERIZABLE_TEXT_TYPES = { 711 exp.DataType.Type.NVARCHAR, 712 exp.DataType.Type.VARCHAR, 713 exp.DataType.Type.CHAR, 714 exp.DataType.Type.NCHAR, 715 } 716 717 # Expressions that need to have all CTEs under them bubbled up to them 718 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 719 720 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 721 722 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 723 724 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 725 726 __slots__ = ( 727 "pretty", 728 "identify", 729 "normalize", 730 "pad", 731 "_indent", 732 "normalize_functions", 733 "unsupported_level", 734 "max_unsupported", 735 "leading_comma", 736 "max_text_width", 737 "comments", 738 "dialect", 739 "unsupported_messages", 740 "_escaped_quote_end", 741 "_escaped_byte_quote_end", 742 "_escaped_identifier_end", 743 "_next_name", 744 "_identifier_start", 745 "_identifier_end", 746 "_quote_json_path_key_using_brackets", 747 ) 748 749 def __init__( 750 self, 751 pretty: t.Optional[bool] = None, 752 identify: str | bool = False, 753 normalize: bool = False, 754 pad: int = 2, 755 indent: int = 2, 756 normalize_functions: t.Optional[str | bool] = None, 757 unsupported_level: ErrorLevel = ErrorLevel.WARN, 758 max_unsupported: int = 3, 759 leading_comma: bool = False, 760 max_text_width: int = 80, 761 comments: bool = True, 762 dialect: DialectType = None, 763 ): 764 import sqlglot 765 from sqlglot.dialects import Dialect 766 767 self.pretty = pretty if pretty is not None else sqlglot.pretty 768 self.identify = identify 769 self.normalize = normalize 770 self.pad = pad 771 self._indent = indent 772 self.unsupported_level = unsupported_level 773 self.max_unsupported = max_unsupported 774 self.leading_comma = leading_comma 775 self.max_text_width = max_text_width 776 self.comments = comments 777 self.dialect = Dialect.get_or_raise(dialect) 778 779 # This is both a Dialect property and a Generator argument, so we prioritize the latter 780 self.normalize_functions = ( 781 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 782 ) 783 784 self.unsupported_messages: t.List[str] = [] 785 self._escaped_quote_end: str = ( 786 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 787 ) 788 self._escaped_byte_quote_end: str = ( 789 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 790 if self.dialect.BYTE_END 791 else "" 792 ) 793 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 794 795 self._next_name = name_sequence("_t") 796 797 self._identifier_start = self.dialect.IDENTIFIER_START 798 self._identifier_end = self.dialect.IDENTIFIER_END 799 800 self._quote_json_path_key_using_brackets = True 801 802 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 803 """ 804 Generates the SQL string corresponding to the given syntax tree. 805 806 Args: 807 expression: The syntax tree. 808 copy: Whether to copy the expression. The generator performs mutations so 809 it is safer to copy. 810 811 Returns: 812 The SQL string corresponding to `expression`. 813 """ 814 if copy: 815 expression = expression.copy() 816 817 expression = self.preprocess(expression) 818 819 self.unsupported_messages = [] 820 sql = self.sql(expression).strip() 821 822 if self.pretty: 823 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 824 825 if self.unsupported_level == ErrorLevel.IGNORE: 826 return sql 827 828 if self.unsupported_level == ErrorLevel.WARN: 829 for msg in self.unsupported_messages: 830 logger.warning(msg) 831 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 832 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 833 834 return sql 835 836 def preprocess(self, expression: exp.Expression) -> exp.Expression: 837 """Apply generic preprocessing transformations to a given expression.""" 838 expression = self._move_ctes_to_top_level(expression) 839 840 if self.ENSURE_BOOLS: 841 from sqlglot.transforms import ensure_bools 842 843 expression = ensure_bools(expression) 844 845 return expression 846 847 def _move_ctes_to_top_level(self, expression: E) -> E: 848 if ( 849 not expression.parent 850 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 851 and any(node.parent is not expression for node in expression.find_all(exp.With)) 852 ): 853 from sqlglot.transforms import move_ctes_to_top_level 854 855 expression = move_ctes_to_top_level(expression) 856 return expression 857 858 def unsupported(self, message: str) -> None: 859 if self.unsupported_level == ErrorLevel.IMMEDIATE: 860 raise UnsupportedError(message) 861 self.unsupported_messages.append(message) 862 863 def sep(self, sep: str = " ") -> str: 864 return f"{sep.strip()}\n" if self.pretty else sep 865 866 def seg(self, sql: str, sep: str = " ") -> str: 867 return f"{self.sep(sep)}{sql}" 868 869 def sanitize_comment(self, comment: str) -> str: 870 comment = " " + comment if comment[0].strip() else comment 871 comment = comment + " " if comment[-1].strip() else comment 872 873 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 874 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 875 comment = comment.replace("*/", "* /") 876 877 return comment 878 879 def maybe_comment( 880 self, 881 sql: str, 882 expression: t.Optional[exp.Expression] = None, 883 comments: t.Optional[t.List[str]] = None, 884 separated: bool = False, 885 ) -> str: 886 comments = ( 887 ((expression and expression.comments) if comments is None else comments) # type: ignore 888 if self.comments 889 else None 890 ) 891 892 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 893 return sql 894 895 comments_sql = " ".join( 896 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 897 ) 898 899 if not comments_sql: 900 return sql 901 902 comments_sql = self._replace_line_breaks(comments_sql) 903 904 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 905 return ( 906 f"{self.sep()}{comments_sql}{sql}" 907 if not sql or sql[0].isspace() 908 else f"{comments_sql}{self.sep()}{sql}" 909 ) 910 911 return f"{sql} {comments_sql}" 912 913 def wrap(self, expression: exp.Expression | str) -> str: 914 this_sql = ( 915 self.sql(expression) 916 if isinstance(expression, exp.UNWRAPPED_QUERIES) 917 else self.sql(expression, "this") 918 ) 919 if not this_sql: 920 return "()" 921 922 this_sql = self.indent(this_sql, level=1, pad=0) 923 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 924 925 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 926 original = self.identify 927 self.identify = False 928 result = func(*args, **kwargs) 929 self.identify = original 930 return result 931 932 def normalize_func(self, name: str) -> str: 933 if self.normalize_functions == "upper" or self.normalize_functions is True: 934 return name.upper() 935 if self.normalize_functions == "lower": 936 return name.lower() 937 return name 938 939 def indent( 940 self, 941 sql: str, 942 level: int = 0, 943 pad: t.Optional[int] = None, 944 skip_first: bool = False, 945 skip_last: bool = False, 946 ) -> str: 947 if not self.pretty or not sql: 948 return sql 949 950 pad = self.pad if pad is None else pad 951 lines = sql.split("\n") 952 953 return "\n".join( 954 ( 955 line 956 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 957 else f"{' ' * (level * self._indent + pad)}{line}" 958 ) 959 for i, line in enumerate(lines) 960 ) 961 962 def sql( 963 self, 964 expression: t.Optional[str | exp.Expression], 965 key: t.Optional[str] = None, 966 comment: bool = True, 967 ) -> str: 968 if not expression: 969 return "" 970 971 if isinstance(expression, str): 972 return expression 973 974 if key: 975 value = expression.args.get(key) 976 if value: 977 return self.sql(value) 978 return "" 979 980 transform = self.TRANSFORMS.get(expression.__class__) 981 982 if callable(transform): 983 sql = transform(self, expression) 984 elif isinstance(expression, exp.Expression): 985 exp_handler_name = f"{expression.key}_sql" 986 987 if hasattr(self, exp_handler_name): 988 sql = getattr(self, exp_handler_name)(expression) 989 elif isinstance(expression, exp.Func): 990 sql = self.function_fallback_sql(expression) 991 elif isinstance(expression, exp.Property): 992 sql = self.property_sql(expression) 993 else: 994 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 995 else: 996 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 997 998 return self.maybe_comment(sql, expression) if self.comments and comment else sql 999 1000 def uncache_sql(self, expression: exp.Uncache) -> str: 1001 table = self.sql(expression, "this") 1002 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1003 return f"UNCACHE TABLE{exists_sql} {table}" 1004 1005 def cache_sql(self, expression: exp.Cache) -> str: 1006 lazy = " LAZY" if expression.args.get("lazy") else "" 1007 table = self.sql(expression, "this") 1008 options = expression.args.get("options") 1009 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1010 sql = self.sql(expression, "expression") 1011 sql = f" AS{self.sep()}{sql}" if sql else "" 1012 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1013 return self.prepend_ctes(expression, sql) 1014 1015 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1016 if isinstance(expression.parent, exp.Cast): 1017 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1018 default = "DEFAULT " if expression.args.get("default") else "" 1019 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1020 1021 def column_parts(self, expression: exp.Column) -> str: 1022 return ".".join( 1023 self.sql(part) 1024 for part in ( 1025 expression.args.get("catalog"), 1026 expression.args.get("db"), 1027 expression.args.get("table"), 1028 expression.args.get("this"), 1029 ) 1030 if part 1031 ) 1032 1033 def column_sql(self, expression: exp.Column) -> str: 1034 join_mark = " (+)" if expression.args.get("join_mark") else "" 1035 1036 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1037 join_mark = "" 1038 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1039 1040 return f"{self.column_parts(expression)}{join_mark}" 1041 1042 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1043 return self.column_sql(expression) 1044 1045 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1046 this = self.sql(expression, "this") 1047 this = f" {this}" if this else "" 1048 position = self.sql(expression, "position") 1049 return f"{position}{this}" 1050 1051 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1052 column = self.sql(expression, "this") 1053 kind = self.sql(expression, "kind") 1054 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1055 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1056 kind = f"{sep}{kind}" if kind else "" 1057 constraints = f" {constraints}" if constraints else "" 1058 position = self.sql(expression, "position") 1059 position = f" {position}" if position else "" 1060 1061 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1062 kind = "" 1063 1064 return f"{exists}{column}{kind}{constraints}{position}" 1065 1066 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1067 this = self.sql(expression, "this") 1068 kind_sql = self.sql(expression, "kind").strip() 1069 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1070 1071 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1072 this = self.sql(expression, "this") 1073 if expression.args.get("not_null"): 1074 persisted = " PERSISTED NOT NULL" 1075 elif expression.args.get("persisted"): 1076 persisted = " PERSISTED" 1077 else: 1078 persisted = "" 1079 1080 return f"AS {this}{persisted}" 1081 1082 def autoincrementcolumnconstraint_sql(self, _) -> str: 1083 return self.token_sql(TokenType.AUTO_INCREMENT) 1084 1085 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1086 if isinstance(expression.this, list): 1087 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1088 else: 1089 this = self.sql(expression, "this") 1090 1091 return f"COMPRESS {this}" 1092 1093 def generatedasidentitycolumnconstraint_sql( 1094 self, expression: exp.GeneratedAsIdentityColumnConstraint 1095 ) -> str: 1096 this = "" 1097 if expression.this is not None: 1098 on_null = " ON NULL" if expression.args.get("on_null") else "" 1099 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1100 1101 start = expression.args.get("start") 1102 start = f"START WITH {start}" if start else "" 1103 increment = expression.args.get("increment") 1104 increment = f" INCREMENT BY {increment}" if increment else "" 1105 minvalue = expression.args.get("minvalue") 1106 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1107 maxvalue = expression.args.get("maxvalue") 1108 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1109 cycle = expression.args.get("cycle") 1110 cycle_sql = "" 1111 1112 if cycle is not None: 1113 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1114 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1115 1116 sequence_opts = "" 1117 if start or increment or cycle_sql: 1118 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1119 sequence_opts = f" ({sequence_opts.strip()})" 1120 1121 expr = self.sql(expression, "expression") 1122 expr = f"({expr})" if expr else "IDENTITY" 1123 1124 return f"GENERATED{this} AS {expr}{sequence_opts}" 1125 1126 def generatedasrowcolumnconstraint_sql( 1127 self, expression: exp.GeneratedAsRowColumnConstraint 1128 ) -> str: 1129 start = "START" if expression.args.get("start") else "END" 1130 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1131 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1132 1133 def periodforsystemtimeconstraint_sql( 1134 self, expression: exp.PeriodForSystemTimeConstraint 1135 ) -> str: 1136 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1137 1138 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1139 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1140 1141 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1142 desc = expression.args.get("desc") 1143 if desc is not None: 1144 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1145 options = self.expressions(expression, key="options", flat=True, sep=" ") 1146 options = f" {options}" if options else "" 1147 return f"PRIMARY KEY{options}" 1148 1149 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1150 this = self.sql(expression, "this") 1151 this = f" {this}" if this else "" 1152 index_type = expression.args.get("index_type") 1153 index_type = f" USING {index_type}" if index_type else "" 1154 on_conflict = self.sql(expression, "on_conflict") 1155 on_conflict = f" {on_conflict}" if on_conflict else "" 1156 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1157 options = self.expressions(expression, key="options", flat=True, sep=" ") 1158 options = f" {options}" if options else "" 1159 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1160 1161 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1162 input_ = expression.args.get("input_") 1163 output = expression.args.get("output") 1164 1165 if input_ and output: 1166 return "IN OUT" 1167 if input_: 1168 return "IN" 1169 if output: 1170 return "OUT" 1171 1172 return "" 1173 1174 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1175 return self.sql(expression, "this") 1176 1177 def create_sql(self, expression: exp.Create) -> str: 1178 kind = self.sql(expression, "kind") 1179 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1180 properties = expression.args.get("properties") 1181 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1182 1183 this = self.createable_sql(expression, properties_locs) 1184 1185 properties_sql = "" 1186 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1187 exp.Properties.Location.POST_WITH 1188 ): 1189 props_ast = exp.Properties( 1190 expressions=[ 1191 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1192 *properties_locs[exp.Properties.Location.POST_WITH], 1193 ] 1194 ) 1195 props_ast.parent = expression 1196 properties_sql = self.sql(props_ast) 1197 1198 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1199 properties_sql = self.sep() + properties_sql 1200 elif not self.pretty: 1201 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1202 properties_sql = f" {properties_sql}" 1203 1204 begin = " BEGIN" if expression.args.get("begin") else "" 1205 end = " END" if expression.args.get("end") else "" 1206 1207 expression_sql = self.sql(expression, "expression") 1208 if expression_sql: 1209 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1210 1211 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1212 postalias_props_sql = "" 1213 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1214 postalias_props_sql = self.properties( 1215 exp.Properties( 1216 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1217 ), 1218 wrapped=False, 1219 ) 1220 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1221 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1222 1223 postindex_props_sql = "" 1224 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1225 postindex_props_sql = self.properties( 1226 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1227 wrapped=False, 1228 prefix=" ", 1229 ) 1230 1231 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1232 indexes = f" {indexes}" if indexes else "" 1233 index_sql = indexes + postindex_props_sql 1234 1235 replace = " OR REPLACE" if expression.args.get("replace") else "" 1236 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1237 unique = " UNIQUE" if expression.args.get("unique") else "" 1238 1239 clustered = expression.args.get("clustered") 1240 if clustered is None: 1241 clustered_sql = "" 1242 elif clustered: 1243 clustered_sql = " CLUSTERED COLUMNSTORE" 1244 else: 1245 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1246 1247 postcreate_props_sql = "" 1248 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1249 postcreate_props_sql = self.properties( 1250 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1251 sep=" ", 1252 prefix=" ", 1253 wrapped=False, 1254 ) 1255 1256 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1257 1258 postexpression_props_sql = "" 1259 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1260 postexpression_props_sql = self.properties( 1261 exp.Properties( 1262 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1263 ), 1264 sep=" ", 1265 prefix=" ", 1266 wrapped=False, 1267 ) 1268 1269 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1270 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1271 no_schema_binding = ( 1272 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1273 ) 1274 1275 clone = self.sql(expression, "clone") 1276 clone = f" {clone}" if clone else "" 1277 1278 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1279 properties_expression = f"{expression_sql}{properties_sql}" 1280 else: 1281 properties_expression = f"{properties_sql}{expression_sql}" 1282 1283 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1284 return self.prepend_ctes(expression, expression_sql) 1285 1286 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1287 start = self.sql(expression, "start") 1288 start = f"START WITH {start}" if start else "" 1289 increment = self.sql(expression, "increment") 1290 increment = f" INCREMENT BY {increment}" if increment else "" 1291 minvalue = self.sql(expression, "minvalue") 1292 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1293 maxvalue = self.sql(expression, "maxvalue") 1294 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1295 owned = self.sql(expression, "owned") 1296 owned = f" OWNED BY {owned}" if owned else "" 1297 1298 cache = expression.args.get("cache") 1299 if cache is None: 1300 cache_str = "" 1301 elif cache is True: 1302 cache_str = " CACHE" 1303 else: 1304 cache_str = f" CACHE {cache}" 1305 1306 options = self.expressions(expression, key="options", flat=True, sep=" ") 1307 options = f" {options}" if options else "" 1308 1309 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1310 1311 def clone_sql(self, expression: exp.Clone) -> str: 1312 this = self.sql(expression, "this") 1313 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1314 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1315 return f"{shallow}{keyword} {this}" 1316 1317 def describe_sql(self, expression: exp.Describe) -> str: 1318 style = expression.args.get("style") 1319 style = f" {style}" if style else "" 1320 partition = self.sql(expression, "partition") 1321 partition = f" {partition}" if partition else "" 1322 format = self.sql(expression, "format") 1323 format = f" {format}" if format else "" 1324 1325 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1326 1327 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1328 tag = self.sql(expression, "tag") 1329 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1330 1331 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1332 with_ = self.sql(expression, "with_") 1333 if with_: 1334 sql = f"{with_}{self.sep()}{sql}" 1335 return sql 1336 1337 def with_sql(self, expression: exp.With) -> str: 1338 sql = self.expressions(expression, flat=True) 1339 recursive = ( 1340 "RECURSIVE " 1341 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1342 else "" 1343 ) 1344 search = self.sql(expression, "search") 1345 search = f" {search}" if search else "" 1346 1347 return f"WITH {recursive}{sql}{search}" 1348 1349 def cte_sql(self, expression: exp.CTE) -> str: 1350 alias = expression.args.get("alias") 1351 if alias: 1352 alias.add_comments(expression.pop_comments()) 1353 1354 alias_sql = self.sql(expression, "alias") 1355 1356 materialized = expression.args.get("materialized") 1357 if materialized is False: 1358 materialized = "NOT MATERIALIZED " 1359 elif materialized: 1360 materialized = "MATERIALIZED " 1361 1362 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1363 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1364 1365 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1366 1367 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1368 alias = self.sql(expression, "this") 1369 columns = self.expressions(expression, key="columns", flat=True) 1370 columns = f"({columns})" if columns else "" 1371 1372 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1373 columns = "" 1374 self.unsupported("Named columns are not supported in table alias.") 1375 1376 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1377 alias = self._next_name() 1378 1379 return f"{alias}{columns}" 1380 1381 def bitstring_sql(self, expression: exp.BitString) -> str: 1382 this = self.sql(expression, "this") 1383 if self.dialect.BIT_START: 1384 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1385 return f"{int(this, 2)}" 1386 1387 def hexstring_sql( 1388 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1389 ) -> str: 1390 this = self.sql(expression, "this") 1391 is_integer_type = expression.args.get("is_integer") 1392 1393 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1394 not self.dialect.HEX_START and not binary_function_repr 1395 ): 1396 # Integer representation will be returned if: 1397 # - The read dialect treats the hex value as integer literal but not the write 1398 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1399 return f"{int(this, 16)}" 1400 1401 if not is_integer_type: 1402 # Read dialect treats the hex value as BINARY/BLOB 1403 if binary_function_repr: 1404 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1405 return self.func(binary_function_repr, exp.Literal.string(this)) 1406 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1407 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1408 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1409 1410 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1411 1412 def bytestring_sql(self, expression: exp.ByteString) -> str: 1413 this = self.sql(expression, "this") 1414 if self.dialect.BYTE_START: 1415 escaped_byte_string = self.escape_str( 1416 this, 1417 escape_backslash=False, 1418 delimiter=self.dialect.BYTE_END, 1419 escaped_delimiter=self._escaped_byte_quote_end, 1420 ) 1421 is_bytes = expression.args.get("is_bytes", False) 1422 delimited_byte_string = ( 1423 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1424 ) 1425 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1426 return self.sql( 1427 exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect) 1428 ) 1429 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1430 return self.sql( 1431 exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect) 1432 ) 1433 1434 return delimited_byte_string 1435 return this 1436 1437 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1438 this = self.sql(expression, "this") 1439 escape = expression.args.get("escape") 1440 1441 if self.dialect.UNICODE_START: 1442 escape_substitute = r"\\\1" 1443 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1444 else: 1445 escape_substitute = r"\\u\1" 1446 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1447 1448 if escape: 1449 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1450 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1451 else: 1452 escape_pattern = ESCAPED_UNICODE_RE 1453 escape_sql = "" 1454 1455 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1456 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1457 1458 return f"{left_quote}{this}{right_quote}{escape_sql}" 1459 1460 def rawstring_sql(self, expression: exp.RawString) -> str: 1461 string = expression.this 1462 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1463 string = string.replace("\\", "\\\\") 1464 1465 string = self.escape_str(string, escape_backslash=False) 1466 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1467 1468 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1469 this = self.sql(expression, "this") 1470 specifier = self.sql(expression, "expression") 1471 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1472 return f"{this}{specifier}" 1473 1474 def datatype_sql(self, expression: exp.DataType) -> str: 1475 nested = "" 1476 values = "" 1477 1478 expr_nested = expression.args.get("nested") 1479 interior = ( 1480 self.expressions( 1481 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1482 ) 1483 if expr_nested and self.pretty 1484 else self.expressions(expression, flat=True) 1485 ) 1486 1487 type_value = expression.this 1488 if type_value in self.UNSUPPORTED_TYPES: 1489 self.unsupported( 1490 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1491 ) 1492 1493 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1494 type_sql = self.sql(expression, "kind") 1495 else: 1496 type_sql = ( 1497 self.TYPE_MAPPING.get(type_value, type_value.value) 1498 if isinstance(type_value, exp.DataType.Type) 1499 else type_value 1500 ) 1501 1502 if interior: 1503 if expr_nested: 1504 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1505 if expression.args.get("values") is not None: 1506 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1507 values = self.expressions(expression, key="values", flat=True) 1508 values = f"{delimiters[0]}{values}{delimiters[1]}" 1509 elif type_value == exp.DataType.Type.INTERVAL: 1510 nested = f" {interior}" 1511 else: 1512 nested = f"({interior})" 1513 1514 type_sql = f"{type_sql}{nested}{values}" 1515 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1516 exp.DataType.Type.TIMETZ, 1517 exp.DataType.Type.TIMESTAMPTZ, 1518 ): 1519 type_sql = f"{type_sql} WITH TIME ZONE" 1520 1521 return type_sql 1522 1523 def directory_sql(self, expression: exp.Directory) -> str: 1524 local = "LOCAL " if expression.args.get("local") else "" 1525 row_format = self.sql(expression, "row_format") 1526 row_format = f" {row_format}" if row_format else "" 1527 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1528 1529 def delete_sql(self, expression: exp.Delete) -> str: 1530 this = self.sql(expression, "this") 1531 this = f" FROM {this}" if this else "" 1532 using = self.expressions(expression, key="using") 1533 using = f" USING {using}" if using else "" 1534 cluster = self.sql(expression, "cluster") 1535 cluster = f" {cluster}" if cluster else "" 1536 where = self.sql(expression, "where") 1537 returning = self.sql(expression, "returning") 1538 order = self.sql(expression, "order") 1539 limit = self.sql(expression, "limit") 1540 tables = self.expressions(expression, key="tables") 1541 tables = f" {tables}" if tables else "" 1542 if self.RETURNING_END: 1543 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1544 else: 1545 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1546 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1547 1548 def drop_sql(self, expression: exp.Drop) -> str: 1549 this = self.sql(expression, "this") 1550 expressions = self.expressions(expression, flat=True) 1551 expressions = f" ({expressions})" if expressions else "" 1552 kind = expression.args["kind"] 1553 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1554 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1555 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1556 on_cluster = self.sql(expression, "cluster") 1557 on_cluster = f" {on_cluster}" if on_cluster else "" 1558 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1559 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1560 cascade = " CASCADE" if expression.args.get("cascade") else "" 1561 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1562 purge = " PURGE" if expression.args.get("purge") else "" 1563 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1564 1565 def set_operation(self, expression: exp.SetOperation) -> str: 1566 op_type = type(expression) 1567 op_name = op_type.key.upper() 1568 1569 distinct = expression.args.get("distinct") 1570 if ( 1571 distinct is False 1572 and op_type in (exp.Except, exp.Intersect) 1573 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1574 ): 1575 self.unsupported(f"{op_name} ALL is not supported") 1576 1577 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1578 1579 if distinct is None: 1580 distinct = default_distinct 1581 if distinct is None: 1582 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1583 1584 if distinct is default_distinct: 1585 distinct_or_all = "" 1586 else: 1587 distinct_or_all = " DISTINCT" if distinct else " ALL" 1588 1589 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1590 side_kind = f"{side_kind} " if side_kind else "" 1591 1592 by_name = " BY NAME" if expression.args.get("by_name") else "" 1593 on = self.expressions(expression, key="on", flat=True) 1594 on = f" ON ({on})" if on else "" 1595 1596 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1597 1598 def set_operations(self, expression: exp.SetOperation) -> str: 1599 if not self.SET_OP_MODIFIERS: 1600 limit = expression.args.get("limit") 1601 order = expression.args.get("order") 1602 1603 if limit or order: 1604 select = self._move_ctes_to_top_level( 1605 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1606 ) 1607 1608 if limit: 1609 select = select.limit(limit.pop(), copy=False) 1610 if order: 1611 select = select.order_by(order.pop(), copy=False) 1612 return self.sql(select) 1613 1614 sqls: t.List[str] = [] 1615 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1616 1617 while stack: 1618 node = stack.pop() 1619 1620 if isinstance(node, exp.SetOperation): 1621 stack.append(node.expression) 1622 stack.append( 1623 self.maybe_comment( 1624 self.set_operation(node), comments=node.comments, separated=True 1625 ) 1626 ) 1627 stack.append(node.this) 1628 else: 1629 sqls.append(self.sql(node)) 1630 1631 this = self.sep().join(sqls) 1632 this = self.query_modifiers(expression, this) 1633 return self.prepend_ctes(expression, this) 1634 1635 def fetch_sql(self, expression: exp.Fetch) -> str: 1636 direction = expression.args.get("direction") 1637 direction = f" {direction}" if direction else "" 1638 count = self.sql(expression, "count") 1639 count = f" {count}" if count else "" 1640 limit_options = self.sql(expression, "limit_options") 1641 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1642 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1643 1644 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1645 percent = " PERCENT" if expression.args.get("percent") else "" 1646 rows = " ROWS" if expression.args.get("rows") else "" 1647 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1648 if not with_ties and rows: 1649 with_ties = " ONLY" 1650 return f"{percent}{rows}{with_ties}" 1651 1652 def filter_sql(self, expression: exp.Filter) -> str: 1653 if self.AGGREGATE_FILTER_SUPPORTED: 1654 this = self.sql(expression, "this") 1655 where = self.sql(expression, "expression").strip() 1656 return f"{this} FILTER({where})" 1657 1658 agg = expression.this 1659 agg_arg = agg.this 1660 cond = expression.expression.this 1661 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1662 return self.sql(agg) 1663 1664 def hint_sql(self, expression: exp.Hint) -> str: 1665 if not self.QUERY_HINTS: 1666 self.unsupported("Hints are not supported") 1667 return "" 1668 1669 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1670 1671 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1672 using = self.sql(expression, "using") 1673 using = f" USING {using}" if using else "" 1674 columns = self.expressions(expression, key="columns", flat=True) 1675 columns = f"({columns})" if columns else "" 1676 partition_by = self.expressions(expression, key="partition_by", flat=True) 1677 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1678 where = self.sql(expression, "where") 1679 include = self.expressions(expression, key="include", flat=True) 1680 if include: 1681 include = f" INCLUDE ({include})" 1682 with_storage = self.expressions(expression, key="with_storage", flat=True) 1683 with_storage = f" WITH ({with_storage})" if with_storage else "" 1684 tablespace = self.sql(expression, "tablespace") 1685 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1686 on = self.sql(expression, "on") 1687 on = f" ON {on}" if on else "" 1688 1689 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1690 1691 def index_sql(self, expression: exp.Index) -> str: 1692 unique = "UNIQUE " if expression.args.get("unique") else "" 1693 primary = "PRIMARY " if expression.args.get("primary") else "" 1694 amp = "AMP " if expression.args.get("amp") else "" 1695 name = self.sql(expression, "this") 1696 name = f"{name} " if name else "" 1697 table = self.sql(expression, "table") 1698 table = f"{self.INDEX_ON} {table}" if table else "" 1699 1700 index = "INDEX " if not table else "" 1701 1702 params = self.sql(expression, "params") 1703 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1704 1705 def identifier_sql(self, expression: exp.Identifier) -> str: 1706 text = expression.name 1707 lower = text.lower() 1708 text = lower if self.normalize and not expression.quoted else text 1709 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1710 if ( 1711 expression.quoted 1712 or self.dialect.can_quote(expression, self.identify) 1713 or lower in self.RESERVED_KEYWORDS 1714 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1715 ): 1716 text = f"{self._identifier_start}{text}{self._identifier_end}" 1717 return text 1718 1719 def hex_sql(self, expression: exp.Hex) -> str: 1720 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1721 if self.dialect.HEX_LOWERCASE: 1722 text = self.func("LOWER", text) 1723 1724 return text 1725 1726 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1727 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1728 if not self.dialect.HEX_LOWERCASE: 1729 text = self.func("LOWER", text) 1730 return text 1731 1732 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1733 input_format = self.sql(expression, "input_format") 1734 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1735 output_format = self.sql(expression, "output_format") 1736 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1737 return self.sep().join((input_format, output_format)) 1738 1739 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1740 string = self.sql(exp.Literal.string(expression.name)) 1741 return f"{prefix}{string}" 1742 1743 def partition_sql(self, expression: exp.Partition) -> str: 1744 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1745 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1746 1747 def properties_sql(self, expression: exp.Properties) -> str: 1748 root_properties = [] 1749 with_properties = [] 1750 1751 for p in expression.expressions: 1752 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1753 if p_loc == exp.Properties.Location.POST_WITH: 1754 with_properties.append(p) 1755 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1756 root_properties.append(p) 1757 1758 root_props_ast = exp.Properties(expressions=root_properties) 1759 root_props_ast.parent = expression.parent 1760 1761 with_props_ast = exp.Properties(expressions=with_properties) 1762 with_props_ast.parent = expression.parent 1763 1764 root_props = self.root_properties(root_props_ast) 1765 with_props = self.with_properties(with_props_ast) 1766 1767 if root_props and with_props and not self.pretty: 1768 with_props = " " + with_props 1769 1770 return root_props + with_props 1771 1772 def root_properties(self, properties: exp.Properties) -> str: 1773 if properties.expressions: 1774 return self.expressions(properties, indent=False, sep=" ") 1775 return "" 1776 1777 def properties( 1778 self, 1779 properties: exp.Properties, 1780 prefix: str = "", 1781 sep: str = ", ", 1782 suffix: str = "", 1783 wrapped: bool = True, 1784 ) -> str: 1785 if properties.expressions: 1786 expressions = self.expressions(properties, sep=sep, indent=False) 1787 if expressions: 1788 expressions = self.wrap(expressions) if wrapped else expressions 1789 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1790 return "" 1791 1792 def with_properties(self, properties: exp.Properties) -> str: 1793 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1794 1795 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1796 properties_locs = defaultdict(list) 1797 for p in properties.expressions: 1798 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1799 if p_loc != exp.Properties.Location.UNSUPPORTED: 1800 properties_locs[p_loc].append(p) 1801 else: 1802 self.unsupported(f"Unsupported property {p.key}") 1803 1804 return properties_locs 1805 1806 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1807 if isinstance(expression.this, exp.Dot): 1808 return self.sql(expression, "this") 1809 return f"'{expression.name}'" if string_key else expression.name 1810 1811 def property_sql(self, expression: exp.Property) -> str: 1812 property_cls = expression.__class__ 1813 if property_cls == exp.Property: 1814 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1815 1816 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1817 if not property_name: 1818 self.unsupported(f"Unsupported property {expression.key}") 1819 1820 return f"{property_name}={self.sql(expression, 'this')}" 1821 1822 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1823 if self.SUPPORTS_CREATE_TABLE_LIKE: 1824 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1825 options = f" {options}" if options else "" 1826 1827 like = f"LIKE {self.sql(expression, 'this')}{options}" 1828 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1829 like = f"({like})" 1830 1831 return like 1832 1833 if expression.expressions: 1834 self.unsupported("Transpilation of LIKE property options is unsupported") 1835 1836 select = exp.select("*").from_(expression.this).limit(0) 1837 return f"AS {self.sql(select)}" 1838 1839 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1840 no = "NO " if expression.args.get("no") else "" 1841 protection = " PROTECTION" if expression.args.get("protection") else "" 1842 return f"{no}FALLBACK{protection}" 1843 1844 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1845 no = "NO " if expression.args.get("no") else "" 1846 local = expression.args.get("local") 1847 local = f"{local} " if local else "" 1848 dual = "DUAL " if expression.args.get("dual") else "" 1849 before = "BEFORE " if expression.args.get("before") else "" 1850 after = "AFTER " if expression.args.get("after") else "" 1851 return f"{no}{local}{dual}{before}{after}JOURNAL" 1852 1853 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1854 freespace = self.sql(expression, "this") 1855 percent = " PERCENT" if expression.args.get("percent") else "" 1856 return f"FREESPACE={freespace}{percent}" 1857 1858 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1859 if expression.args.get("default"): 1860 property = "DEFAULT" 1861 elif expression.args.get("on"): 1862 property = "ON" 1863 else: 1864 property = "OFF" 1865 return f"CHECKSUM={property}" 1866 1867 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1868 if expression.args.get("no"): 1869 return "NO MERGEBLOCKRATIO" 1870 if expression.args.get("default"): 1871 return "DEFAULT MERGEBLOCKRATIO" 1872 1873 percent = " PERCENT" if expression.args.get("percent") else "" 1874 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1875 1876 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1877 default = expression.args.get("default") 1878 minimum = expression.args.get("minimum") 1879 maximum = expression.args.get("maximum") 1880 if default or minimum or maximum: 1881 if default: 1882 prop = "DEFAULT" 1883 elif minimum: 1884 prop = "MINIMUM" 1885 else: 1886 prop = "MAXIMUM" 1887 return f"{prop} DATABLOCKSIZE" 1888 units = expression.args.get("units") 1889 units = f" {units}" if units else "" 1890 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1891 1892 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1893 autotemp = expression.args.get("autotemp") 1894 always = expression.args.get("always") 1895 default = expression.args.get("default") 1896 manual = expression.args.get("manual") 1897 never = expression.args.get("never") 1898 1899 if autotemp is not None: 1900 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1901 elif always: 1902 prop = "ALWAYS" 1903 elif default: 1904 prop = "DEFAULT" 1905 elif manual: 1906 prop = "MANUAL" 1907 elif never: 1908 prop = "NEVER" 1909 return f"BLOCKCOMPRESSION={prop}" 1910 1911 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1912 no = expression.args.get("no") 1913 no = " NO" if no else "" 1914 concurrent = expression.args.get("concurrent") 1915 concurrent = " CONCURRENT" if concurrent else "" 1916 target = self.sql(expression, "target") 1917 target = f" {target}" if target else "" 1918 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1919 1920 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1921 if isinstance(expression.this, list): 1922 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1923 if expression.this: 1924 modulus = self.sql(expression, "this") 1925 remainder = self.sql(expression, "expression") 1926 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1927 1928 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1929 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1930 return f"FROM ({from_expressions}) TO ({to_expressions})" 1931 1932 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1933 this = self.sql(expression, "this") 1934 1935 for_values_or_default = expression.expression 1936 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1937 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1938 else: 1939 for_values_or_default = " DEFAULT" 1940 1941 return f"PARTITION OF {this}{for_values_or_default}" 1942 1943 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1944 kind = expression.args.get("kind") 1945 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1946 for_or_in = expression.args.get("for_or_in") 1947 for_or_in = f" {for_or_in}" if for_or_in else "" 1948 lock_type = expression.args.get("lock_type") 1949 override = " OVERRIDE" if expression.args.get("override") else "" 1950 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1951 1952 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1953 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1954 statistics = expression.args.get("statistics") 1955 statistics_sql = "" 1956 if statistics is not None: 1957 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1958 return f"{data_sql}{statistics_sql}" 1959 1960 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1961 this = self.sql(expression, "this") 1962 this = f"HISTORY_TABLE={this}" if this else "" 1963 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1964 data_consistency = ( 1965 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1966 ) 1967 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1968 retention_period = ( 1969 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1970 ) 1971 1972 if this: 1973 on_sql = self.func("ON", this, data_consistency, retention_period) 1974 else: 1975 on_sql = "ON" if expression.args.get("on") else "OFF" 1976 1977 sql = f"SYSTEM_VERSIONING={on_sql}" 1978 1979 return f"WITH({sql})" if expression.args.get("with_") else sql 1980 1981 def insert_sql(self, expression: exp.Insert) -> str: 1982 hint = self.sql(expression, "hint") 1983 overwrite = expression.args.get("overwrite") 1984 1985 if isinstance(expression.this, exp.Directory): 1986 this = " OVERWRITE" if overwrite else " INTO" 1987 else: 1988 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1989 1990 stored = self.sql(expression, "stored") 1991 stored = f" {stored}" if stored else "" 1992 alternative = expression.args.get("alternative") 1993 alternative = f" OR {alternative}" if alternative else "" 1994 ignore = " IGNORE" if expression.args.get("ignore") else "" 1995 is_function = expression.args.get("is_function") 1996 if is_function: 1997 this = f"{this} FUNCTION" 1998 this = f"{this} {self.sql(expression, 'this')}" 1999 2000 exists = " IF EXISTS" if expression.args.get("exists") else "" 2001 where = self.sql(expression, "where") 2002 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2003 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2004 on_conflict = self.sql(expression, "conflict") 2005 on_conflict = f" {on_conflict}" if on_conflict else "" 2006 by_name = " BY NAME" if expression.args.get("by_name") else "" 2007 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2008 returning = self.sql(expression, "returning") 2009 2010 if self.RETURNING_END: 2011 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2012 else: 2013 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2014 2015 partition_by = self.sql(expression, "partition") 2016 partition_by = f" {partition_by}" if partition_by else "" 2017 settings = self.sql(expression, "settings") 2018 settings = f" {settings}" if settings else "" 2019 2020 source = self.sql(expression, "source") 2021 source = f"TABLE {source}" if source else "" 2022 2023 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2024 return self.prepend_ctes(expression, sql) 2025 2026 def introducer_sql(self, expression: exp.Introducer) -> str: 2027 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2028 2029 def kill_sql(self, expression: exp.Kill) -> str: 2030 kind = self.sql(expression, "kind") 2031 kind = f" {kind}" if kind else "" 2032 this = self.sql(expression, "this") 2033 this = f" {this}" if this else "" 2034 return f"KILL{kind}{this}" 2035 2036 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2037 return expression.name 2038 2039 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2040 return expression.name 2041 2042 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2043 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2044 2045 constraint = self.sql(expression, "constraint") 2046 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2047 2048 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2049 if conflict_keys: 2050 conflict_keys = f"({conflict_keys})" 2051 2052 index_predicate = self.sql(expression, "index_predicate") 2053 conflict_keys = f"{conflict_keys}{index_predicate} " 2054 2055 action = self.sql(expression, "action") 2056 2057 expressions = self.expressions(expression, flat=True) 2058 if expressions: 2059 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2060 expressions = f" {set_keyword}{expressions}" 2061 2062 where = self.sql(expression, "where") 2063 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2064 2065 def returning_sql(self, expression: exp.Returning) -> str: 2066 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2067 2068 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2069 fields = self.sql(expression, "fields") 2070 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2071 escaped = self.sql(expression, "escaped") 2072 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2073 items = self.sql(expression, "collection_items") 2074 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2075 keys = self.sql(expression, "map_keys") 2076 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2077 lines = self.sql(expression, "lines") 2078 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2079 null = self.sql(expression, "null") 2080 null = f" NULL DEFINED AS {null}" if null else "" 2081 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2082 2083 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2084 return f"WITH ({self.expressions(expression, flat=True)})" 2085 2086 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2087 this = f"{self.sql(expression, 'this')} INDEX" 2088 target = self.sql(expression, "target") 2089 target = f" FOR {target}" if target else "" 2090 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2091 2092 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2093 this = self.sql(expression, "this") 2094 kind = self.sql(expression, "kind") 2095 expr = self.sql(expression, "expression") 2096 return f"{this} ({kind} => {expr})" 2097 2098 def table_parts(self, expression: exp.Table) -> str: 2099 return ".".join( 2100 self.sql(part) 2101 for part in ( 2102 expression.args.get("catalog"), 2103 expression.args.get("db"), 2104 expression.args.get("this"), 2105 ) 2106 if part is not None 2107 ) 2108 2109 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2110 table = self.table_parts(expression) 2111 only = "ONLY " if expression.args.get("only") else "" 2112 partition = self.sql(expression, "partition") 2113 partition = f" {partition}" if partition else "" 2114 version = self.sql(expression, "version") 2115 version = f" {version}" if version else "" 2116 alias = self.sql(expression, "alias") 2117 alias = f"{sep}{alias}" if alias else "" 2118 2119 sample = self.sql(expression, "sample") 2120 if self.dialect.ALIAS_POST_TABLESAMPLE: 2121 sample_pre_alias = sample 2122 sample_post_alias = "" 2123 else: 2124 sample_pre_alias = "" 2125 sample_post_alias = sample 2126 2127 hints = self.expressions(expression, key="hints", sep=" ") 2128 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2129 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2130 joins = self.indent( 2131 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2132 ) 2133 laterals = self.expressions(expression, key="laterals", sep="") 2134 2135 file_format = self.sql(expression, "format") 2136 if file_format: 2137 pattern = self.sql(expression, "pattern") 2138 pattern = f", PATTERN => {pattern}" if pattern else "" 2139 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2140 2141 ordinality = expression.args.get("ordinality") or "" 2142 if ordinality: 2143 ordinality = f" WITH ORDINALITY{alias}" 2144 alias = "" 2145 2146 when = self.sql(expression, "when") 2147 if when: 2148 table = f"{table} {when}" 2149 2150 changes = self.sql(expression, "changes") 2151 changes = f" {changes}" if changes else "" 2152 2153 rows_from = self.expressions(expression, key="rows_from") 2154 if rows_from: 2155 table = f"ROWS FROM {self.wrap(rows_from)}" 2156 2157 indexed = expression.args.get("indexed") 2158 if indexed is not None: 2159 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2160 else: 2161 indexed = "" 2162 2163 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2164 2165 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2166 table = self.func("TABLE", expression.this) 2167 alias = self.sql(expression, "alias") 2168 alias = f" AS {alias}" if alias else "" 2169 sample = self.sql(expression, "sample") 2170 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2171 joins = self.indent( 2172 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2173 ) 2174 return f"{table}{alias}{pivots}{sample}{joins}" 2175 2176 def tablesample_sql( 2177 self, 2178 expression: exp.TableSample, 2179 tablesample_keyword: t.Optional[str] = None, 2180 ) -> str: 2181 method = self.sql(expression, "method") 2182 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2183 numerator = self.sql(expression, "bucket_numerator") 2184 denominator = self.sql(expression, "bucket_denominator") 2185 field = self.sql(expression, "bucket_field") 2186 field = f" ON {field}" if field else "" 2187 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2188 seed = self.sql(expression, "seed") 2189 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2190 2191 size = self.sql(expression, "size") 2192 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2193 size = f"{size} ROWS" 2194 2195 percent = self.sql(expression, "percent") 2196 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2197 percent = f"{percent} PERCENT" 2198 2199 expr = f"{bucket}{percent}{size}" 2200 if self.TABLESAMPLE_REQUIRES_PARENS: 2201 expr = f"({expr})" 2202 2203 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2204 2205 def pivot_sql(self, expression: exp.Pivot) -> str: 2206 expressions = self.expressions(expression, flat=True) 2207 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2208 2209 group = self.sql(expression, "group") 2210 2211 if expression.this: 2212 this = self.sql(expression, "this") 2213 if not expressions: 2214 sql = f"UNPIVOT {this}" 2215 else: 2216 on = f"{self.seg('ON')} {expressions}" 2217 into = self.sql(expression, "into") 2218 into = f"{self.seg('INTO')} {into}" if into else "" 2219 using = self.expressions(expression, key="using", flat=True) 2220 using = f"{self.seg('USING')} {using}" if using else "" 2221 sql = f"{direction} {this}{on}{into}{using}{group}" 2222 return self.prepend_ctes(expression, sql) 2223 2224 alias = self.sql(expression, "alias") 2225 alias = f" AS {alias}" if alias else "" 2226 2227 fields = self.expressions( 2228 expression, 2229 "fields", 2230 sep=" ", 2231 dynamic=True, 2232 new_line=True, 2233 skip_first=True, 2234 skip_last=True, 2235 ) 2236 2237 include_nulls = expression.args.get("include_nulls") 2238 if include_nulls is not None: 2239 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2240 else: 2241 nulls = "" 2242 2243 default_on_null = self.sql(expression, "default_on_null") 2244 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2245 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2246 return self.prepend_ctes(expression, sql) 2247 2248 def version_sql(self, expression: exp.Version) -> str: 2249 this = f"FOR {expression.name}" 2250 kind = expression.text("kind") 2251 expr = self.sql(expression, "expression") 2252 return f"{this} {kind} {expr}" 2253 2254 def tuple_sql(self, expression: exp.Tuple) -> str: 2255 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2256 2257 def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]: 2258 """ 2259 Returns (join_sql, from_sql) for UPDATE statements. 2260 - join_sql: placed after UPDATE table, before SET 2261 - from_sql: placed after SET clause (standard position) 2262 Dialects like MySQL need to convert FROM to JOIN syntax. 2263 """ 2264 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2265 return ("", self.sql(expression, "from_")) 2266 2267 # Qualify unqualified columns in SET clause with the target table 2268 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2269 target_table = expression.this 2270 if isinstance(target_table, exp.Table): 2271 target_name = exp.to_identifier(target_table.alias_or_name) 2272 for eq in expression.expressions: 2273 col = eq.this 2274 if isinstance(col, exp.Column) and not col.table: 2275 col.set("table", target_name) 2276 2277 table = from_expr.this 2278 if nested_joins := table.args.get("joins", []): 2279 table.set("joins", None) 2280 2281 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2282 for nested in nested_joins: 2283 if not nested.args.get("on") and not nested.args.get("using"): 2284 nested.set("on", exp.true()) 2285 join_sql += self.sql(nested) 2286 2287 return (join_sql, "") 2288 2289 def update_sql(self, expression: exp.Update) -> str: 2290 this = self.sql(expression, "this") 2291 join_sql, from_sql = self._update_from_joins_sql(expression) 2292 set_sql = self.expressions(expression, flat=True) 2293 where_sql = self.sql(expression, "where") 2294 returning = self.sql(expression, "returning") 2295 order = self.sql(expression, "order") 2296 limit = self.sql(expression, "limit") 2297 if self.RETURNING_END: 2298 expression_sql = f"{from_sql}{where_sql}{returning}" 2299 else: 2300 expression_sql = f"{returning}{from_sql}{where_sql}" 2301 options = self.expressions(expression, key="options") 2302 options = f" OPTION({options})" if options else "" 2303 sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2304 return self.prepend_ctes(expression, sql) 2305 2306 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2307 values_as_table = values_as_table and self.VALUES_AS_TABLE 2308 2309 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2310 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2311 args = self.expressions(expression) 2312 alias = self.sql(expression, "alias") 2313 values = f"VALUES{self.seg('')}{args}" 2314 values = ( 2315 f"({values})" 2316 if self.WRAP_DERIVED_VALUES 2317 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2318 else values 2319 ) 2320 values = self.query_modifiers(expression, values) 2321 return f"{values} AS {alias}" if alias else values 2322 2323 # Converts `VALUES...` expression into a series of select unions. 2324 alias_node = expression.args.get("alias") 2325 column_names = alias_node and alias_node.columns 2326 2327 selects: t.List[exp.Query] = [] 2328 2329 for i, tup in enumerate(expression.expressions): 2330 row = tup.expressions 2331 2332 if i == 0 and column_names: 2333 row = [ 2334 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2335 ] 2336 2337 selects.append(exp.Select(expressions=row)) 2338 2339 if self.pretty: 2340 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2341 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2342 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2343 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2344 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2345 2346 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2347 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2348 return f"({unions}){alias}" 2349 2350 def var_sql(self, expression: exp.Var) -> str: 2351 return self.sql(expression, "this") 2352 2353 @unsupported_args("expressions") 2354 def into_sql(self, expression: exp.Into) -> str: 2355 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2356 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2357 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2358 2359 def from_sql(self, expression: exp.From) -> str: 2360 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2361 2362 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2363 grouping_sets = self.expressions(expression, indent=False) 2364 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2365 2366 def rollup_sql(self, expression: exp.Rollup) -> str: 2367 expressions = self.expressions(expression, indent=False) 2368 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2369 2370 def cube_sql(self, expression: exp.Cube) -> str: 2371 expressions = self.expressions(expression, indent=False) 2372 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2373 2374 def group_sql(self, expression: exp.Group) -> str: 2375 group_by_all = expression.args.get("all") 2376 if group_by_all is True: 2377 modifier = " ALL" 2378 elif group_by_all is False: 2379 modifier = " DISTINCT" 2380 else: 2381 modifier = "" 2382 2383 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2384 2385 grouping_sets = self.expressions(expression, key="grouping_sets") 2386 cube = self.expressions(expression, key="cube") 2387 rollup = self.expressions(expression, key="rollup") 2388 2389 groupings = csv( 2390 self.seg(grouping_sets) if grouping_sets else "", 2391 self.seg(cube) if cube else "", 2392 self.seg(rollup) if rollup else "", 2393 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2394 sep=self.GROUPINGS_SEP, 2395 ) 2396 2397 if ( 2398 expression.expressions 2399 and groupings 2400 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2401 ): 2402 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2403 2404 return f"{group_by}{groupings}" 2405 2406 def having_sql(self, expression: exp.Having) -> str: 2407 this = self.indent(self.sql(expression, "this")) 2408 return f"{self.seg('HAVING')}{self.sep()}{this}" 2409 2410 def connect_sql(self, expression: exp.Connect) -> str: 2411 start = self.sql(expression, "start") 2412 start = self.seg(f"START WITH {start}") if start else "" 2413 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2414 connect = self.sql(expression, "connect") 2415 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2416 return start + connect 2417 2418 def prior_sql(self, expression: exp.Prior) -> str: 2419 return f"PRIOR {self.sql(expression, 'this')}" 2420 2421 def join_sql(self, expression: exp.Join) -> str: 2422 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2423 side = None 2424 else: 2425 side = expression.side 2426 2427 op_sql = " ".join( 2428 op 2429 for op in ( 2430 expression.method, 2431 "GLOBAL" if expression.args.get("global_") else None, 2432 side, 2433 expression.kind, 2434 expression.hint if self.JOIN_HINTS else None, 2435 ) 2436 if op 2437 ) 2438 match_cond = self.sql(expression, "match_condition") 2439 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2440 on_sql = self.sql(expression, "on") 2441 using = expression.args.get("using") 2442 2443 if not on_sql and using: 2444 on_sql = csv(*(self.sql(column) for column in using)) 2445 2446 this = expression.this 2447 this_sql = self.sql(this) 2448 2449 exprs = self.expressions(expression) 2450 if exprs: 2451 this_sql = f"{this_sql},{self.seg(exprs)}" 2452 2453 if on_sql: 2454 on_sql = self.indent(on_sql, skip_first=True) 2455 space = self.seg(" " * self.pad) if self.pretty else " " 2456 if using: 2457 on_sql = f"{space}USING ({on_sql})" 2458 else: 2459 on_sql = f"{space}ON {on_sql}" 2460 elif not op_sql: 2461 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2462 return f" {this_sql}" 2463 2464 return f", {this_sql}" 2465 2466 if op_sql != "STRAIGHT_JOIN": 2467 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2468 2469 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2470 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2471 2472 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2473 args = self.expressions(expression, flat=True) 2474 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2475 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2476 2477 def lateral_op(self, expression: exp.Lateral) -> str: 2478 cross_apply = expression.args.get("cross_apply") 2479 2480 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2481 if cross_apply is True: 2482 op = "INNER JOIN " 2483 elif cross_apply is False: 2484 op = "LEFT JOIN " 2485 else: 2486 op = "" 2487 2488 return f"{op}LATERAL" 2489 2490 def lateral_sql(self, expression: exp.Lateral) -> str: 2491 this = self.sql(expression, "this") 2492 2493 if expression.args.get("view"): 2494 alias = expression.args["alias"] 2495 columns = self.expressions(alias, key="columns", flat=True) 2496 table = f" {alias.name}" if alias.name else "" 2497 columns = f" AS {columns}" if columns else "" 2498 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2499 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2500 2501 alias = self.sql(expression, "alias") 2502 alias = f" AS {alias}" if alias else "" 2503 2504 ordinality = expression.args.get("ordinality") or "" 2505 if ordinality: 2506 ordinality = f" WITH ORDINALITY{alias}" 2507 alias = "" 2508 2509 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2510 2511 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2512 this = self.sql(expression, "this") 2513 2514 args = [ 2515 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2516 for e in (expression.args.get(k) for k in ("offset", "expression")) 2517 if e 2518 ] 2519 2520 args_sql = ", ".join(self.sql(e) for e in args) 2521 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2522 expressions = self.expressions(expression, flat=True) 2523 limit_options = self.sql(expression, "limit_options") 2524 expressions = f" BY {expressions}" if expressions else "" 2525 2526 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2527 2528 def offset_sql(self, expression: exp.Offset) -> str: 2529 this = self.sql(expression, "this") 2530 value = expression.expression 2531 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2532 expressions = self.expressions(expression, flat=True) 2533 expressions = f" BY {expressions}" if expressions else "" 2534 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2535 2536 def setitem_sql(self, expression: exp.SetItem) -> str: 2537 kind = self.sql(expression, "kind") 2538 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2539 kind = "" 2540 else: 2541 kind = f"{kind} " if kind else "" 2542 this = self.sql(expression, "this") 2543 expressions = self.expressions(expression) 2544 collate = self.sql(expression, "collate") 2545 collate = f" COLLATE {collate}" if collate else "" 2546 global_ = "GLOBAL " if expression.args.get("global_") else "" 2547 return f"{global_}{kind}{this}{expressions}{collate}" 2548 2549 def set_sql(self, expression: exp.Set) -> str: 2550 expressions = f" {self.expressions(expression, flat=True)}" 2551 tag = " TAG" if expression.args.get("tag") else "" 2552 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2553 2554 def queryband_sql(self, expression: exp.QueryBand) -> str: 2555 this = self.sql(expression, "this") 2556 update = " UPDATE" if expression.args.get("update") else "" 2557 scope = self.sql(expression, "scope") 2558 scope = f" FOR {scope}" if scope else "" 2559 2560 return f"QUERY_BAND = {this}{update}{scope}" 2561 2562 def pragma_sql(self, expression: exp.Pragma) -> str: 2563 return f"PRAGMA {self.sql(expression, 'this')}" 2564 2565 def lock_sql(self, expression: exp.Lock) -> str: 2566 if not self.LOCKING_READS_SUPPORTED: 2567 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2568 return "" 2569 2570 update = expression.args["update"] 2571 key = expression.args.get("key") 2572 if update: 2573 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2574 else: 2575 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2576 expressions = self.expressions(expression, flat=True) 2577 expressions = f" OF {expressions}" if expressions else "" 2578 wait = expression.args.get("wait") 2579 2580 if wait is not None: 2581 if isinstance(wait, exp.Literal): 2582 wait = f" WAIT {self.sql(wait)}" 2583 else: 2584 wait = " NOWAIT" if wait else " SKIP LOCKED" 2585 2586 return f"{lock_type}{expressions}{wait or ''}" 2587 2588 def literal_sql(self, expression: exp.Literal) -> str: 2589 text = expression.this or "" 2590 if expression.is_string: 2591 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2592 return text 2593 2594 def escape_str( 2595 self, 2596 text: str, 2597 escape_backslash: bool = True, 2598 delimiter: t.Optional[str] = None, 2599 escaped_delimiter: t.Optional[str] = None, 2600 ) -> str: 2601 if self.dialect.ESCAPED_SEQUENCES: 2602 to_escaped = self.dialect.ESCAPED_SEQUENCES 2603 text = "".join( 2604 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2605 ) 2606 2607 delimiter = delimiter or self.dialect.QUOTE_END 2608 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2609 2610 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2611 2612 def loaddata_sql(self, expression: exp.LoadData) -> str: 2613 local = " LOCAL" if expression.args.get("local") else "" 2614 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2615 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2616 this = f" INTO TABLE {self.sql(expression, 'this')}" 2617 partition = self.sql(expression, "partition") 2618 partition = f" {partition}" if partition else "" 2619 input_format = self.sql(expression, "input_format") 2620 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2621 serde = self.sql(expression, "serde") 2622 serde = f" SERDE {serde}" if serde else "" 2623 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2624 2625 def null_sql(self, *_) -> str: 2626 return "NULL" 2627 2628 def boolean_sql(self, expression: exp.Boolean) -> str: 2629 return "TRUE" if expression.this else "FALSE" 2630 2631 def booland_sql(self, expression: exp.Booland) -> str: 2632 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2633 2634 def boolor_sql(self, expression: exp.Boolor) -> str: 2635 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2636 2637 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2638 this = self.sql(expression, "this") 2639 this = f"{this} " if this else this 2640 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2641 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2642 2643 def withfill_sql(self, expression: exp.WithFill) -> str: 2644 from_sql = self.sql(expression, "from_") 2645 from_sql = f" FROM {from_sql}" if from_sql else "" 2646 to_sql = self.sql(expression, "to") 2647 to_sql = f" TO {to_sql}" if to_sql else "" 2648 step_sql = self.sql(expression, "step") 2649 step_sql = f" STEP {step_sql}" if step_sql else "" 2650 interpolated_values = [ 2651 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2652 if isinstance(e, exp.Alias) 2653 else self.sql(e, "this") 2654 for e in expression.args.get("interpolate") or [] 2655 ] 2656 interpolate = ( 2657 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2658 ) 2659 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2660 2661 def cluster_sql(self, expression: exp.Cluster) -> str: 2662 return self.op_expressions("CLUSTER BY", expression) 2663 2664 def distribute_sql(self, expression: exp.Distribute) -> str: 2665 return self.op_expressions("DISTRIBUTE BY", expression) 2666 2667 def sort_sql(self, expression: exp.Sort) -> str: 2668 return self.op_expressions("SORT BY", expression) 2669 2670 def ordered_sql(self, expression: exp.Ordered) -> str: 2671 desc = expression.args.get("desc") 2672 asc = not desc 2673 2674 nulls_first = expression.args.get("nulls_first") 2675 nulls_last = not nulls_first 2676 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2677 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2678 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2679 2680 this = self.sql(expression, "this") 2681 2682 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2683 nulls_sort_change = "" 2684 if nulls_first and ( 2685 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2686 ): 2687 nulls_sort_change = " NULLS FIRST" 2688 elif ( 2689 nulls_last 2690 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2691 and not nulls_are_last 2692 ): 2693 nulls_sort_change = " NULLS LAST" 2694 2695 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2696 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2697 window = expression.find_ancestor(exp.Window, exp.Select) 2698 if isinstance(window, exp.Window) and window.args.get("spec"): 2699 self.unsupported( 2700 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2701 ) 2702 nulls_sort_change = "" 2703 elif self.NULL_ORDERING_SUPPORTED is False and ( 2704 (asc and nulls_sort_change == " NULLS LAST") 2705 or (desc and nulls_sort_change == " NULLS FIRST") 2706 ): 2707 # BigQuery does not allow these ordering/nulls combinations when used under 2708 # an aggregation func or under a window containing one 2709 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2710 2711 if isinstance(ancestor, exp.Window): 2712 ancestor = ancestor.this 2713 if isinstance(ancestor, exp.AggFunc): 2714 self.unsupported( 2715 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2716 ) 2717 nulls_sort_change = "" 2718 elif self.NULL_ORDERING_SUPPORTED is None: 2719 if expression.this.is_int: 2720 self.unsupported( 2721 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2722 ) 2723 elif not isinstance(expression.this, exp.Rand): 2724 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2725 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2726 nulls_sort_change = "" 2727 2728 with_fill = self.sql(expression, "with_fill") 2729 with_fill = f" {with_fill}" if with_fill else "" 2730 2731 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2732 2733 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2734 window_frame = self.sql(expression, "window_frame") 2735 window_frame = f"{window_frame} " if window_frame else "" 2736 2737 this = self.sql(expression, "this") 2738 2739 return f"{window_frame}{this}" 2740 2741 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2742 partition = self.partition_by_sql(expression) 2743 order = self.sql(expression, "order") 2744 measures = self.expressions(expression, key="measures") 2745 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2746 rows = self.sql(expression, "rows") 2747 rows = self.seg(rows) if rows else "" 2748 after = self.sql(expression, "after") 2749 after = self.seg(after) if after else "" 2750 pattern = self.sql(expression, "pattern") 2751 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2752 definition_sqls = [ 2753 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2754 for definition in expression.args.get("define", []) 2755 ] 2756 definitions = self.expressions(sqls=definition_sqls) 2757 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2758 body = "".join( 2759 ( 2760 partition, 2761 order, 2762 measures, 2763 rows, 2764 after, 2765 pattern, 2766 define, 2767 ) 2768 ) 2769 alias = self.sql(expression, "alias") 2770 alias = f" {alias}" if alias else "" 2771 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2772 2773 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2774 limit = expression.args.get("limit") 2775 2776 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2777 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2778 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2779 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2780 2781 return csv( 2782 *sqls, 2783 *[self.sql(join) for join in expression.args.get("joins") or []], 2784 self.sql(expression, "match"), 2785 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2786 self.sql(expression, "prewhere"), 2787 self.sql(expression, "where"), 2788 self.sql(expression, "connect"), 2789 self.sql(expression, "group"), 2790 self.sql(expression, "having"), 2791 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2792 self.sql(expression, "order"), 2793 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2794 *self.after_limit_modifiers(expression), 2795 self.options_modifier(expression), 2796 self.for_modifiers(expression), 2797 sep="", 2798 ) 2799 2800 def options_modifier(self, expression: exp.Expression) -> str: 2801 options = self.expressions(expression, key="options") 2802 return f" {options}" if options else "" 2803 2804 def for_modifiers(self, expression: exp.Expression) -> str: 2805 for_modifiers = self.expressions(expression, key="for_") 2806 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2807 2808 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2809 self.unsupported("Unsupported query option.") 2810 return "" 2811 2812 def offset_limit_modifiers( 2813 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2814 ) -> t.List[str]: 2815 return [ 2816 self.sql(expression, "offset") if fetch else self.sql(limit), 2817 self.sql(limit) if fetch else self.sql(expression, "offset"), 2818 ] 2819 2820 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2821 locks = self.expressions(expression, key="locks", sep=" ") 2822 locks = f" {locks}" if locks else "" 2823 return [locks, self.sql(expression, "sample")] 2824 2825 def select_sql(self, expression: exp.Select) -> str: 2826 into = expression.args.get("into") 2827 if not self.SUPPORTS_SELECT_INTO and into: 2828 into.pop() 2829 2830 hint = self.sql(expression, "hint") 2831 distinct = self.sql(expression, "distinct") 2832 distinct = f" {distinct}" if distinct else "" 2833 kind = self.sql(expression, "kind") 2834 2835 limit = expression.args.get("limit") 2836 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2837 top = self.limit_sql(limit, top=True) 2838 limit.pop() 2839 else: 2840 top = "" 2841 2842 expressions = self.expressions(expression) 2843 2844 if kind: 2845 if kind in self.SELECT_KINDS: 2846 kind = f" AS {kind}" 2847 else: 2848 if kind == "STRUCT": 2849 expressions = self.expressions( 2850 sqls=[ 2851 self.sql( 2852 exp.Struct( 2853 expressions=[ 2854 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2855 if isinstance(e, exp.Alias) 2856 else e 2857 for e in expression.expressions 2858 ] 2859 ) 2860 ) 2861 ] 2862 ) 2863 kind = "" 2864 2865 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2866 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2867 2868 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2869 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2870 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2871 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2872 sql = self.query_modifiers( 2873 expression, 2874 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2875 self.sql(expression, "into", comment=False), 2876 self.sql(expression, "from_", comment=False), 2877 ) 2878 2879 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2880 if expression.args.get("with_"): 2881 sql = self.maybe_comment(sql, expression) 2882 expression.pop_comments() 2883 2884 sql = self.prepend_ctes(expression, sql) 2885 2886 if not self.SUPPORTS_SELECT_INTO and into: 2887 if into.args.get("temporary"): 2888 table_kind = " TEMPORARY" 2889 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2890 table_kind = " UNLOGGED" 2891 else: 2892 table_kind = "" 2893 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2894 2895 return sql 2896 2897 def schema_sql(self, expression: exp.Schema) -> str: 2898 this = self.sql(expression, "this") 2899 sql = self.schema_columns_sql(expression) 2900 return f"{this} {sql}" if this and sql else this or sql 2901 2902 def schema_columns_sql(self, expression: exp.Schema) -> str: 2903 if expression.expressions: 2904 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2905 return "" 2906 2907 def star_sql(self, expression: exp.Star) -> str: 2908 except_ = self.expressions(expression, key="except_", flat=True) 2909 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2910 replace = self.expressions(expression, key="replace", flat=True) 2911 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2912 rename = self.expressions(expression, key="rename", flat=True) 2913 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2914 return f"*{except_}{replace}{rename}" 2915 2916 def parameter_sql(self, expression: exp.Parameter) -> str: 2917 this = self.sql(expression, "this") 2918 return f"{self.PARAMETER_TOKEN}{this}" 2919 2920 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2921 this = self.sql(expression, "this") 2922 kind = expression.text("kind") 2923 if kind: 2924 kind = f"{kind}." 2925 return f"@@{kind}{this}" 2926 2927 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2928 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2929 2930 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2931 alias = self.sql(expression, "alias") 2932 alias = f"{sep}{alias}" if alias else "" 2933 sample = self.sql(expression, "sample") 2934 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2935 alias = f"{sample}{alias}" 2936 2937 # Set to None so it's not generated again by self.query_modifiers() 2938 expression.set("sample", None) 2939 2940 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2941 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2942 return self.prepend_ctes(expression, sql) 2943 2944 def qualify_sql(self, expression: exp.Qualify) -> str: 2945 this = self.indent(self.sql(expression, "this")) 2946 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2947 2948 def unnest_sql(self, expression: exp.Unnest) -> str: 2949 args = self.expressions(expression, flat=True) 2950 2951 alias = expression.args.get("alias") 2952 offset = expression.args.get("offset") 2953 2954 if self.UNNEST_WITH_ORDINALITY: 2955 if alias and isinstance(offset, exp.Expression): 2956 alias.append("columns", offset) 2957 2958 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2959 columns = alias.columns 2960 alias = self.sql(columns[0]) if columns else "" 2961 else: 2962 alias = self.sql(alias) 2963 2964 alias = f" AS {alias}" if alias else alias 2965 if self.UNNEST_WITH_ORDINALITY: 2966 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2967 else: 2968 if isinstance(offset, exp.Expression): 2969 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2970 elif offset: 2971 suffix = f"{alias} WITH OFFSET" 2972 else: 2973 suffix = alias 2974 2975 return f"UNNEST({args}){suffix}" 2976 2977 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2978 return "" 2979 2980 def where_sql(self, expression: exp.Where) -> str: 2981 this = self.indent(self.sql(expression, "this")) 2982 return f"{self.seg('WHERE')}{self.sep()}{this}" 2983 2984 def window_sql(self, expression: exp.Window) -> str: 2985 this = self.sql(expression, "this") 2986 partition = self.partition_by_sql(expression) 2987 order = expression.args.get("order") 2988 order = self.order_sql(order, flat=True) if order else "" 2989 spec = self.sql(expression, "spec") 2990 alias = self.sql(expression, "alias") 2991 over = self.sql(expression, "over") or "OVER" 2992 2993 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2994 2995 first = expression.args.get("first") 2996 if first is None: 2997 first = "" 2998 else: 2999 first = "FIRST" if first else "LAST" 3000 3001 if not partition and not order and not spec and alias: 3002 return f"{this} {alias}" 3003 3004 args = self.format_args( 3005 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3006 ) 3007 return f"{this} ({args})" 3008 3009 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3010 partition = self.expressions(expression, key="partition_by", flat=True) 3011 return f"PARTITION BY {partition}" if partition else "" 3012 3013 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3014 kind = self.sql(expression, "kind") 3015 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3016 end = ( 3017 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3018 or "CURRENT ROW" 3019 ) 3020 3021 window_spec = f"{kind} BETWEEN {start} AND {end}" 3022 3023 exclude = self.sql(expression, "exclude") 3024 if exclude: 3025 if self.SUPPORTS_WINDOW_EXCLUDE: 3026 window_spec += f" EXCLUDE {exclude}" 3027 else: 3028 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3029 3030 return window_spec 3031 3032 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3033 this = self.sql(expression, "this") 3034 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3035 return f"{this} WITHIN GROUP ({expression_sql})" 3036 3037 def between_sql(self, expression: exp.Between) -> str: 3038 this = self.sql(expression, "this") 3039 low = self.sql(expression, "low") 3040 high = self.sql(expression, "high") 3041 symmetric = expression.args.get("symmetric") 3042 3043 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3044 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3045 3046 flag = ( 3047 " SYMMETRIC" 3048 if symmetric 3049 else " ASYMMETRIC" 3050 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3051 else "" # silently drop ASYMMETRIC – semantics identical 3052 ) 3053 return f"{this} BETWEEN{flag} {low} AND {high}" 3054 3055 def bracket_offset_expressions( 3056 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 3057 ) -> t.List[exp.Expression]: 3058 return apply_index_offset( 3059 expression.this, 3060 expression.expressions, 3061 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3062 dialect=self.dialect, 3063 ) 3064 3065 def bracket_sql(self, expression: exp.Bracket) -> str: 3066 expressions = self.bracket_offset_expressions(expression) 3067 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3068 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3069 3070 def all_sql(self, expression: exp.All) -> str: 3071 this = self.sql(expression, "this") 3072 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3073 this = self.wrap(this) 3074 return f"ALL {this}" 3075 3076 def any_sql(self, expression: exp.Any) -> str: 3077 this = self.sql(expression, "this") 3078 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3079 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3080 this = self.wrap(this) 3081 return f"ANY{this}" 3082 return f"ANY {this}" 3083 3084 def exists_sql(self, expression: exp.Exists) -> str: 3085 return f"EXISTS{self.wrap(expression)}" 3086 3087 def case_sql(self, expression: exp.Case) -> str: 3088 this = self.sql(expression, "this") 3089 statements = [f"CASE {this}" if this else "CASE"] 3090 3091 for e in expression.args["ifs"]: 3092 statements.append(f"WHEN {self.sql(e, 'this')}") 3093 statements.append(f"THEN {self.sql(e, 'true')}") 3094 3095 default = self.sql(expression, "default") 3096 3097 if default: 3098 statements.append(f"ELSE {default}") 3099 3100 statements.append("END") 3101 3102 if self.pretty and self.too_wide(statements): 3103 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3104 3105 return " ".join(statements) 3106 3107 def constraint_sql(self, expression: exp.Constraint) -> str: 3108 this = self.sql(expression, "this") 3109 expressions = self.expressions(expression, flat=True) 3110 return f"CONSTRAINT {this} {expressions}" 3111 3112 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3113 order = expression.args.get("order") 3114 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3115 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3116 3117 def extract_sql(self, expression: exp.Extract) -> str: 3118 from sqlglot.dialects.dialect import map_date_part 3119 3120 this = ( 3121 map_date_part(expression.this, self.dialect) 3122 if self.NORMALIZE_EXTRACT_DATE_PARTS 3123 else expression.this 3124 ) 3125 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3126 expression_sql = self.sql(expression, "expression") 3127 3128 return f"EXTRACT({this_sql} FROM {expression_sql})" 3129 3130 def trim_sql(self, expression: exp.Trim) -> str: 3131 trim_type = self.sql(expression, "position") 3132 3133 if trim_type == "LEADING": 3134 func_name = "LTRIM" 3135 elif trim_type == "TRAILING": 3136 func_name = "RTRIM" 3137 else: 3138 func_name = "TRIM" 3139 3140 return self.func(func_name, expression.this, expression.expression) 3141 3142 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3143 args = expression.expressions 3144 if isinstance(expression, exp.ConcatWs): 3145 args = args[1:] # Skip the delimiter 3146 3147 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3148 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3149 3150 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3151 3152 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3153 if not e.type: 3154 from sqlglot.optimizer.annotate_types import annotate_types 3155 3156 e = annotate_types(e, dialect=self.dialect) 3157 3158 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3159 return e 3160 3161 return exp.func("coalesce", e, exp.Literal.string("")) 3162 3163 args = [_wrap_with_coalesce(e) for e in args] 3164 3165 return args 3166 3167 def concat_sql(self, expression: exp.Concat) -> str: 3168 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3169 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3170 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3171 # instead of coalescing them to empty string. 3172 from sqlglot.dialects.dialect import concat_to_dpipe_sql 3173 3174 return concat_to_dpipe_sql(self, expression) 3175 3176 expressions = self.convert_concat_args(expression) 3177 3178 # Some dialects don't allow a single-argument CONCAT call 3179 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3180 return self.sql(expressions[0]) 3181 3182 return self.func("CONCAT", *expressions) 3183 3184 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3185 return self.func( 3186 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3187 ) 3188 3189 def check_sql(self, expression: exp.Check) -> str: 3190 this = self.sql(expression, key="this") 3191 return f"CHECK ({this})" 3192 3193 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3194 expressions = self.expressions(expression, flat=True) 3195 expressions = f" ({expressions})" if expressions else "" 3196 reference = self.sql(expression, "reference") 3197 reference = f" {reference}" if reference else "" 3198 delete = self.sql(expression, "delete") 3199 delete = f" ON DELETE {delete}" if delete else "" 3200 update = self.sql(expression, "update") 3201 update = f" ON UPDATE {update}" if update else "" 3202 options = self.expressions(expression, key="options", flat=True, sep=" ") 3203 options = f" {options}" if options else "" 3204 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3205 3206 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3207 this = self.sql(expression, "this") 3208 this = f" {this}" if this else "" 3209 expressions = self.expressions(expression, flat=True) 3210 include = self.sql(expression, "include") 3211 options = self.expressions(expression, key="options", flat=True, sep=" ") 3212 options = f" {options}" if options else "" 3213 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3214 3215 def if_sql(self, expression: exp.If) -> str: 3216 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3217 3218 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3219 if self.MATCH_AGAINST_TABLE_PREFIX: 3220 expressions = [] 3221 for expr in expression.expressions: 3222 if isinstance(expr, exp.Table): 3223 expressions.append(f"TABLE {self.sql(expr)}") 3224 else: 3225 expressions.append(expr) 3226 else: 3227 expressions = expression.expressions 3228 3229 modifier = expression.args.get("modifier") 3230 modifier = f" {modifier}" if modifier else "" 3231 return ( 3232 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3233 ) 3234 3235 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3236 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3237 3238 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3239 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3240 3241 if expression.args.get("escape"): 3242 path = self.escape_str(path) 3243 3244 if self.QUOTE_JSON_PATH: 3245 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3246 3247 return path 3248 3249 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3250 if isinstance(expression, exp.JSONPathPart): 3251 transform = self.TRANSFORMS.get(expression.__class__) 3252 if not callable(transform): 3253 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3254 return "" 3255 3256 return transform(self, expression) 3257 3258 if isinstance(expression, int): 3259 return str(expression) 3260 3261 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3262 escaped = expression.replace("'", "\\'") 3263 escaped = f"\\'{expression}\\'" 3264 else: 3265 escaped = expression.replace('"', '\\"') 3266 escaped = f'"{escaped}"' 3267 3268 return escaped 3269 3270 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3271 return f"{self.sql(expression, 'this')} FORMAT JSON" 3272 3273 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3274 # Output the Teradata column FORMAT override. 3275 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3276 this = self.sql(expression, "this") 3277 fmt = self.sql(expression, "format") 3278 return f"{this} (FORMAT {fmt})" 3279 3280 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3281 null_handling = expression.args.get("null_handling") 3282 null_handling = f" {null_handling}" if null_handling else "" 3283 3284 unique_keys = expression.args.get("unique_keys") 3285 if unique_keys is not None: 3286 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3287 else: 3288 unique_keys = "" 3289 3290 return_type = self.sql(expression, "return_type") 3291 return_type = f" RETURNING {return_type}" if return_type else "" 3292 encoding = self.sql(expression, "encoding") 3293 encoding = f" ENCODING {encoding}" if encoding else "" 3294 3295 return self.func( 3296 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3297 *expression.expressions, 3298 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3299 ) 3300 3301 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3302 return self.jsonobject_sql(expression) 3303 3304 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3305 null_handling = expression.args.get("null_handling") 3306 null_handling = f" {null_handling}" if null_handling else "" 3307 return_type = self.sql(expression, "return_type") 3308 return_type = f" RETURNING {return_type}" if return_type else "" 3309 strict = " STRICT" if expression.args.get("strict") else "" 3310 return self.func( 3311 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3312 ) 3313 3314 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3315 this = self.sql(expression, "this") 3316 order = self.sql(expression, "order") 3317 null_handling = expression.args.get("null_handling") 3318 null_handling = f" {null_handling}" if null_handling else "" 3319 return_type = self.sql(expression, "return_type") 3320 return_type = f" RETURNING {return_type}" if return_type else "" 3321 strict = " STRICT" if expression.args.get("strict") else "" 3322 return self.func( 3323 "JSON_ARRAYAGG", 3324 this, 3325 suffix=f"{order}{null_handling}{return_type}{strict})", 3326 ) 3327 3328 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3329 path = self.sql(expression, "path") 3330 path = f" PATH {path}" if path else "" 3331 nested_schema = self.sql(expression, "nested_schema") 3332 3333 if nested_schema: 3334 return f"NESTED{path} {nested_schema}" 3335 3336 this = self.sql(expression, "this") 3337 kind = self.sql(expression, "kind") 3338 kind = f" {kind}" if kind else "" 3339 3340 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3341 return f"{this}{kind}{path}{ordinality}" 3342 3343 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3344 return self.func("COLUMNS", *expression.expressions) 3345 3346 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3347 this = self.sql(expression, "this") 3348 path = self.sql(expression, "path") 3349 path = f", {path}" if path else "" 3350 error_handling = expression.args.get("error_handling") 3351 error_handling = f" {error_handling}" if error_handling else "" 3352 empty_handling = expression.args.get("empty_handling") 3353 empty_handling = f" {empty_handling}" if empty_handling else "" 3354 schema = self.sql(expression, "schema") 3355 return self.func( 3356 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3357 ) 3358 3359 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3360 this = self.sql(expression, "this") 3361 kind = self.sql(expression, "kind") 3362 path = self.sql(expression, "path") 3363 path = f" {path}" if path else "" 3364 as_json = " AS JSON" if expression.args.get("as_json") else "" 3365 return f"{this} {kind}{path}{as_json}" 3366 3367 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3368 this = self.sql(expression, "this") 3369 path = self.sql(expression, "path") 3370 path = f", {path}" if path else "" 3371 expressions = self.expressions(expression) 3372 with_ = ( 3373 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3374 if expressions 3375 else "" 3376 ) 3377 return f"OPENJSON({this}{path}){with_}" 3378 3379 def in_sql(self, expression: exp.In) -> str: 3380 query = expression.args.get("query") 3381 unnest = expression.args.get("unnest") 3382 field = expression.args.get("field") 3383 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3384 3385 if query: 3386 in_sql = self.sql(query) 3387 elif unnest: 3388 in_sql = self.in_unnest_op(unnest) 3389 elif field: 3390 in_sql = self.sql(field) 3391 else: 3392 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3393 3394 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3395 3396 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3397 return f"(SELECT {self.sql(unnest)})" 3398 3399 def interval_sql(self, expression: exp.Interval) -> str: 3400 unit_expression = expression.args.get("unit") 3401 unit = self.sql(unit_expression) if unit_expression else "" 3402 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3403 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3404 unit = f" {unit}" if unit else "" 3405 3406 if self.SINGLE_STRING_INTERVAL: 3407 this = expression.this.name if expression.this else "" 3408 if this: 3409 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3410 return f"INTERVAL '{this}'{unit}" 3411 return f"INTERVAL '{this}{unit}'" 3412 return f"INTERVAL{unit}" 3413 3414 this = self.sql(expression, "this") 3415 if this: 3416 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3417 this = f" {this}" if unwrapped else f" ({this})" 3418 3419 return f"INTERVAL{this}{unit}" 3420 3421 def return_sql(self, expression: exp.Return) -> str: 3422 return f"RETURN {self.sql(expression, 'this')}" 3423 3424 def reference_sql(self, expression: exp.Reference) -> str: 3425 this = self.sql(expression, "this") 3426 expressions = self.expressions(expression, flat=True) 3427 expressions = f"({expressions})" if expressions else "" 3428 options = self.expressions(expression, key="options", flat=True, sep=" ") 3429 options = f" {options}" if options else "" 3430 return f"REFERENCES {this}{expressions}{options}" 3431 3432 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3433 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3434 parent = expression.parent 3435 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3436 return self.func( 3437 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3438 ) 3439 3440 def paren_sql(self, expression: exp.Paren) -> str: 3441 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3442 return f"({sql}{self.seg(')', sep='')}" 3443 3444 def neg_sql(self, expression: exp.Neg) -> str: 3445 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3446 this_sql = self.sql(expression, "this") 3447 sep = " " if this_sql[0] == "-" else "" 3448 return f"-{sep}{this_sql}" 3449 3450 def not_sql(self, expression: exp.Not) -> str: 3451 return f"NOT {self.sql(expression, 'this')}" 3452 3453 def alias_sql(self, expression: exp.Alias) -> str: 3454 alias = self.sql(expression, "alias") 3455 alias = f" AS {alias}" if alias else "" 3456 return f"{self.sql(expression, 'this')}{alias}" 3457 3458 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3459 alias = expression.args["alias"] 3460 3461 parent = expression.parent 3462 pivot = parent and parent.parent 3463 3464 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3465 identifier_alias = isinstance(alias, exp.Identifier) 3466 literal_alias = isinstance(alias, exp.Literal) 3467 3468 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3469 alias.replace(exp.Literal.string(alias.output_name)) 3470 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3471 alias.replace(exp.to_identifier(alias.output_name)) 3472 3473 return self.alias_sql(expression) 3474 3475 def aliases_sql(self, expression: exp.Aliases) -> str: 3476 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3477 3478 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3479 this = self.sql(expression, "this") 3480 index = self.sql(expression, "expression") 3481 return f"{this} AT {index}" 3482 3483 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3484 this = self.sql(expression, "this") 3485 zone = self.sql(expression, "zone") 3486 return f"{this} AT TIME ZONE {zone}" 3487 3488 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3489 this = self.sql(expression, "this") 3490 zone = self.sql(expression, "zone") 3491 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3492 3493 def add_sql(self, expression: exp.Add) -> str: 3494 return self.binary(expression, "+") 3495 3496 def and_sql( 3497 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3498 ) -> str: 3499 return self.connector_sql(expression, "AND", stack) 3500 3501 def or_sql( 3502 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3503 ) -> str: 3504 return self.connector_sql(expression, "OR", stack) 3505 3506 def xor_sql( 3507 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3508 ) -> str: 3509 return self.connector_sql(expression, "XOR", stack) 3510 3511 def connector_sql( 3512 self, 3513 expression: exp.Connector, 3514 op: str, 3515 stack: t.Optional[t.List[str | exp.Expression]] = None, 3516 ) -> str: 3517 if stack is not None: 3518 if expression.expressions: 3519 stack.append(self.expressions(expression, sep=f" {op} ")) 3520 else: 3521 stack.append(expression.right) 3522 if expression.comments and self.comments: 3523 for comment in expression.comments: 3524 if comment: 3525 op += f" /*{self.sanitize_comment(comment)}*/" 3526 stack.extend((op, expression.left)) 3527 return op 3528 3529 stack = [expression] 3530 sqls: t.List[str] = [] 3531 ops = set() 3532 3533 while stack: 3534 node = stack.pop() 3535 if isinstance(node, exp.Connector): 3536 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3537 else: 3538 sql = self.sql(node) 3539 if sqls and sqls[-1] in ops: 3540 sqls[-1] += f" {sql}" 3541 else: 3542 sqls.append(sql) 3543 3544 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3545 return sep.join(sqls) 3546 3547 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3548 return self.binary(expression, "&") 3549 3550 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3551 return self.binary(expression, "<<") 3552 3553 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3554 return f"~{self.sql(expression, 'this')}" 3555 3556 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3557 return self.binary(expression, "|") 3558 3559 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3560 return self.binary(expression, ">>") 3561 3562 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3563 return self.binary(expression, "^") 3564 3565 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3566 format_sql = self.sql(expression, "format") 3567 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3568 to_sql = self.sql(expression, "to") 3569 to_sql = f" {to_sql}" if to_sql else "" 3570 action = self.sql(expression, "action") 3571 action = f" {action}" if action else "" 3572 default = self.sql(expression, "default") 3573 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3574 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3575 3576 # Base implementation that excludes safe, zone, and target_type metadata args 3577 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3578 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3579 3580 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3581 zone = self.sql(expression, "this") 3582 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3583 3584 def collate_sql(self, expression: exp.Collate) -> str: 3585 if self.COLLATE_IS_FUNC: 3586 return self.function_fallback_sql(expression) 3587 return self.binary(expression, "COLLATE") 3588 3589 def command_sql(self, expression: exp.Command) -> str: 3590 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3591 3592 def comment_sql(self, expression: exp.Comment) -> str: 3593 this = self.sql(expression, "this") 3594 kind = expression.args["kind"] 3595 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3596 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3597 expression_sql = self.sql(expression, "expression") 3598 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3599 3600 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3601 this = self.sql(expression, "this") 3602 delete = " DELETE" if expression.args.get("delete") else "" 3603 recompress = self.sql(expression, "recompress") 3604 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3605 to_disk = self.sql(expression, "to_disk") 3606 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3607 to_volume = self.sql(expression, "to_volume") 3608 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3609 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3610 3611 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3612 where = self.sql(expression, "where") 3613 group = self.sql(expression, "group") 3614 aggregates = self.expressions(expression, key="aggregates") 3615 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3616 3617 if not (where or group or aggregates) and len(expression.expressions) == 1: 3618 return f"TTL {self.expressions(expression, flat=True)}" 3619 3620 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3621 3622 def transaction_sql(self, expression: exp.Transaction) -> str: 3623 modes = self.expressions(expression, key="modes") 3624 modes = f" {modes}" if modes else "" 3625 return f"BEGIN{modes}" 3626 3627 def commit_sql(self, expression: exp.Commit) -> str: 3628 chain = expression.args.get("chain") 3629 if chain is not None: 3630 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3631 3632 return f"COMMIT{chain or ''}" 3633 3634 def rollback_sql(self, expression: exp.Rollback) -> str: 3635 savepoint = expression.args.get("savepoint") 3636 savepoint = f" TO {savepoint}" if savepoint else "" 3637 return f"ROLLBACK{savepoint}" 3638 3639 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3640 this = self.sql(expression, "this") 3641 3642 dtype = self.sql(expression, "dtype") 3643 if dtype: 3644 collate = self.sql(expression, "collate") 3645 collate = f" COLLATE {collate}" if collate else "" 3646 using = self.sql(expression, "using") 3647 using = f" USING {using}" if using else "" 3648 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3649 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3650 3651 default = self.sql(expression, "default") 3652 if default: 3653 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3654 3655 comment = self.sql(expression, "comment") 3656 if comment: 3657 return f"ALTER COLUMN {this} COMMENT {comment}" 3658 3659 visible = expression.args.get("visible") 3660 if visible: 3661 return f"ALTER COLUMN {this} SET {visible}" 3662 3663 allow_null = expression.args.get("allow_null") 3664 drop = expression.args.get("drop") 3665 3666 if not drop and not allow_null: 3667 self.unsupported("Unsupported ALTER COLUMN syntax") 3668 3669 if allow_null is not None: 3670 keyword = "DROP" if drop else "SET" 3671 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3672 3673 return f"ALTER COLUMN {this} DROP DEFAULT" 3674 3675 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3676 this = self.sql(expression, "this") 3677 3678 visible = expression.args.get("visible") 3679 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3680 3681 return f"ALTER INDEX {this} {visible_sql}" 3682 3683 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3684 this = self.sql(expression, "this") 3685 if not isinstance(expression.this, exp.Var): 3686 this = f"KEY DISTKEY {this}" 3687 return f"ALTER DISTSTYLE {this}" 3688 3689 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3690 compound = " COMPOUND" if expression.args.get("compound") else "" 3691 this = self.sql(expression, "this") 3692 expressions = self.expressions(expression, flat=True) 3693 expressions = f"({expressions})" if expressions else "" 3694 return f"ALTER{compound} SORTKEY {this or expressions}" 3695 3696 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3697 if not self.RENAME_TABLE_WITH_DB: 3698 # Remove db from tables 3699 expression = expression.transform( 3700 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3701 ).assert_is(exp.AlterRename) 3702 this = self.sql(expression, "this") 3703 to_kw = " TO" if include_to else "" 3704 return f"RENAME{to_kw} {this}" 3705 3706 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3707 exists = " IF EXISTS" if expression.args.get("exists") else "" 3708 old_column = self.sql(expression, "this") 3709 new_column = self.sql(expression, "to") 3710 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3711 3712 def alterset_sql(self, expression: exp.AlterSet) -> str: 3713 exprs = self.expressions(expression, flat=True) 3714 if self.ALTER_SET_WRAPPED: 3715 exprs = f"({exprs})" 3716 3717 return f"SET {exprs}" 3718 3719 def alter_sql(self, expression: exp.Alter) -> str: 3720 actions = expression.args["actions"] 3721 3722 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3723 actions[0], exp.ColumnDef 3724 ): 3725 actions_sql = self.expressions(expression, key="actions", flat=True) 3726 actions_sql = f"ADD {actions_sql}" 3727 else: 3728 actions_list = [] 3729 for action in actions: 3730 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3731 action_sql = self.add_column_sql(action) 3732 else: 3733 action_sql = self.sql(action) 3734 if isinstance(action, exp.Query): 3735 action_sql = f"AS {action_sql}" 3736 3737 actions_list.append(action_sql) 3738 3739 actions_sql = self.format_args(*actions_list).lstrip("\n") 3740 3741 exists = " IF EXISTS" if expression.args.get("exists") else "" 3742 on_cluster = self.sql(expression, "cluster") 3743 on_cluster = f" {on_cluster}" if on_cluster else "" 3744 only = " ONLY" if expression.args.get("only") else "" 3745 options = self.expressions(expression, key="options") 3746 options = f", {options}" if options else "" 3747 kind = self.sql(expression, "kind") 3748 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3749 check = " WITH CHECK" if expression.args.get("check") else "" 3750 cascade = ( 3751 " CASCADE" 3752 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3753 else "" 3754 ) 3755 this = self.sql(expression, "this") 3756 this = f" {this}" if this else "" 3757 3758 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 3759 3760 def altersession_sql(self, expression: exp.AlterSession) -> str: 3761 items_sql = self.expressions(expression, flat=True) 3762 keyword = "UNSET" if expression.args.get("unset") else "SET" 3763 return f"{keyword} {items_sql}" 3764 3765 def add_column_sql(self, expression: exp.Expression) -> str: 3766 sql = self.sql(expression) 3767 if isinstance(expression, exp.Schema): 3768 column_text = " COLUMNS" 3769 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3770 column_text = " COLUMN" 3771 else: 3772 column_text = "" 3773 3774 return f"ADD{column_text} {sql}" 3775 3776 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3777 expressions = self.expressions(expression) 3778 exists = " IF EXISTS " if expression.args.get("exists") else " " 3779 return f"DROP{exists}{expressions}" 3780 3781 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3782 return f"ADD {self.expressions(expression, indent=False)}" 3783 3784 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3785 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3786 location = self.sql(expression, "location") 3787 location = f" {location}" if location else "" 3788 return f"ADD {exists}{self.sql(expression.this)}{location}" 3789 3790 def distinct_sql(self, expression: exp.Distinct) -> str: 3791 this = self.expressions(expression, flat=True) 3792 3793 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3794 case = exp.case() 3795 for arg in expression.expressions: 3796 case = case.when(arg.is_(exp.null()), exp.null()) 3797 this = self.sql(case.else_(f"({this})")) 3798 3799 this = f" {this}" if this else "" 3800 3801 on = self.sql(expression, "on") 3802 on = f" ON {on}" if on else "" 3803 return f"DISTINCT{this}{on}" 3804 3805 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3806 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3807 3808 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3809 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3810 3811 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3812 this_sql = self.sql(expression, "this") 3813 expression_sql = self.sql(expression, "expression") 3814 kind = "MAX" if expression.args.get("max") else "MIN" 3815 return f"{this_sql} HAVING {kind} {expression_sql}" 3816 3817 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3818 return self.sql( 3819 exp.Cast( 3820 this=exp.Div(this=expression.this, expression=expression.expression), 3821 to=exp.DataType(this=exp.DataType.Type.INT), 3822 ) 3823 ) 3824 3825 def dpipe_sql(self, expression: exp.DPipe) -> str: 3826 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3827 return self.func( 3828 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3829 ) 3830 return self.binary(expression, "||") 3831 3832 def div_sql(self, expression: exp.Div) -> str: 3833 l, r = expression.left, expression.right 3834 3835 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3836 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3837 3838 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3839 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3840 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3841 3842 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3843 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3844 return self.sql( 3845 exp.cast( 3846 l / r, 3847 to=exp.DataType.Type.BIGINT, 3848 ) 3849 ) 3850 3851 return self.binary(expression, "/") 3852 3853 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3854 n = exp._wrap(expression.this, exp.Binary) 3855 d = exp._wrap(expression.expression, exp.Binary) 3856 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3857 3858 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3859 return self.binary(expression, "OVERLAPS") 3860 3861 def distance_sql(self, expression: exp.Distance) -> str: 3862 return self.binary(expression, "<->") 3863 3864 def dot_sql(self, expression: exp.Dot) -> str: 3865 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3866 3867 def eq_sql(self, expression: exp.EQ) -> str: 3868 return self.binary(expression, "=") 3869 3870 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3871 return self.binary(expression, ":=") 3872 3873 def escape_sql(self, expression: exp.Escape) -> str: 3874 return self.binary(expression, "ESCAPE") 3875 3876 def glob_sql(self, expression: exp.Glob) -> str: 3877 return self.binary(expression, "GLOB") 3878 3879 def gt_sql(self, expression: exp.GT) -> str: 3880 return self.binary(expression, ">") 3881 3882 def gte_sql(self, expression: exp.GTE) -> str: 3883 return self.binary(expression, ">=") 3884 3885 def is_sql(self, expression: exp.Is) -> str: 3886 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3887 return self.sql( 3888 expression.this if expression.expression.this else exp.not_(expression.this) 3889 ) 3890 return self.binary(expression, "IS") 3891 3892 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3893 this = expression.this 3894 rhs = expression.expression 3895 3896 if isinstance(expression, exp.Like): 3897 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3898 op = "LIKE" 3899 else: 3900 exp_class = exp.ILike 3901 op = "ILIKE" 3902 3903 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3904 exprs = rhs.this.unnest() 3905 3906 if isinstance(exprs, exp.Tuple): 3907 exprs = exprs.expressions 3908 3909 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3910 3911 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3912 for expr in exprs[1:]: 3913 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3914 3915 parent = expression.parent 3916 if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition): 3917 like_expr = exp.paren(like_expr, copy=False) 3918 3919 return self.sql(like_expr) 3920 3921 return self.binary(expression, op) 3922 3923 def like_sql(self, expression: exp.Like) -> str: 3924 return self._like_sql(expression) 3925 3926 def ilike_sql(self, expression: exp.ILike) -> str: 3927 return self._like_sql(expression) 3928 3929 def match_sql(self, expression: exp.Match) -> str: 3930 return self.binary(expression, "MATCH") 3931 3932 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3933 return self.binary(expression, "SIMILAR TO") 3934 3935 def lt_sql(self, expression: exp.LT) -> str: 3936 return self.binary(expression, "<") 3937 3938 def lte_sql(self, expression: exp.LTE) -> str: 3939 return self.binary(expression, "<=") 3940 3941 def mod_sql(self, expression: exp.Mod) -> str: 3942 return self.binary(expression, "%") 3943 3944 def mul_sql(self, expression: exp.Mul) -> str: 3945 return self.binary(expression, "*") 3946 3947 def neq_sql(self, expression: exp.NEQ) -> str: 3948 return self.binary(expression, "<>") 3949 3950 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3951 return self.binary(expression, "IS NOT DISTINCT FROM") 3952 3953 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3954 return self.binary(expression, "IS DISTINCT FROM") 3955 3956 def sub_sql(self, expression: exp.Sub) -> str: 3957 return self.binary(expression, "-") 3958 3959 def trycast_sql(self, expression: exp.TryCast) -> str: 3960 return self.cast_sql(expression, safe_prefix="TRY_") 3961 3962 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3963 return self.cast_sql(expression) 3964 3965 def try_sql(self, expression: exp.Try) -> str: 3966 if not self.TRY_SUPPORTED: 3967 self.unsupported("Unsupported TRY function") 3968 return self.sql(expression, "this") 3969 3970 return self.func("TRY", expression.this) 3971 3972 def log_sql(self, expression: exp.Log) -> str: 3973 this = expression.this 3974 expr = expression.expression 3975 3976 if self.dialect.LOG_BASE_FIRST is False: 3977 this, expr = expr, this 3978 elif self.dialect.LOG_BASE_FIRST is None and expr: 3979 if this.name in ("2", "10"): 3980 return self.func(f"LOG{this.name}", expr) 3981 3982 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3983 3984 return self.func("LOG", this, expr) 3985 3986 def use_sql(self, expression: exp.Use) -> str: 3987 kind = self.sql(expression, "kind") 3988 kind = f" {kind}" if kind else "" 3989 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3990 this = f" {this}" if this else "" 3991 return f"USE{kind}{this}" 3992 3993 def binary(self, expression: exp.Binary, op: str) -> str: 3994 sqls: t.List[str] = [] 3995 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3996 binary_type = type(expression) 3997 3998 while stack: 3999 node = stack.pop() 4000 4001 if type(node) is binary_type: 4002 op_func = node.args.get("operator") 4003 if op_func: 4004 op = f"OPERATOR({self.sql(op_func)})" 4005 4006 stack.append(node.right) 4007 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4008 stack.append(node.left) 4009 else: 4010 sqls.append(self.sql(node)) 4011 4012 return "".join(sqls) 4013 4014 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4015 to_clause = self.sql(expression, "to") 4016 if to_clause: 4017 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4018 4019 return self.function_fallback_sql(expression) 4020 4021 def function_fallback_sql(self, expression: exp.Func) -> str: 4022 args = [] 4023 4024 for key in expression.arg_types: 4025 arg_value = expression.args.get(key) 4026 4027 if isinstance(arg_value, list): 4028 for value in arg_value: 4029 args.append(value) 4030 elif arg_value is not None: 4031 args.append(arg_value) 4032 4033 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4034 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4035 else: 4036 name = expression.sql_name() 4037 4038 return self.func(name, *args) 4039 4040 def func( 4041 self, 4042 name: str, 4043 *args: t.Optional[exp.Expression | str], 4044 prefix: str = "(", 4045 suffix: str = ")", 4046 normalize: bool = True, 4047 ) -> str: 4048 name = self.normalize_func(name) if normalize else name 4049 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4050 4051 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 4052 arg_sqls = tuple( 4053 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4054 ) 4055 if self.pretty and self.too_wide(arg_sqls): 4056 return self.indent( 4057 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4058 ) 4059 return sep.join(arg_sqls) 4060 4061 def too_wide(self, args: t.Iterable) -> bool: 4062 return sum(len(arg) for arg in args) > self.max_text_width 4063 4064 def format_time( 4065 self, 4066 expression: exp.Expression, 4067 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 4068 inverse_time_trie: t.Optional[t.Dict] = None, 4069 ) -> t.Optional[str]: 4070 return format_time( 4071 self.sql(expression, "format"), 4072 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4073 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4074 ) 4075 4076 def expressions( 4077 self, 4078 expression: t.Optional[exp.Expression] = None, 4079 key: t.Optional[str] = None, 4080 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 4081 flat: bool = False, 4082 indent: bool = True, 4083 skip_first: bool = False, 4084 skip_last: bool = False, 4085 sep: str = ", ", 4086 prefix: str = "", 4087 dynamic: bool = False, 4088 new_line: bool = False, 4089 ) -> str: 4090 expressions = expression.args.get(key or "expressions") if expression else sqls 4091 4092 if not expressions: 4093 return "" 4094 4095 if flat: 4096 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4097 4098 num_sqls = len(expressions) 4099 result_sqls = [] 4100 4101 for i, e in enumerate(expressions): 4102 sql = self.sql(e, comment=False) 4103 if not sql: 4104 continue 4105 4106 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 4107 4108 if self.pretty: 4109 if self.leading_comma: 4110 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4111 else: 4112 result_sqls.append( 4113 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4114 ) 4115 else: 4116 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4117 4118 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4119 if new_line: 4120 result_sqls.insert(0, "") 4121 result_sqls.append("") 4122 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4123 else: 4124 result_sql = "".join(result_sqls) 4125 4126 return ( 4127 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4128 if indent 4129 else result_sql 4130 ) 4131 4132 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4133 flat = flat or isinstance(expression.parent, exp.Properties) 4134 expressions_sql = self.expressions(expression, flat=flat) 4135 if flat: 4136 return f"{op} {expressions_sql}" 4137 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4138 4139 def naked_property(self, expression: exp.Property) -> str: 4140 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4141 if not property_name: 4142 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4143 return f"{property_name} {self.sql(expression, 'this')}" 4144 4145 def tag_sql(self, expression: exp.Tag) -> str: 4146 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4147 4148 def token_sql(self, token_type: TokenType) -> str: 4149 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4150 4151 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4152 this = self.sql(expression, "this") 4153 expressions = self.no_identify(self.expressions, expression) 4154 expressions = ( 4155 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4156 ) 4157 return f"{this}{expressions}" if expressions.strip() != "" else this 4158 4159 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4160 this = self.sql(expression, "this") 4161 expressions = self.expressions(expression, flat=True) 4162 return f"{this}({expressions})" 4163 4164 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4165 return self.binary(expression, "=>") 4166 4167 def when_sql(self, expression: exp.When) -> str: 4168 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4169 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4170 condition = self.sql(expression, "condition") 4171 condition = f" AND {condition}" if condition else "" 4172 4173 then_expression = expression.args.get("then") 4174 if isinstance(then_expression, exp.Insert): 4175 this = self.sql(then_expression, "this") 4176 this = f"INSERT {this}" if this else "INSERT" 4177 then = self.sql(then_expression, "expression") 4178 then = f"{this} VALUES {then}" if then else this 4179 elif isinstance(then_expression, exp.Update): 4180 if isinstance(then_expression.args.get("expressions"), exp.Star): 4181 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4182 else: 4183 expressions_sql = self.expressions(then_expression) 4184 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4185 4186 else: 4187 then = self.sql(then_expression) 4188 return f"WHEN {matched}{source}{condition} THEN {then}" 4189 4190 def whens_sql(self, expression: exp.Whens) -> str: 4191 return self.expressions(expression, sep=" ", indent=False) 4192 4193 def merge_sql(self, expression: exp.Merge) -> str: 4194 table = expression.this 4195 table_alias = "" 4196 4197 hints = table.args.get("hints") 4198 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4199 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4200 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4201 4202 this = self.sql(table) 4203 using = f"USING {self.sql(expression, 'using')}" 4204 whens = self.sql(expression, "whens") 4205 4206 on = self.sql(expression, "on") 4207 on = f"ON {on}" if on else "" 4208 4209 if not on: 4210 on = self.expressions(expression, key="using_cond") 4211 on = f"USING ({on})" if on else "" 4212 4213 returning = self.sql(expression, "returning") 4214 if returning: 4215 whens = f"{whens}{returning}" 4216 4217 sep = self.sep() 4218 4219 return self.prepend_ctes( 4220 expression, 4221 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4222 ) 4223 4224 @unsupported_args("format") 4225 def tochar_sql(self, expression: exp.ToChar) -> str: 4226 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4227 4228 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4229 if not self.SUPPORTS_TO_NUMBER: 4230 self.unsupported("Unsupported TO_NUMBER function") 4231 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4232 4233 fmt = expression.args.get("format") 4234 if not fmt: 4235 self.unsupported("Conversion format is required for TO_NUMBER") 4236 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4237 4238 return self.func("TO_NUMBER", expression.this, fmt) 4239 4240 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4241 this = self.sql(expression, "this") 4242 kind = self.sql(expression, "kind") 4243 settings_sql = self.expressions(expression, key="settings", sep=" ") 4244 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4245 return f"{this}({kind}{args})" 4246 4247 def dictrange_sql(self, expression: exp.DictRange) -> str: 4248 this = self.sql(expression, "this") 4249 max = self.sql(expression, "max") 4250 min = self.sql(expression, "min") 4251 return f"{this}(MIN {min} MAX {max})" 4252 4253 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4254 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4255 4256 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4257 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4258 4259 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4260 def uniquekeyproperty_sql( 4261 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4262 ) -> str: 4263 return f"{prefix} ({self.expressions(expression, flat=True)})" 4264 4265 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4266 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4267 expressions = self.expressions(expression, flat=True) 4268 expressions = f" {self.wrap(expressions)}" if expressions else "" 4269 buckets = self.sql(expression, "buckets") 4270 kind = self.sql(expression, "kind") 4271 buckets = f" BUCKETS {buckets}" if buckets else "" 4272 order = self.sql(expression, "order") 4273 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4274 4275 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4276 return "" 4277 4278 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4279 expressions = self.expressions(expression, key="expressions", flat=True) 4280 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4281 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4282 buckets = self.sql(expression, "buckets") 4283 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4284 4285 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4286 this = self.sql(expression, "this") 4287 having = self.sql(expression, "having") 4288 4289 if having: 4290 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4291 4292 return self.func("ANY_VALUE", this) 4293 4294 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4295 transform = self.func("TRANSFORM", *expression.expressions) 4296 row_format_before = self.sql(expression, "row_format_before") 4297 row_format_before = f" {row_format_before}" if row_format_before else "" 4298 record_writer = self.sql(expression, "record_writer") 4299 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4300 using = f" USING {self.sql(expression, 'command_script')}" 4301 schema = self.sql(expression, "schema") 4302 schema = f" AS {schema}" if schema else "" 4303 row_format_after = self.sql(expression, "row_format_after") 4304 row_format_after = f" {row_format_after}" if row_format_after else "" 4305 record_reader = self.sql(expression, "record_reader") 4306 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4307 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4308 4309 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4310 key_block_size = self.sql(expression, "key_block_size") 4311 if key_block_size: 4312 return f"KEY_BLOCK_SIZE = {key_block_size}" 4313 4314 using = self.sql(expression, "using") 4315 if using: 4316 return f"USING {using}" 4317 4318 parser = self.sql(expression, "parser") 4319 if parser: 4320 return f"WITH PARSER {parser}" 4321 4322 comment = self.sql(expression, "comment") 4323 if comment: 4324 return f"COMMENT {comment}" 4325 4326 visible = expression.args.get("visible") 4327 if visible is not None: 4328 return "VISIBLE" if visible else "INVISIBLE" 4329 4330 engine_attr = self.sql(expression, "engine_attr") 4331 if engine_attr: 4332 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4333 4334 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4335 if secondary_engine_attr: 4336 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4337 4338 self.unsupported("Unsupported index constraint option.") 4339 return "" 4340 4341 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4342 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4343 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4344 4345 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4346 kind = self.sql(expression, "kind") 4347 kind = f"{kind} INDEX" if kind else "INDEX" 4348 this = self.sql(expression, "this") 4349 this = f" {this}" if this else "" 4350 index_type = self.sql(expression, "index_type") 4351 index_type = f" USING {index_type}" if index_type else "" 4352 expressions = self.expressions(expression, flat=True) 4353 expressions = f" ({expressions})" if expressions else "" 4354 options = self.expressions(expression, key="options", sep=" ") 4355 options = f" {options}" if options else "" 4356 return f"{kind}{this}{index_type}{expressions}{options}" 4357 4358 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4359 if self.NVL2_SUPPORTED: 4360 return self.function_fallback_sql(expression) 4361 4362 case = exp.Case().when( 4363 expression.this.is_(exp.null()).not_(copy=False), 4364 expression.args["true"], 4365 copy=False, 4366 ) 4367 else_cond = expression.args.get("false") 4368 if else_cond: 4369 case.else_(else_cond, copy=False) 4370 4371 return self.sql(case) 4372 4373 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4374 this = self.sql(expression, "this") 4375 expr = self.sql(expression, "expression") 4376 position = self.sql(expression, "position") 4377 position = f", {position}" if position else "" 4378 iterator = self.sql(expression, "iterator") 4379 condition = self.sql(expression, "condition") 4380 condition = f" IF {condition}" if condition else "" 4381 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4382 4383 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4384 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4385 4386 def opclass_sql(self, expression: exp.Opclass) -> str: 4387 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4388 4389 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4390 model = self.sql(expression, "this") 4391 model = f"MODEL {model}" 4392 expr = expression.expression 4393 if expr: 4394 expr_sql = self.sql(expression, "expression") 4395 expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql 4396 else: 4397 expr_sql = None 4398 4399 parameters = self.sql(expression, "params_struct") or None 4400 4401 return self.func(name, model, expr_sql, parameters) 4402 4403 def predict_sql(self, expression: exp.Predict) -> str: 4404 return self._ml_sql(expression, "PREDICT") 4405 4406 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4407 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4408 return self._ml_sql(expression, name) 4409 4410 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4411 return self._ml_sql(expression, "TRANSLATE") 4412 4413 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4414 return self._ml_sql(expression, "FORECAST") 4415 4416 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4417 this_sql = self.sql(expression, "this") 4418 if isinstance(expression.this, exp.Table): 4419 this_sql = f"TABLE {this_sql}" 4420 4421 return self.func( 4422 "FEATURES_AT_TIME", 4423 this_sql, 4424 expression.args.get("time"), 4425 expression.args.get("num_rows"), 4426 expression.args.get("ignore_feature_nulls"), 4427 ) 4428 4429 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4430 this_sql = self.sql(expression, "this") 4431 if isinstance(expression.this, exp.Table): 4432 this_sql = f"TABLE {this_sql}" 4433 4434 query_table = self.sql(expression, "query_table") 4435 if isinstance(expression.args["query_table"], exp.Table): 4436 query_table = f"TABLE {query_table}" 4437 4438 return self.func( 4439 "VECTOR_SEARCH", 4440 this_sql, 4441 expression.args.get("column_to_search"), 4442 query_table, 4443 expression.args.get("query_column_to_search"), 4444 expression.args.get("top_k"), 4445 expression.args.get("distance_type"), 4446 expression.args.get("options"), 4447 ) 4448 4449 def forin_sql(self, expression: exp.ForIn) -> str: 4450 this = self.sql(expression, "this") 4451 expression_sql = self.sql(expression, "expression") 4452 return f"FOR {this} DO {expression_sql}" 4453 4454 def refresh_sql(self, expression: exp.Refresh) -> str: 4455 this = self.sql(expression, "this") 4456 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4457 return f"REFRESH {kind}{this}" 4458 4459 def toarray_sql(self, expression: exp.ToArray) -> str: 4460 arg = expression.this 4461 if not arg.type: 4462 from sqlglot.optimizer.annotate_types import annotate_types 4463 4464 arg = annotate_types(arg, dialect=self.dialect) 4465 4466 if arg.is_type(exp.DataType.Type.ARRAY): 4467 return self.sql(arg) 4468 4469 cond_for_null = arg.is_(exp.null()) 4470 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4471 4472 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4473 this = expression.this 4474 time_format = self.format_time(expression) 4475 4476 if time_format: 4477 return self.sql( 4478 exp.cast( 4479 exp.StrToTime(this=this, format=expression.args["format"]), 4480 exp.DataType.Type.TIME, 4481 ) 4482 ) 4483 4484 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4485 return self.sql(this) 4486 4487 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4488 4489 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4490 this = expression.this 4491 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4492 return self.sql(this) 4493 4494 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4495 4496 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4497 this = expression.this 4498 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4499 return self.sql(this) 4500 4501 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4502 4503 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4504 this = expression.this 4505 time_format = self.format_time(expression) 4506 4507 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4508 return self.sql( 4509 exp.cast( 4510 exp.StrToTime(this=this, format=expression.args["format"]), 4511 exp.DataType.Type.DATE, 4512 ) 4513 ) 4514 4515 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4516 return self.sql(this) 4517 4518 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4519 4520 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4521 return self.sql( 4522 exp.func( 4523 "DATEDIFF", 4524 expression.this, 4525 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4526 "day", 4527 ) 4528 ) 4529 4530 def lastday_sql(self, expression: exp.LastDay) -> str: 4531 if self.LAST_DAY_SUPPORTS_DATE_PART: 4532 return self.function_fallback_sql(expression) 4533 4534 unit = expression.text("unit") 4535 if unit and unit != "MONTH": 4536 self.unsupported("Date parts are not supported in LAST_DAY.") 4537 4538 return self.func("LAST_DAY", expression.this) 4539 4540 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4541 from sqlglot.dialects.dialect import unit_to_str 4542 4543 return self.func( 4544 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4545 ) 4546 4547 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4548 if self.CAN_IMPLEMENT_ARRAY_ANY: 4549 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4550 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4551 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4552 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4553 4554 from sqlglot.dialects import Dialect 4555 4556 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4557 if self.dialect.__class__ != Dialect: 4558 self.unsupported("ARRAY_ANY is unsupported") 4559 4560 return self.function_fallback_sql(expression) 4561 4562 def struct_sql(self, expression: exp.Struct) -> str: 4563 expression.set( 4564 "expressions", 4565 [ 4566 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4567 if isinstance(e, exp.PropertyEQ) 4568 else e 4569 for e in expression.expressions 4570 ], 4571 ) 4572 4573 return self.function_fallback_sql(expression) 4574 4575 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4576 low = self.sql(expression, "this") 4577 high = self.sql(expression, "expression") 4578 4579 return f"{low} TO {high}" 4580 4581 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4582 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4583 tables = f" {self.expressions(expression)}" 4584 4585 exists = " IF EXISTS" if expression.args.get("exists") else "" 4586 4587 on_cluster = self.sql(expression, "cluster") 4588 on_cluster = f" {on_cluster}" if on_cluster else "" 4589 4590 identity = self.sql(expression, "identity") 4591 identity = f" {identity} IDENTITY" if identity else "" 4592 4593 option = self.sql(expression, "option") 4594 option = f" {option}" if option else "" 4595 4596 partition = self.sql(expression, "partition") 4597 partition = f" {partition}" if partition else "" 4598 4599 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4600 4601 # This transpiles T-SQL's CONVERT function 4602 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4603 def convert_sql(self, expression: exp.Convert) -> str: 4604 to = expression.this 4605 value = expression.expression 4606 style = expression.args.get("style") 4607 safe = expression.args.get("safe") 4608 strict = expression.args.get("strict") 4609 4610 if not to or not value: 4611 return "" 4612 4613 # Retrieve length of datatype and override to default if not specified 4614 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4615 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4616 4617 transformed: t.Optional[exp.Expression] = None 4618 cast = exp.Cast if strict else exp.TryCast 4619 4620 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4621 if isinstance(style, exp.Literal) and style.is_int: 4622 from sqlglot.dialects.tsql import TSQL 4623 4624 style_value = style.name 4625 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4626 if not converted_style: 4627 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4628 4629 fmt = exp.Literal.string(converted_style) 4630 4631 if to.this == exp.DataType.Type.DATE: 4632 transformed = exp.StrToDate(this=value, format=fmt) 4633 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4634 transformed = exp.StrToTime(this=value, format=fmt) 4635 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4636 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4637 elif to.this == exp.DataType.Type.TEXT: 4638 transformed = exp.TimeToStr(this=value, format=fmt) 4639 4640 if not transformed: 4641 transformed = cast(this=value, to=to, safe=safe) 4642 4643 return self.sql(transformed) 4644 4645 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4646 this = expression.this 4647 if isinstance(this, exp.JSONPathWildcard): 4648 this = self.json_path_part(this) 4649 return f".{this}" if this else "" 4650 4651 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4652 return f".{this}" 4653 4654 this = self.json_path_part(this) 4655 return ( 4656 f"[{this}]" 4657 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4658 else f".{this}" 4659 ) 4660 4661 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4662 this = self.json_path_part(expression.this) 4663 return f"[{this}]" if this else "" 4664 4665 def _simplify_unless_literal(self, expression: E) -> E: 4666 if not isinstance(expression, exp.Literal): 4667 from sqlglot.optimizer.simplify import simplify 4668 4669 expression = simplify(expression, dialect=self.dialect) 4670 4671 return expression 4672 4673 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4674 this = expression.this 4675 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4676 self.unsupported( 4677 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4678 ) 4679 return self.sql(this) 4680 4681 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4682 # The first modifier here will be the one closest to the AggFunc's arg 4683 mods = sorted( 4684 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4685 key=lambda x: 0 4686 if isinstance(x, exp.HavingMax) 4687 else (1 if isinstance(x, exp.Order) else 2), 4688 ) 4689 4690 if mods: 4691 mod = mods[0] 4692 this = expression.__class__(this=mod.this.copy()) 4693 this.meta["inline"] = True 4694 mod.this.replace(this) 4695 return self.sql(expression.this) 4696 4697 agg_func = expression.find(exp.AggFunc) 4698 4699 if agg_func: 4700 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4701 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4702 4703 return f"{self.sql(expression, 'this')} {text}" 4704 4705 def _replace_line_breaks(self, string: str) -> str: 4706 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4707 if self.pretty: 4708 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4709 return string 4710 4711 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4712 option = self.sql(expression, "this") 4713 4714 if expression.expressions: 4715 upper = option.upper() 4716 4717 # Snowflake FILE_FORMAT options are separated by whitespace 4718 sep = " " if upper == "FILE_FORMAT" else ", " 4719 4720 # Databricks copy/format options do not set their list of values with EQ 4721 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4722 values = self.expressions(expression, flat=True, sep=sep) 4723 return f"{option}{op}({values})" 4724 4725 value = self.sql(expression, "expression") 4726 4727 if not value: 4728 return option 4729 4730 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4731 4732 return f"{option}{op}{value}" 4733 4734 def credentials_sql(self, expression: exp.Credentials) -> str: 4735 cred_expr = expression.args.get("credentials") 4736 if isinstance(cred_expr, exp.Literal): 4737 # Redshift case: CREDENTIALS <string> 4738 credentials = self.sql(expression, "credentials") 4739 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4740 else: 4741 # Snowflake case: CREDENTIALS = (...) 4742 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4743 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4744 4745 storage = self.sql(expression, "storage") 4746 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4747 4748 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4749 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4750 4751 iam_role = self.sql(expression, "iam_role") 4752 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4753 4754 region = self.sql(expression, "region") 4755 region = f" REGION {region}" if region else "" 4756 4757 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4758 4759 def copy_sql(self, expression: exp.Copy) -> str: 4760 this = self.sql(expression, "this") 4761 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4762 4763 credentials = self.sql(expression, "credentials") 4764 credentials = self.seg(credentials) if credentials else "" 4765 files = self.expressions(expression, key="files", flat=True) 4766 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4767 4768 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4769 params = self.expressions( 4770 expression, 4771 key="params", 4772 sep=sep, 4773 new_line=True, 4774 skip_last=True, 4775 skip_first=True, 4776 indent=self.COPY_PARAMS_ARE_WRAPPED, 4777 ) 4778 4779 if params: 4780 if self.COPY_PARAMS_ARE_WRAPPED: 4781 params = f" WITH ({params})" 4782 elif not self.pretty and (files or credentials): 4783 params = f" {params}" 4784 4785 return f"COPY{this}{kind} {files}{credentials}{params}" 4786 4787 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4788 return "" 4789 4790 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4791 on_sql = "ON" if expression.args.get("on") else "OFF" 4792 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4793 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4794 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4795 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4796 4797 if filter_col or retention_period: 4798 on_sql = self.func("ON", filter_col, retention_period) 4799 4800 return f"DATA_DELETION={on_sql}" 4801 4802 def maskingpolicycolumnconstraint_sql( 4803 self, expression: exp.MaskingPolicyColumnConstraint 4804 ) -> str: 4805 this = self.sql(expression, "this") 4806 expressions = self.expressions(expression, flat=True) 4807 expressions = f" USING ({expressions})" if expressions else "" 4808 return f"MASKING POLICY {this}{expressions}" 4809 4810 def gapfill_sql(self, expression: exp.GapFill) -> str: 4811 this = self.sql(expression, "this") 4812 this = f"TABLE {this}" 4813 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4814 4815 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4816 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4817 4818 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4819 this = self.sql(expression, "this") 4820 expr = expression.expression 4821 4822 if isinstance(expr, exp.Func): 4823 # T-SQL's CLR functions are case sensitive 4824 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4825 else: 4826 expr = self.sql(expression, "expression") 4827 4828 return self.scope_resolution(expr, this) 4829 4830 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4831 if self.PARSE_JSON_NAME is None: 4832 return self.sql(expression.this) 4833 4834 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4835 4836 def rand_sql(self, expression: exp.Rand) -> str: 4837 lower = self.sql(expression, "lower") 4838 upper = self.sql(expression, "upper") 4839 4840 if lower and upper: 4841 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4842 return self.func("RAND", expression.this) 4843 4844 def changes_sql(self, expression: exp.Changes) -> str: 4845 information = self.sql(expression, "information") 4846 information = f"INFORMATION => {information}" 4847 at_before = self.sql(expression, "at_before") 4848 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4849 end = self.sql(expression, "end") 4850 end = f"{self.seg('')}{end}" if end else "" 4851 4852 return f"CHANGES ({information}){at_before}{end}" 4853 4854 def pad_sql(self, expression: exp.Pad) -> str: 4855 prefix = "L" if expression.args.get("is_left") else "R" 4856 4857 fill_pattern = self.sql(expression, "fill_pattern") or None 4858 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4859 fill_pattern = "' '" 4860 4861 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4862 4863 def summarize_sql(self, expression: exp.Summarize) -> str: 4864 table = " TABLE" if expression.args.get("table") else "" 4865 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4866 4867 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4868 generate_series = exp.GenerateSeries(**expression.args) 4869 4870 parent = expression.parent 4871 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4872 parent = parent.parent 4873 4874 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4875 return self.sql(exp.Unnest(expressions=[generate_series])) 4876 4877 if isinstance(parent, exp.Select): 4878 self.unsupported("GenerateSeries projection unnesting is not supported.") 4879 4880 return self.sql(generate_series) 4881 4882 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4883 exprs = expression.expressions 4884 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4885 if len(exprs) == 0: 4886 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4887 else: 4888 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4889 else: 4890 rhs = self.expressions(expression) # type: ignore 4891 4892 return self.func(name, expression.this, rhs or None) 4893 4894 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4895 if self.SUPPORTS_CONVERT_TIMEZONE: 4896 return self.function_fallback_sql(expression) 4897 4898 source_tz = expression.args.get("source_tz") 4899 target_tz = expression.args.get("target_tz") 4900 timestamp = expression.args.get("timestamp") 4901 4902 if source_tz and timestamp: 4903 timestamp = exp.AtTimeZone( 4904 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4905 ) 4906 4907 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4908 4909 return self.sql(expr) 4910 4911 def json_sql(self, expression: exp.JSON) -> str: 4912 this = self.sql(expression, "this") 4913 this = f" {this}" if this else "" 4914 4915 _with = expression.args.get("with_") 4916 4917 if _with is None: 4918 with_sql = "" 4919 elif not _with: 4920 with_sql = " WITHOUT" 4921 else: 4922 with_sql = " WITH" 4923 4924 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4925 4926 return f"JSON{this}{with_sql}{unique_sql}" 4927 4928 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4929 def _generate_on_options(arg: t.Any) -> str: 4930 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4931 4932 path = self.sql(expression, "path") 4933 returning = self.sql(expression, "returning") 4934 returning = f" RETURNING {returning}" if returning else "" 4935 4936 on_condition = self.sql(expression, "on_condition") 4937 on_condition = f" {on_condition}" if on_condition else "" 4938 4939 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4940 4941 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4942 else_ = "ELSE " if expression.args.get("else_") else "" 4943 condition = self.sql(expression, "expression") 4944 condition = f"WHEN {condition} THEN " if condition else else_ 4945 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4946 return f"{condition}{insert}" 4947 4948 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4949 kind = self.sql(expression, "kind") 4950 expressions = self.seg(self.expressions(expression, sep=" ")) 4951 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4952 return res 4953 4954 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4955 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4956 empty = expression.args.get("empty") 4957 empty = ( 4958 f"DEFAULT {empty} ON EMPTY" 4959 if isinstance(empty, exp.Expression) 4960 else self.sql(expression, "empty") 4961 ) 4962 4963 error = expression.args.get("error") 4964 error = ( 4965 f"DEFAULT {error} ON ERROR" 4966 if isinstance(error, exp.Expression) 4967 else self.sql(expression, "error") 4968 ) 4969 4970 if error and empty: 4971 error = ( 4972 f"{empty} {error}" 4973 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4974 else f"{error} {empty}" 4975 ) 4976 empty = "" 4977 4978 null = self.sql(expression, "null") 4979 4980 return f"{empty}{error}{null}" 4981 4982 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4983 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4984 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4985 4986 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4987 this = self.sql(expression, "this") 4988 path = self.sql(expression, "path") 4989 4990 passing = self.expressions(expression, "passing") 4991 passing = f" PASSING {passing}" if passing else "" 4992 4993 on_condition = self.sql(expression, "on_condition") 4994 on_condition = f" {on_condition}" if on_condition else "" 4995 4996 path = f"{path}{passing}{on_condition}" 4997 4998 return self.func("JSON_EXISTS", this, path) 4999 5000 def _add_arrayagg_null_filter( 5001 self, 5002 array_agg_sql: str, 5003 array_agg_expr: exp.ArrayAgg, 5004 column_expr: exp.Expression, 5005 ) -> str: 5006 """ 5007 Add NULL filter to ARRAY_AGG if dialect requires it. 5008 5009 Args: 5010 array_agg_sql: The generated ARRAY_AGG SQL string 5011 array_agg_expr: The ArrayAgg expression node 5012 column_expr: The column/expression to filter (before ORDER BY wrapping) 5013 5014 Returns: 5015 SQL string with FILTER clause added if needed 5016 """ 5017 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5018 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5019 if not ( 5020 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5021 ): 5022 return array_agg_sql 5023 5024 parent = array_agg_expr.parent 5025 if isinstance(parent, exp.Filter): 5026 parent_cond = parent.expression.this 5027 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5028 elif column_expr.find(exp.Column): 5029 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5030 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5031 this_sql = ( 5032 self.expressions(column_expr) 5033 if isinstance(column_expr, exp.Distinct) 5034 else self.sql(column_expr) 5035 ) 5036 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5037 5038 return array_agg_sql 5039 5040 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5041 array_agg = self.function_fallback_sql(expression) 5042 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5043 5044 def slice_sql(self, expression: exp.Slice) -> str: 5045 step = self.sql(expression, "step") 5046 end = self.sql(expression.expression) 5047 begin = self.sql(expression.this) 5048 5049 sql = f"{end}:{step}" if step else end 5050 return f"{begin}:{sql}" if sql else f"{begin}:" 5051 5052 def apply_sql(self, expression: exp.Apply) -> str: 5053 this = self.sql(expression, "this") 5054 expr = self.sql(expression, "expression") 5055 5056 return f"{this} APPLY({expr})" 5057 5058 def _grant_or_revoke_sql( 5059 self, 5060 expression: exp.Grant | exp.Revoke, 5061 keyword: str, 5062 preposition: str, 5063 grant_option_prefix: str = "", 5064 grant_option_suffix: str = "", 5065 ) -> str: 5066 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5067 5068 kind = self.sql(expression, "kind") 5069 kind = f" {kind}" if kind else "" 5070 5071 securable = self.sql(expression, "securable") 5072 securable = f" {securable}" if securable else "" 5073 5074 principals = self.expressions(expression, key="principals", flat=True) 5075 5076 if not expression.args.get("grant_option"): 5077 grant_option_prefix = grant_option_suffix = "" 5078 5079 # cascade for revoke only 5080 cascade = self.sql(expression, "cascade") 5081 cascade = f" {cascade}" if cascade else "" 5082 5083 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5084 5085 def grant_sql(self, expression: exp.Grant) -> str: 5086 return self._grant_or_revoke_sql( 5087 expression, 5088 keyword="GRANT", 5089 preposition="TO", 5090 grant_option_suffix=" WITH GRANT OPTION", 5091 ) 5092 5093 def revoke_sql(self, expression: exp.Revoke) -> str: 5094 return self._grant_or_revoke_sql( 5095 expression, 5096 keyword="REVOKE", 5097 preposition="FROM", 5098 grant_option_prefix="GRANT OPTION FOR ", 5099 ) 5100 5101 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 5102 this = self.sql(expression, "this") 5103 columns = self.expressions(expression, flat=True) 5104 columns = f"({columns})" if columns else "" 5105 5106 return f"{this}{columns}" 5107 5108 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 5109 this = self.sql(expression, "this") 5110 5111 kind = self.sql(expression, "kind") 5112 kind = f"{kind} " if kind else "" 5113 5114 return f"{kind}{this}" 5115 5116 def columns_sql(self, expression: exp.Columns): 5117 func = self.function_fallback_sql(expression) 5118 if expression.args.get("unpack"): 5119 func = f"*{func}" 5120 5121 return func 5122 5123 def overlay_sql(self, expression: exp.Overlay): 5124 this = self.sql(expression, "this") 5125 expr = self.sql(expression, "expression") 5126 from_sql = self.sql(expression, "from_") 5127 for_sql = self.sql(expression, "for_") 5128 for_sql = f" FOR {for_sql}" if for_sql else "" 5129 5130 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5131 5132 @unsupported_args("format") 5133 def todouble_sql(self, expression: exp.ToDouble) -> str: 5134 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 5135 5136 def string_sql(self, expression: exp.String) -> str: 5137 this = expression.this 5138 zone = expression.args.get("zone") 5139 5140 if zone: 5141 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5142 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5143 # set for source_tz to transpile the time conversion before the STRING cast 5144 this = exp.ConvertTimezone( 5145 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5146 ) 5147 5148 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 5149 5150 def median_sql(self, expression: exp.Median): 5151 if not self.SUPPORTS_MEDIAN: 5152 return self.sql( 5153 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5154 ) 5155 5156 return self.function_fallback_sql(expression) 5157 5158 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5159 filler = self.sql(expression, "this") 5160 filler = f" {filler}" if filler else "" 5161 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5162 return f"TRUNCATE{filler} {with_count}" 5163 5164 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5165 if self.SUPPORTS_UNIX_SECONDS: 5166 return self.function_fallback_sql(expression) 5167 5168 start_ts = exp.cast( 5169 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5170 ) 5171 5172 return self.sql( 5173 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5174 ) 5175 5176 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5177 dim = expression.expression 5178 5179 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5180 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5181 if not (dim.is_int and dim.name == "1"): 5182 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5183 dim = None 5184 5185 # If dimension is required but not specified, default initialize it 5186 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5187 dim = exp.Literal.number(1) 5188 5189 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5190 5191 def attach_sql(self, expression: exp.Attach) -> str: 5192 this = self.sql(expression, "this") 5193 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5194 expressions = self.expressions(expression) 5195 expressions = f" ({expressions})" if expressions else "" 5196 5197 return f"ATTACH{exists_sql} {this}{expressions}" 5198 5199 def detach_sql(self, expression: exp.Detach) -> str: 5200 this = self.sql(expression, "this") 5201 # the DATABASE keyword is required if IF EXISTS is set 5202 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5203 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5204 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5205 5206 return f"DETACH{exists_sql} {this}" 5207 5208 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5209 this = self.sql(expression, "this") 5210 value = self.sql(expression, "expression") 5211 value = f" {value}" if value else "" 5212 return f"{this}{value}" 5213 5214 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5215 return ( 5216 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5217 ) 5218 5219 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5220 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5221 encode = f"{encode} {self.sql(expression, 'this')}" 5222 5223 properties = expression.args.get("properties") 5224 if properties: 5225 encode = f"{encode} {self.properties(properties)}" 5226 5227 return encode 5228 5229 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5230 this = self.sql(expression, "this") 5231 include = f"INCLUDE {this}" 5232 5233 column_def = self.sql(expression, "column_def") 5234 if column_def: 5235 include = f"{include} {column_def}" 5236 5237 alias = self.sql(expression, "alias") 5238 if alias: 5239 include = f"{include} AS {alias}" 5240 5241 return include 5242 5243 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5244 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5245 name = f"{prefix} {self.sql(expression, 'this')}" 5246 return self.func("XMLELEMENT", name, *expression.expressions) 5247 5248 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5249 this = self.sql(expression, "this") 5250 expr = self.sql(expression, "expression") 5251 expr = f"({expr})" if expr else "" 5252 return f"{this}{expr}" 5253 5254 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5255 partitions = self.expressions(expression, "partition_expressions") 5256 create = self.expressions(expression, "create_expressions") 5257 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5258 5259 def partitionbyrangepropertydynamic_sql( 5260 self, expression: exp.PartitionByRangePropertyDynamic 5261 ) -> str: 5262 start = self.sql(expression, "start") 5263 end = self.sql(expression, "end") 5264 5265 every = expression.args["every"] 5266 if isinstance(every, exp.Interval) and every.this.is_string: 5267 every.this.replace(exp.Literal.number(every.name)) 5268 5269 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5270 5271 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5272 name = self.sql(expression, "this") 5273 values = self.expressions(expression, flat=True) 5274 5275 return f"NAME {name} VALUE {values}" 5276 5277 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5278 kind = self.sql(expression, "kind") 5279 sample = self.sql(expression, "sample") 5280 return f"SAMPLE {sample} {kind}" 5281 5282 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5283 kind = self.sql(expression, "kind") 5284 option = self.sql(expression, "option") 5285 option = f" {option}" if option else "" 5286 this = self.sql(expression, "this") 5287 this = f" {this}" if this else "" 5288 columns = self.expressions(expression) 5289 columns = f" {columns}" if columns else "" 5290 return f"{kind}{option} STATISTICS{this}{columns}" 5291 5292 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5293 this = self.sql(expression, "this") 5294 columns = self.expressions(expression) 5295 inner_expression = self.sql(expression, "expression") 5296 inner_expression = f" {inner_expression}" if inner_expression else "" 5297 update_options = self.sql(expression, "update_options") 5298 update_options = f" {update_options} UPDATE" if update_options else "" 5299 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5300 5301 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5302 kind = self.sql(expression, "kind") 5303 kind = f" {kind}" if kind else "" 5304 return f"DELETE{kind} STATISTICS" 5305 5306 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5307 inner_expression = self.sql(expression, "expression") 5308 return f"LIST CHAINED ROWS{inner_expression}" 5309 5310 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5311 kind = self.sql(expression, "kind") 5312 this = self.sql(expression, "this") 5313 this = f" {this}" if this else "" 5314 inner_expression = self.sql(expression, "expression") 5315 return f"VALIDATE {kind}{this}{inner_expression}" 5316 5317 def analyze_sql(self, expression: exp.Analyze) -> str: 5318 options = self.expressions(expression, key="options", sep=" ") 5319 options = f" {options}" if options else "" 5320 kind = self.sql(expression, "kind") 5321 kind = f" {kind}" if kind else "" 5322 this = self.sql(expression, "this") 5323 this = f" {this}" if this else "" 5324 mode = self.sql(expression, "mode") 5325 mode = f" {mode}" if mode else "" 5326 properties = self.sql(expression, "properties") 5327 properties = f" {properties}" if properties else "" 5328 partition = self.sql(expression, "partition") 5329 partition = f" {partition}" if partition else "" 5330 inner_expression = self.sql(expression, "expression") 5331 inner_expression = f" {inner_expression}" if inner_expression else "" 5332 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5333 5334 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5335 this = self.sql(expression, "this") 5336 namespaces = self.expressions(expression, key="namespaces") 5337 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5338 passing = self.expressions(expression, key="passing") 5339 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5340 columns = self.expressions(expression, key="columns") 5341 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5342 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5343 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5344 5345 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5346 this = self.sql(expression, "this") 5347 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5348 5349 def export_sql(self, expression: exp.Export) -> str: 5350 this = self.sql(expression, "this") 5351 connection = self.sql(expression, "connection") 5352 connection = f"WITH CONNECTION {connection} " if connection else "" 5353 options = self.sql(expression, "options") 5354 return f"EXPORT DATA {connection}{options} AS {this}" 5355 5356 def declare_sql(self, expression: exp.Declare) -> str: 5357 return f"DECLARE {self.expressions(expression, flat=True)}" 5358 5359 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5360 variable = self.sql(expression, "this") 5361 default = self.sql(expression, "default") 5362 default = f" = {default}" if default else "" 5363 5364 kind = self.sql(expression, "kind") 5365 if isinstance(expression.args.get("kind"), exp.Schema): 5366 kind = f"TABLE {kind}" 5367 5368 return f"{variable} AS {kind}{default}" 5369 5370 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5371 kind = self.sql(expression, "kind") 5372 this = self.sql(expression, "this") 5373 set = self.sql(expression, "expression") 5374 using = self.sql(expression, "using") 5375 using = f" USING {using}" if using else "" 5376 5377 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5378 5379 return f"{kind_sql} {this} SET {set}{using}" 5380 5381 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5382 params = self.expressions(expression, key="params", flat=True) 5383 return self.func(expression.name, *expression.expressions) + f"({params})" 5384 5385 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5386 return self.func(expression.name, *expression.expressions) 5387 5388 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5389 return self.anonymousaggfunc_sql(expression) 5390 5391 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5392 return self.parameterizedagg_sql(expression) 5393 5394 def show_sql(self, expression: exp.Show) -> str: 5395 self.unsupported("Unsupported SHOW statement") 5396 return "" 5397 5398 def install_sql(self, expression: exp.Install) -> str: 5399 self.unsupported("Unsupported INSTALL statement") 5400 return "" 5401 5402 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5403 # Snowflake GET/PUT statements: 5404 # PUT <file> <internalStage> <properties> 5405 # GET <internalStage> <file> <properties> 5406 props = expression.args.get("properties") 5407 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5408 this = self.sql(expression, "this") 5409 target = self.sql(expression, "target") 5410 5411 if isinstance(expression, exp.Put): 5412 return f"PUT {this} {target}{props_sql}" 5413 else: 5414 return f"GET {target} {this}{props_sql}" 5415 5416 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5417 this = self.sql(expression, "this") 5418 expr = self.sql(expression, "expression") 5419 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5420 return f"TRANSLATE({this} USING {expr}{with_error})" 5421 5422 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5423 if self.SUPPORTS_DECODE_CASE: 5424 return self.func("DECODE", *expression.expressions) 5425 5426 expression, *expressions = expression.expressions 5427 5428 ifs = [] 5429 for search, result in zip(expressions[::2], expressions[1::2]): 5430 if isinstance(search, exp.Literal): 5431 ifs.append(exp.If(this=expression.eq(search), true=result)) 5432 elif isinstance(search, exp.Null): 5433 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5434 else: 5435 if isinstance(search, exp.Binary): 5436 search = exp.paren(search) 5437 5438 cond = exp.or_( 5439 expression.eq(search), 5440 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5441 copy=False, 5442 ) 5443 ifs.append(exp.If(this=cond, true=result)) 5444 5445 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5446 return self.sql(case) 5447 5448 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5449 this = self.sql(expression, "this") 5450 this = self.seg(this, sep="") 5451 dimensions = self.expressions( 5452 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5453 ) 5454 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5455 metrics = self.expressions( 5456 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5457 ) 5458 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5459 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5460 facts = self.seg(f"FACTS {facts}") if facts else "" 5461 where = self.sql(expression, "where") 5462 where = self.seg(f"WHERE {where}") if where else "" 5463 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5464 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5465 5466 def getextract_sql(self, expression: exp.GetExtract) -> str: 5467 this = expression.this 5468 expr = expression.expression 5469 5470 if not this.type or not expression.type: 5471 from sqlglot.optimizer.annotate_types import annotate_types 5472 5473 this = annotate_types(this, dialect=self.dialect) 5474 5475 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5476 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5477 5478 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5479 5480 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5481 return self.sql( 5482 exp.DateAdd( 5483 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5484 expression=expression.this, 5485 unit=exp.var("DAY"), 5486 ) 5487 ) 5488 5489 def space_sql(self: Generator, expression: exp.Space) -> str: 5490 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5491 5492 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5493 return f"BUILD {self.sql(expression, 'this')}" 5494 5495 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5496 method = self.sql(expression, "method") 5497 kind = expression.args.get("kind") 5498 if not kind: 5499 return f"REFRESH {method}" 5500 5501 every = self.sql(expression, "every") 5502 unit = self.sql(expression, "unit") 5503 every = f" EVERY {every} {unit}" if every else "" 5504 starts = self.sql(expression, "starts") 5505 starts = f" STARTS {starts}" if starts else "" 5506 5507 return f"REFRESH {method} ON {kind}{every}{starts}" 5508 5509 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5510 self.unsupported("The model!attribute syntax is not supported") 5511 return "" 5512 5513 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5514 return self.func("DIRECTORY", expression.this) 5515 5516 def uuid_sql(self, expression: exp.Uuid) -> str: 5517 is_string = expression.args.get("is_string", False) 5518 uuid_func_sql = self.func("UUID") 5519 5520 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5521 return self.sql( 5522 exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect) 5523 ) 5524 5525 return uuid_func_sql 5526 5527 def initcap_sql(self, expression: exp.Initcap) -> str: 5528 delimiters = expression.expression 5529 5530 if delimiters: 5531 # do not generate delimiters arg if we are round-tripping from default delimiters 5532 if ( 5533 delimiters.is_string 5534 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5535 ): 5536 delimiters = None 5537 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5538 self.unsupported("INITCAP does not support custom delimiters") 5539 delimiters = None 5540 5541 return self.func("INITCAP", expression.this, delimiters) 5542 5543 def localtime_sql(self, expression: exp.Localtime) -> str: 5544 this = expression.this 5545 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5546 5547 def localtimestamp_sql(self, expression: exp.Localtime) -> str: 5548 this = expression.this 5549 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5550 5551 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5552 this = expression.this.name.upper() 5553 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5554 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5555 return "WEEK" 5556 5557 return self.func("WEEK", expression.this) 5558 5559 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5560 this = self.expressions(expression) 5561 charset = self.sql(expression, "charset") 5562 using = f" USING {charset}" if charset else "" 5563 return self.func(name, this + using)
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: Union[str, Tuple[str, str]]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
30def unsupported_args( 31 *args: t.Union[str, t.Tuple[str, str]], 32) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 33 """ 34 Decorator that can be used to mark certain args of an `Expression` 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: t.Dict[str, t.Optional[str]] = {} 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
Decorator that can be used to mark certain args of an Expression subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
class
Generator:
75class Generator(metaclass=_Generator): 76 """ 77 Generator converts a given syntax tree to the corresponding SQL string. 78 79 Args: 80 pretty: Whether to format the produced SQL string. 81 Default: False. 82 identify: Determines when an identifier should be quoted. Possible values are: 83 False (default): Never quote, except in cases where it's mandatory by the dialect. 84 True: Always quote except for specials cases. 85 'safe': Only quote identifiers that are case insensitive. 86 normalize: Whether to normalize identifiers to lowercase. 87 Default: False. 88 pad: The pad size in a formatted string. For example, this affects the indentation of 89 a projection in a query, relative to its nesting level. 90 Default: 2. 91 indent: The indentation size in a formatted string. For example, this affects the 92 indentation of subqueries and filters under a `WHERE` clause. 93 Default: 2. 94 normalize_functions: How to normalize function names. Possible values are: 95 "upper" or True (default): Convert names to uppercase. 96 "lower": Convert names to lowercase. 97 False: Disables function name normalization. 98 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 99 Default ErrorLevel.WARN. 100 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 101 This is only relevant if unsupported_level is ErrorLevel.RAISE. 102 Default: 3 103 leading_comma: Whether the comma is leading or trailing in select expressions. 104 This is only relevant when generating in pretty mode. 105 Default: False 106 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 107 The default is on the smaller end because the length only represents a segment and not the true 108 line length. 109 Default: 80 110 comments: Whether to preserve comments in the output SQL code. 111 Default: True 112 """ 113 114 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 115 **JSON_PATH_PART_TRANSFORMS, 116 exp.Adjacent: lambda self, e: self.binary(e, "-|-"), 117 exp.AllowedValuesProperty: lambda self, 118 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 119 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 120 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 121 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 122 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 123 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 124 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 125 exp.CaseSpecificColumnConstraint: lambda _, 126 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 127 exp.Ceil: lambda self, e: self.ceil_floor(e), 128 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 129 exp.CharacterSetProperty: lambda self, 130 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 131 exp.ClusteredColumnConstraint: lambda self, 132 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 133 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 134 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 135 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 136 exp.ConvertToCharset: lambda self, e: self.func( 137 "CONVERT", e.this, e.args["dest"], e.args.get("source") 138 ), 139 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 140 exp.CredentialsProperty: lambda self, 141 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 142 exp.CurrentCatalog: lambda *_: "CURRENT_CATALOG", 143 exp.SessionUser: lambda *_: "SESSION_USER", 144 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 145 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 146 exp.DynamicProperty: lambda *_: "DYNAMIC", 147 exp.EmptyProperty: lambda *_: "EMPTY", 148 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 149 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 150 exp.EphemeralColumnConstraint: lambda self, 151 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 152 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 153 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 154 exp.Except: lambda self, e: self.set_operations(e), 155 exp.ExternalProperty: lambda *_: "EXTERNAL", 156 exp.Floor: lambda self, e: self.ceil_floor(e), 157 exp.Get: lambda self, e: self.get_put_sql(e), 158 exp.GlobalProperty: lambda *_: "GLOBAL", 159 exp.HeapProperty: lambda *_: "HEAP", 160 exp.IcebergProperty: lambda *_: "ICEBERG", 161 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 162 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 163 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 164 exp.Intersect: lambda self, e: self.set_operations(e), 165 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 166 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 167 exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 168 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 169 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 170 exp.LanguageProperty: lambda self, e: self.naked_property(e), 171 exp.LocationProperty: lambda self, e: self.naked_property(e), 172 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 173 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 174 exp.NonClusteredColumnConstraint: lambda self, 175 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 176 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 177 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 178 exp.OnCommitProperty: lambda _, 179 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 180 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 181 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 182 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 183 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 184 exp.ExtendsLeft: lambda self, e: self.binary(e, "&<"), 185 exp.ExtendsRight: lambda self, e: self.binary(e, "&>"), 186 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 187 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 188 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 189 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 190 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 191 exp.ProjectionPolicyColumnConstraint: lambda self, 192 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 193 exp.ZeroFillColumnConstraint: lambda self, e: "ZEROFILL", 194 exp.Put: lambda self, e: self.get_put_sql(e), 195 exp.RemoteWithConnectionModelProperty: lambda self, 196 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 197 exp.ReturnsProperty: lambda self, e: ( 198 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 199 ), 200 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 201 exp.SecureProperty: lambda *_: "SECURE", 202 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 203 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 204 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 205 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 206 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 207 exp.SqlReadWriteProperty: lambda _, e: e.name, 208 exp.SqlSecurityProperty: lambda self, e: f"SQL SECURITY {self.sql(e, 'this')}", 209 exp.StabilityProperty: lambda _, e: e.name, 210 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 211 exp.StreamingTableProperty: lambda *_: "STREAMING", 212 exp.StrictProperty: lambda *_: "STRICT", 213 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 214 exp.TableColumn: lambda self, e: self.sql(e.this), 215 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 216 exp.TemporaryProperty: lambda *_: "TEMPORARY", 217 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 218 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 219 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 220 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 221 exp.TransientProperty: lambda *_: "TRANSIENT", 222 exp.Union: lambda self, e: self.set_operations(e), 223 exp.UnloggedProperty: lambda *_: "UNLOGGED", 224 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 225 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 226 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 227 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 228 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 229 exp.UtcTimestamp: lambda self, e: self.sql( 230 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 231 ), 232 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 233 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 234 exp.VolatileProperty: lambda *_: "VOLATILE", 235 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 236 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 237 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 238 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 239 exp.ForceProperty: lambda *_: "FORCE", 240 } 241 242 # Whether null ordering is supported in order by 243 # True: Full Support, None: No support, False: No support for certain cases 244 # such as window specifications, aggregate functions etc 245 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 246 247 # Whether ignore nulls is inside the agg or outside. 248 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 249 IGNORE_NULLS_IN_FUNC = False 250 251 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 252 LOCKING_READS_SUPPORTED = False 253 254 # Whether the EXCEPT and INTERSECT operations can return duplicates 255 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 256 257 # Wrap derived values in parens, usually standard but spark doesn't support it 258 WRAP_DERIVED_VALUES = True 259 260 # Whether create function uses an AS before the RETURN 261 CREATE_FUNCTION_RETURN_AS = True 262 263 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 264 MATCHED_BY_SOURCE = True 265 266 # Whether the INTERVAL expression works only with values like '1 day' 267 SINGLE_STRING_INTERVAL = False 268 269 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 270 INTERVAL_ALLOWS_PLURAL_FORM = True 271 272 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 273 LIMIT_FETCH = "ALL" 274 275 # Whether limit and fetch allows expresions or just limits 276 LIMIT_ONLY_LITERALS = False 277 278 # Whether a table is allowed to be renamed with a db 279 RENAME_TABLE_WITH_DB = True 280 281 # The separator for grouping sets and rollups 282 GROUPINGS_SEP = "," 283 284 # The string used for creating an index on a table 285 INDEX_ON = "ON" 286 287 # Whether join hints should be generated 288 JOIN_HINTS = True 289 290 # Whether table hints should be generated 291 TABLE_HINTS = True 292 293 # Whether query hints should be generated 294 QUERY_HINTS = True 295 296 # What kind of separator to use for query hints 297 QUERY_HINT_SEP = ", " 298 299 # Whether comparing against booleans (e.g. x IS TRUE) is supported 300 IS_BOOL_ALLOWED = True 301 302 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 303 DUPLICATE_KEY_UPDATE_WITH_SET = True 304 305 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 306 LIMIT_IS_TOP = False 307 308 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 309 RETURNING_END = True 310 311 # Whether to generate an unquoted value for EXTRACT's date part argument 312 EXTRACT_ALLOWS_QUOTES = True 313 314 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 315 TZ_TO_WITH_TIME_ZONE = False 316 317 # Whether the NVL2 function is supported 318 NVL2_SUPPORTED = True 319 320 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 321 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 322 323 # Whether VALUES statements can be used as derived tables. 324 # MySQL 5 and Redshift do not allow this, so when False, it will convert 325 # SELECT * VALUES into SELECT UNION 326 VALUES_AS_TABLE = True 327 328 # Whether the word COLUMN is included when adding a column with ALTER TABLE 329 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 330 331 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 332 UNNEST_WITH_ORDINALITY = True 333 334 # Whether FILTER (WHERE cond) can be used for conditional aggregation 335 AGGREGATE_FILTER_SUPPORTED = True 336 337 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 338 SEMI_ANTI_JOIN_WITH_SIDE = True 339 340 # Whether to include the type of a computed column in the CREATE DDL 341 COMPUTED_COLUMN_WITH_TYPE = True 342 343 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 344 SUPPORTS_TABLE_COPY = True 345 346 # Whether parentheses are required around the table sample's expression 347 TABLESAMPLE_REQUIRES_PARENS = True 348 349 # Whether a table sample clause's size needs to be followed by the ROWS keyword 350 TABLESAMPLE_SIZE_IS_ROWS = True 351 352 # The keyword(s) to use when generating a sample clause 353 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 354 355 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 356 TABLESAMPLE_WITH_METHOD = True 357 358 # The keyword to use when specifying the seed of a sample clause 359 TABLESAMPLE_SEED_KEYWORD = "SEED" 360 361 # Whether COLLATE is a function instead of a binary operator 362 COLLATE_IS_FUNC = False 363 364 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 365 DATA_TYPE_SPECIFIERS_ALLOWED = False 366 367 # Whether conditions require booleans WHERE x = 0 vs WHERE x 368 ENSURE_BOOLS = False 369 370 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 371 CTE_RECURSIVE_KEYWORD_REQUIRED = True 372 373 # Whether CONCAT requires >1 arguments 374 SUPPORTS_SINGLE_ARG_CONCAT = True 375 376 # Whether LAST_DAY function supports a date part argument 377 LAST_DAY_SUPPORTS_DATE_PART = True 378 379 # Whether named columns are allowed in table aliases 380 SUPPORTS_TABLE_ALIAS_COLUMNS = True 381 382 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 383 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 384 385 # What delimiter to use for separating JSON key/value pairs 386 JSON_KEY_VALUE_PAIR_SEP = ":" 387 388 # INSERT OVERWRITE TABLE x override 389 INSERT_OVERWRITE = " OVERWRITE TABLE" 390 391 # Whether the SELECT .. INTO syntax is used instead of CTAS 392 SUPPORTS_SELECT_INTO = False 393 394 # Whether UNLOGGED tables can be created 395 SUPPORTS_UNLOGGED_TABLES = False 396 397 # Whether the CREATE TABLE LIKE statement is supported 398 SUPPORTS_CREATE_TABLE_LIKE = True 399 400 # Whether the LikeProperty needs to be specified inside of the schema clause 401 LIKE_PROPERTY_INSIDE_SCHEMA = False 402 403 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 404 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 405 MULTI_ARG_DISTINCT = True 406 407 # Whether the JSON extraction operators expect a value of type JSON 408 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 409 410 # Whether bracketed keys like ["foo"] are supported in JSON paths 411 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 412 413 # Whether to escape keys using single quotes in JSON paths 414 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 415 416 # The JSONPathPart expressions supported by this dialect 417 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 418 419 # Whether any(f(x) for x in array) can be implemented by this dialect 420 CAN_IMPLEMENT_ARRAY_ANY = False 421 422 # Whether the function TO_NUMBER is supported 423 SUPPORTS_TO_NUMBER = True 424 425 # Whether EXCLUDE in window specification is supported 426 SUPPORTS_WINDOW_EXCLUDE = False 427 428 # Whether or not set op modifiers apply to the outer set op or select. 429 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 430 # True means limit 1 happens after the set op, False means it it happens on y. 431 SET_OP_MODIFIERS = True 432 433 # Whether parameters from COPY statement are wrapped in parentheses 434 COPY_PARAMS_ARE_WRAPPED = True 435 436 # Whether values of params are set with "=" token or empty space 437 COPY_PARAMS_EQ_REQUIRED = False 438 439 # Whether COPY statement has INTO keyword 440 COPY_HAS_INTO_KEYWORD = True 441 442 # Whether the conditional TRY(expression) function is supported 443 TRY_SUPPORTED = True 444 445 # Whether the UESCAPE syntax in unicode strings is supported 446 SUPPORTS_UESCAPE = True 447 448 # Function used to replace escaped unicode codes in unicode strings 449 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 450 451 # The keyword to use when generating a star projection with excluded columns 452 STAR_EXCEPT = "EXCEPT" 453 454 # The HEX function name 455 HEX_FUNC = "HEX" 456 457 # The keywords to use when prefixing & separating WITH based properties 458 WITH_PROPERTIES_PREFIX = "WITH" 459 460 # Whether to quote the generated expression of exp.JsonPath 461 QUOTE_JSON_PATH = True 462 463 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 464 PAD_FILL_PATTERN_IS_REQUIRED = False 465 466 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 467 SUPPORTS_EXPLODING_PROJECTIONS = True 468 469 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 470 ARRAY_CONCAT_IS_VAR_LEN = True 471 472 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 473 SUPPORTS_CONVERT_TIMEZONE = False 474 475 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 476 SUPPORTS_MEDIAN = True 477 478 # Whether UNIX_SECONDS(timestamp) is supported 479 SUPPORTS_UNIX_SECONDS = False 480 481 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 482 ALTER_SET_WRAPPED = False 483 484 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 485 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 486 # TODO: The normalization should be done by default once we've tested it across all dialects. 487 NORMALIZE_EXTRACT_DATE_PARTS = False 488 489 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 490 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 491 492 # The function name of the exp.ArraySize expression 493 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 494 495 # The syntax to use when altering the type of a column 496 ALTER_SET_TYPE = "SET DATA TYPE" 497 498 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 499 # None -> Doesn't support it at all 500 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 501 # True (Postgres) -> Explicitly requires it 502 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 503 504 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 505 SUPPORTS_DECODE_CASE = True 506 507 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 508 SUPPORTS_BETWEEN_FLAGS = False 509 510 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 511 SUPPORTS_LIKE_QUANTIFIERS = True 512 513 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 514 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 515 516 # Whether to include the VARIABLE keyword for SET assignments 517 SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False 518 519 # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g: 520 # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2 521 # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b 522 UPDATE_STATEMENT_SUPPORTS_FROM = True 523 524 TYPE_MAPPING = { 525 exp.DataType.Type.DATETIME2: "TIMESTAMP", 526 exp.DataType.Type.NCHAR: "CHAR", 527 exp.DataType.Type.NVARCHAR: "VARCHAR", 528 exp.DataType.Type.MEDIUMTEXT: "TEXT", 529 exp.DataType.Type.LONGTEXT: "TEXT", 530 exp.DataType.Type.TINYTEXT: "TEXT", 531 exp.DataType.Type.BLOB: "VARBINARY", 532 exp.DataType.Type.MEDIUMBLOB: "BLOB", 533 exp.DataType.Type.LONGBLOB: "BLOB", 534 exp.DataType.Type.TINYBLOB: "BLOB", 535 exp.DataType.Type.INET: "INET", 536 exp.DataType.Type.ROWVERSION: "VARBINARY", 537 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 538 } 539 540 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 541 542 TIME_PART_SINGULARS = { 543 "MICROSECONDS": "MICROSECOND", 544 "SECONDS": "SECOND", 545 "MINUTES": "MINUTE", 546 "HOURS": "HOUR", 547 "DAYS": "DAY", 548 "WEEKS": "WEEK", 549 "MONTHS": "MONTH", 550 "QUARTERS": "QUARTER", 551 "YEARS": "YEAR", 552 } 553 554 AFTER_HAVING_MODIFIER_TRANSFORMS = { 555 "cluster": lambda self, e: self.sql(e, "cluster"), 556 "distribute": lambda self, e: self.sql(e, "distribute"), 557 "sort": lambda self, e: self.sql(e, "sort"), 558 "windows": lambda self, e: ( 559 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 560 if e.args.get("windows") 561 else "" 562 ), 563 "qualify": lambda self, e: self.sql(e, "qualify"), 564 } 565 566 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 567 568 STRUCT_DELIMITER = ("<", ">") 569 570 PARAMETER_TOKEN = "@" 571 NAMED_PLACEHOLDER_TOKEN = ":" 572 573 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 574 575 PROPERTIES_LOCATION = { 576 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 578 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 579 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 582 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 584 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 587 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 591 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 593 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 594 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 595 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 596 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 600 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 604 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 605 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 606 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 607 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 608 exp.HeapProperty: exp.Properties.Location.POST_WITH, 609 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 611 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 614 exp.JournalProperty: exp.Properties.Location.POST_NAME, 615 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 620 exp.LogProperty: exp.Properties.Location.POST_NAME, 621 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 622 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 623 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 624 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 626 exp.Order: exp.Properties.Location.POST_SCHEMA, 627 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 629 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 630 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 631 exp.Property: exp.Properties.Location.POST_WITH, 632 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 636 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 640 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 641 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 642 exp.Set: exp.Properties.Location.POST_SCHEMA, 643 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 644 exp.SetProperty: exp.Properties.Location.POST_CREATE, 645 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 646 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 647 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 648 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 649 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 650 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 651 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 652 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 653 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 654 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 655 exp.Tags: exp.Properties.Location.POST_WITH, 656 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 657 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 658 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 659 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 660 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 661 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 662 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 663 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 664 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 665 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 666 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 667 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 668 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 669 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 670 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 671 } 672 673 # Keywords that can't be used as unquoted identifier names 674 RESERVED_KEYWORDS: t.Set[str] = set() 675 676 # Expressions whose comments are separated from them for better formatting 677 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 678 exp.Command, 679 exp.Create, 680 exp.Describe, 681 exp.Delete, 682 exp.Drop, 683 exp.From, 684 exp.Insert, 685 exp.Join, 686 exp.MultitableInserts, 687 exp.Order, 688 exp.Group, 689 exp.Having, 690 exp.Select, 691 exp.SetOperation, 692 exp.Update, 693 exp.Where, 694 exp.With, 695 ) 696 697 # Expressions that should not have their comments generated in maybe_comment 698 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 699 exp.Binary, 700 exp.SetOperation, 701 ) 702 703 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 704 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 705 exp.Column, 706 exp.Literal, 707 exp.Neg, 708 exp.Paren, 709 ) 710 711 PARAMETERIZABLE_TEXT_TYPES = { 712 exp.DataType.Type.NVARCHAR, 713 exp.DataType.Type.VARCHAR, 714 exp.DataType.Type.CHAR, 715 exp.DataType.Type.NCHAR, 716 } 717 718 # Expressions that need to have all CTEs under them bubbled up to them 719 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 720 721 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 722 723 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 724 725 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 726 727 __slots__ = ( 728 "pretty", 729 "identify", 730 "normalize", 731 "pad", 732 "_indent", 733 "normalize_functions", 734 "unsupported_level", 735 "max_unsupported", 736 "leading_comma", 737 "max_text_width", 738 "comments", 739 "dialect", 740 "unsupported_messages", 741 "_escaped_quote_end", 742 "_escaped_byte_quote_end", 743 "_escaped_identifier_end", 744 "_next_name", 745 "_identifier_start", 746 "_identifier_end", 747 "_quote_json_path_key_using_brackets", 748 ) 749 750 def __init__( 751 self, 752 pretty: t.Optional[bool] = None, 753 identify: str | bool = False, 754 normalize: bool = False, 755 pad: int = 2, 756 indent: int = 2, 757 normalize_functions: t.Optional[str | bool] = None, 758 unsupported_level: ErrorLevel = ErrorLevel.WARN, 759 max_unsupported: int = 3, 760 leading_comma: bool = False, 761 max_text_width: int = 80, 762 comments: bool = True, 763 dialect: DialectType = None, 764 ): 765 import sqlglot 766 from sqlglot.dialects import Dialect 767 768 self.pretty = pretty if pretty is not None else sqlglot.pretty 769 self.identify = identify 770 self.normalize = normalize 771 self.pad = pad 772 self._indent = indent 773 self.unsupported_level = unsupported_level 774 self.max_unsupported = max_unsupported 775 self.leading_comma = leading_comma 776 self.max_text_width = max_text_width 777 self.comments = comments 778 self.dialect = Dialect.get_or_raise(dialect) 779 780 # This is both a Dialect property and a Generator argument, so we prioritize the latter 781 self.normalize_functions = ( 782 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 783 ) 784 785 self.unsupported_messages: t.List[str] = [] 786 self._escaped_quote_end: str = ( 787 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 788 ) 789 self._escaped_byte_quote_end: str = ( 790 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 791 if self.dialect.BYTE_END 792 else "" 793 ) 794 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 795 796 self._next_name = name_sequence("_t") 797 798 self._identifier_start = self.dialect.IDENTIFIER_START 799 self._identifier_end = self.dialect.IDENTIFIER_END 800 801 self._quote_json_path_key_using_brackets = True 802 803 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 804 """ 805 Generates the SQL string corresponding to the given syntax tree. 806 807 Args: 808 expression: The syntax tree. 809 copy: Whether to copy the expression. The generator performs mutations so 810 it is safer to copy. 811 812 Returns: 813 The SQL string corresponding to `expression`. 814 """ 815 if copy: 816 expression = expression.copy() 817 818 expression = self.preprocess(expression) 819 820 self.unsupported_messages = [] 821 sql = self.sql(expression).strip() 822 823 if self.pretty: 824 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 825 826 if self.unsupported_level == ErrorLevel.IGNORE: 827 return sql 828 829 if self.unsupported_level == ErrorLevel.WARN: 830 for msg in self.unsupported_messages: 831 logger.warning(msg) 832 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 833 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 834 835 return sql 836 837 def preprocess(self, expression: exp.Expression) -> exp.Expression: 838 """Apply generic preprocessing transformations to a given expression.""" 839 expression = self._move_ctes_to_top_level(expression) 840 841 if self.ENSURE_BOOLS: 842 from sqlglot.transforms import ensure_bools 843 844 expression = ensure_bools(expression) 845 846 return expression 847 848 def _move_ctes_to_top_level(self, expression: E) -> E: 849 if ( 850 not expression.parent 851 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 852 and any(node.parent is not expression for node in expression.find_all(exp.With)) 853 ): 854 from sqlglot.transforms import move_ctes_to_top_level 855 856 expression = move_ctes_to_top_level(expression) 857 return expression 858 859 def unsupported(self, message: str) -> None: 860 if self.unsupported_level == ErrorLevel.IMMEDIATE: 861 raise UnsupportedError(message) 862 self.unsupported_messages.append(message) 863 864 def sep(self, sep: str = " ") -> str: 865 return f"{sep.strip()}\n" if self.pretty else sep 866 867 def seg(self, sql: str, sep: str = " ") -> str: 868 return f"{self.sep(sep)}{sql}" 869 870 def sanitize_comment(self, comment: str) -> str: 871 comment = " " + comment if comment[0].strip() else comment 872 comment = comment + " " if comment[-1].strip() else comment 873 874 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 875 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 876 comment = comment.replace("*/", "* /") 877 878 return comment 879 880 def maybe_comment( 881 self, 882 sql: str, 883 expression: t.Optional[exp.Expression] = None, 884 comments: t.Optional[t.List[str]] = None, 885 separated: bool = False, 886 ) -> str: 887 comments = ( 888 ((expression and expression.comments) if comments is None else comments) # type: ignore 889 if self.comments 890 else None 891 ) 892 893 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 894 return sql 895 896 comments_sql = " ".join( 897 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 898 ) 899 900 if not comments_sql: 901 return sql 902 903 comments_sql = self._replace_line_breaks(comments_sql) 904 905 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 906 return ( 907 f"{self.sep()}{comments_sql}{sql}" 908 if not sql or sql[0].isspace() 909 else f"{comments_sql}{self.sep()}{sql}" 910 ) 911 912 return f"{sql} {comments_sql}" 913 914 def wrap(self, expression: exp.Expression | str) -> str: 915 this_sql = ( 916 self.sql(expression) 917 if isinstance(expression, exp.UNWRAPPED_QUERIES) 918 else self.sql(expression, "this") 919 ) 920 if not this_sql: 921 return "()" 922 923 this_sql = self.indent(this_sql, level=1, pad=0) 924 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 925 926 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 927 original = self.identify 928 self.identify = False 929 result = func(*args, **kwargs) 930 self.identify = original 931 return result 932 933 def normalize_func(self, name: str) -> str: 934 if self.normalize_functions == "upper" or self.normalize_functions is True: 935 return name.upper() 936 if self.normalize_functions == "lower": 937 return name.lower() 938 return name 939 940 def indent( 941 self, 942 sql: str, 943 level: int = 0, 944 pad: t.Optional[int] = None, 945 skip_first: bool = False, 946 skip_last: bool = False, 947 ) -> str: 948 if not self.pretty or not sql: 949 return sql 950 951 pad = self.pad if pad is None else pad 952 lines = sql.split("\n") 953 954 return "\n".join( 955 ( 956 line 957 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 958 else f"{' ' * (level * self._indent + pad)}{line}" 959 ) 960 for i, line in enumerate(lines) 961 ) 962 963 def sql( 964 self, 965 expression: t.Optional[str | exp.Expression], 966 key: t.Optional[str] = None, 967 comment: bool = True, 968 ) -> str: 969 if not expression: 970 return "" 971 972 if isinstance(expression, str): 973 return expression 974 975 if key: 976 value = expression.args.get(key) 977 if value: 978 return self.sql(value) 979 return "" 980 981 transform = self.TRANSFORMS.get(expression.__class__) 982 983 if callable(transform): 984 sql = transform(self, expression) 985 elif isinstance(expression, exp.Expression): 986 exp_handler_name = f"{expression.key}_sql" 987 988 if hasattr(self, exp_handler_name): 989 sql = getattr(self, exp_handler_name)(expression) 990 elif isinstance(expression, exp.Func): 991 sql = self.function_fallback_sql(expression) 992 elif isinstance(expression, exp.Property): 993 sql = self.property_sql(expression) 994 else: 995 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 996 else: 997 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 998 999 return self.maybe_comment(sql, expression) if self.comments and comment else sql 1000 1001 def uncache_sql(self, expression: exp.Uncache) -> str: 1002 table = self.sql(expression, "this") 1003 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 1004 return f"UNCACHE TABLE{exists_sql} {table}" 1005 1006 def cache_sql(self, expression: exp.Cache) -> str: 1007 lazy = " LAZY" if expression.args.get("lazy") else "" 1008 table = self.sql(expression, "this") 1009 options = expression.args.get("options") 1010 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1011 sql = self.sql(expression, "expression") 1012 sql = f" AS{self.sep()}{sql}" if sql else "" 1013 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1014 return self.prepend_ctes(expression, sql) 1015 1016 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1017 if isinstance(expression.parent, exp.Cast): 1018 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1019 default = "DEFAULT " if expression.args.get("default") else "" 1020 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1021 1022 def column_parts(self, expression: exp.Column) -> str: 1023 return ".".join( 1024 self.sql(part) 1025 for part in ( 1026 expression.args.get("catalog"), 1027 expression.args.get("db"), 1028 expression.args.get("table"), 1029 expression.args.get("this"), 1030 ) 1031 if part 1032 ) 1033 1034 def column_sql(self, expression: exp.Column) -> str: 1035 join_mark = " (+)" if expression.args.get("join_mark") else "" 1036 1037 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1038 join_mark = "" 1039 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1040 1041 return f"{self.column_parts(expression)}{join_mark}" 1042 1043 def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str: 1044 return self.column_sql(expression) 1045 1046 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1047 this = self.sql(expression, "this") 1048 this = f" {this}" if this else "" 1049 position = self.sql(expression, "position") 1050 return f"{position}{this}" 1051 1052 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1053 column = self.sql(expression, "this") 1054 kind = self.sql(expression, "kind") 1055 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1056 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1057 kind = f"{sep}{kind}" if kind else "" 1058 constraints = f" {constraints}" if constraints else "" 1059 position = self.sql(expression, "position") 1060 position = f" {position}" if position else "" 1061 1062 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1063 kind = "" 1064 1065 return f"{exists}{column}{kind}{constraints}{position}" 1066 1067 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1068 this = self.sql(expression, "this") 1069 kind_sql = self.sql(expression, "kind").strip() 1070 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1071 1072 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1073 this = self.sql(expression, "this") 1074 if expression.args.get("not_null"): 1075 persisted = " PERSISTED NOT NULL" 1076 elif expression.args.get("persisted"): 1077 persisted = " PERSISTED" 1078 else: 1079 persisted = "" 1080 1081 return f"AS {this}{persisted}" 1082 1083 def autoincrementcolumnconstraint_sql(self, _) -> str: 1084 return self.token_sql(TokenType.AUTO_INCREMENT) 1085 1086 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1087 if isinstance(expression.this, list): 1088 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1089 else: 1090 this = self.sql(expression, "this") 1091 1092 return f"COMPRESS {this}" 1093 1094 def generatedasidentitycolumnconstraint_sql( 1095 self, expression: exp.GeneratedAsIdentityColumnConstraint 1096 ) -> str: 1097 this = "" 1098 if expression.this is not None: 1099 on_null = " ON NULL" if expression.args.get("on_null") else "" 1100 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1101 1102 start = expression.args.get("start") 1103 start = f"START WITH {start}" if start else "" 1104 increment = expression.args.get("increment") 1105 increment = f" INCREMENT BY {increment}" if increment else "" 1106 minvalue = expression.args.get("minvalue") 1107 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1108 maxvalue = expression.args.get("maxvalue") 1109 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1110 cycle = expression.args.get("cycle") 1111 cycle_sql = "" 1112 1113 if cycle is not None: 1114 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1115 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1116 1117 sequence_opts = "" 1118 if start or increment or cycle_sql: 1119 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1120 sequence_opts = f" ({sequence_opts.strip()})" 1121 1122 expr = self.sql(expression, "expression") 1123 expr = f"({expr})" if expr else "IDENTITY" 1124 1125 return f"GENERATED{this} AS {expr}{sequence_opts}" 1126 1127 def generatedasrowcolumnconstraint_sql( 1128 self, expression: exp.GeneratedAsRowColumnConstraint 1129 ) -> str: 1130 start = "START" if expression.args.get("start") else "END" 1131 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1132 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1133 1134 def periodforsystemtimeconstraint_sql( 1135 self, expression: exp.PeriodForSystemTimeConstraint 1136 ) -> str: 1137 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1138 1139 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1140 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1141 1142 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1143 desc = expression.args.get("desc") 1144 if desc is not None: 1145 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1146 options = self.expressions(expression, key="options", flat=True, sep=" ") 1147 options = f" {options}" if options else "" 1148 return f"PRIMARY KEY{options}" 1149 1150 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1151 this = self.sql(expression, "this") 1152 this = f" {this}" if this else "" 1153 index_type = expression.args.get("index_type") 1154 index_type = f" USING {index_type}" if index_type else "" 1155 on_conflict = self.sql(expression, "on_conflict") 1156 on_conflict = f" {on_conflict}" if on_conflict else "" 1157 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1158 options = self.expressions(expression, key="options", flat=True, sep=" ") 1159 options = f" {options}" if options else "" 1160 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1161 1162 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1163 input_ = expression.args.get("input_") 1164 output = expression.args.get("output") 1165 1166 if input_ and output: 1167 return "IN OUT" 1168 if input_: 1169 return "IN" 1170 if output: 1171 return "OUT" 1172 1173 return "" 1174 1175 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1176 return self.sql(expression, "this") 1177 1178 def create_sql(self, expression: exp.Create) -> str: 1179 kind = self.sql(expression, "kind") 1180 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1181 properties = expression.args.get("properties") 1182 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1183 1184 this = self.createable_sql(expression, properties_locs) 1185 1186 properties_sql = "" 1187 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1188 exp.Properties.Location.POST_WITH 1189 ): 1190 props_ast = exp.Properties( 1191 expressions=[ 1192 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1193 *properties_locs[exp.Properties.Location.POST_WITH], 1194 ] 1195 ) 1196 props_ast.parent = expression 1197 properties_sql = self.sql(props_ast) 1198 1199 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1200 properties_sql = self.sep() + properties_sql 1201 elif not self.pretty: 1202 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1203 properties_sql = f" {properties_sql}" 1204 1205 begin = " BEGIN" if expression.args.get("begin") else "" 1206 end = " END" if expression.args.get("end") else "" 1207 1208 expression_sql = self.sql(expression, "expression") 1209 if expression_sql: 1210 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1211 1212 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1213 postalias_props_sql = "" 1214 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1215 postalias_props_sql = self.properties( 1216 exp.Properties( 1217 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1218 ), 1219 wrapped=False, 1220 ) 1221 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1222 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1223 1224 postindex_props_sql = "" 1225 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1226 postindex_props_sql = self.properties( 1227 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1228 wrapped=False, 1229 prefix=" ", 1230 ) 1231 1232 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1233 indexes = f" {indexes}" if indexes else "" 1234 index_sql = indexes + postindex_props_sql 1235 1236 replace = " OR REPLACE" if expression.args.get("replace") else "" 1237 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1238 unique = " UNIQUE" if expression.args.get("unique") else "" 1239 1240 clustered = expression.args.get("clustered") 1241 if clustered is None: 1242 clustered_sql = "" 1243 elif clustered: 1244 clustered_sql = " CLUSTERED COLUMNSTORE" 1245 else: 1246 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1247 1248 postcreate_props_sql = "" 1249 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1250 postcreate_props_sql = self.properties( 1251 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1252 sep=" ", 1253 prefix=" ", 1254 wrapped=False, 1255 ) 1256 1257 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1258 1259 postexpression_props_sql = "" 1260 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1261 postexpression_props_sql = self.properties( 1262 exp.Properties( 1263 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1264 ), 1265 sep=" ", 1266 prefix=" ", 1267 wrapped=False, 1268 ) 1269 1270 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1271 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1272 no_schema_binding = ( 1273 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1274 ) 1275 1276 clone = self.sql(expression, "clone") 1277 clone = f" {clone}" if clone else "" 1278 1279 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1280 properties_expression = f"{expression_sql}{properties_sql}" 1281 else: 1282 properties_expression = f"{properties_sql}{expression_sql}" 1283 1284 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1285 return self.prepend_ctes(expression, expression_sql) 1286 1287 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1288 start = self.sql(expression, "start") 1289 start = f"START WITH {start}" if start else "" 1290 increment = self.sql(expression, "increment") 1291 increment = f" INCREMENT BY {increment}" if increment else "" 1292 minvalue = self.sql(expression, "minvalue") 1293 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1294 maxvalue = self.sql(expression, "maxvalue") 1295 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1296 owned = self.sql(expression, "owned") 1297 owned = f" OWNED BY {owned}" if owned else "" 1298 1299 cache = expression.args.get("cache") 1300 if cache is None: 1301 cache_str = "" 1302 elif cache is True: 1303 cache_str = " CACHE" 1304 else: 1305 cache_str = f" CACHE {cache}" 1306 1307 options = self.expressions(expression, key="options", flat=True, sep=" ") 1308 options = f" {options}" if options else "" 1309 1310 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1311 1312 def clone_sql(self, expression: exp.Clone) -> str: 1313 this = self.sql(expression, "this") 1314 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1315 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1316 return f"{shallow}{keyword} {this}" 1317 1318 def describe_sql(self, expression: exp.Describe) -> str: 1319 style = expression.args.get("style") 1320 style = f" {style}" if style else "" 1321 partition = self.sql(expression, "partition") 1322 partition = f" {partition}" if partition else "" 1323 format = self.sql(expression, "format") 1324 format = f" {format}" if format else "" 1325 1326 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1327 1328 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1329 tag = self.sql(expression, "tag") 1330 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1331 1332 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1333 with_ = self.sql(expression, "with_") 1334 if with_: 1335 sql = f"{with_}{self.sep()}{sql}" 1336 return sql 1337 1338 def with_sql(self, expression: exp.With) -> str: 1339 sql = self.expressions(expression, flat=True) 1340 recursive = ( 1341 "RECURSIVE " 1342 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1343 else "" 1344 ) 1345 search = self.sql(expression, "search") 1346 search = f" {search}" if search else "" 1347 1348 return f"WITH {recursive}{sql}{search}" 1349 1350 def cte_sql(self, expression: exp.CTE) -> str: 1351 alias = expression.args.get("alias") 1352 if alias: 1353 alias.add_comments(expression.pop_comments()) 1354 1355 alias_sql = self.sql(expression, "alias") 1356 1357 materialized = expression.args.get("materialized") 1358 if materialized is False: 1359 materialized = "NOT MATERIALIZED " 1360 elif materialized: 1361 materialized = "MATERIALIZED " 1362 1363 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1364 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1365 1366 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}" 1367 1368 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1369 alias = self.sql(expression, "this") 1370 columns = self.expressions(expression, key="columns", flat=True) 1371 columns = f"({columns})" if columns else "" 1372 1373 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1374 columns = "" 1375 self.unsupported("Named columns are not supported in table alias.") 1376 1377 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1378 alias = self._next_name() 1379 1380 return f"{alias}{columns}" 1381 1382 def bitstring_sql(self, expression: exp.BitString) -> str: 1383 this = self.sql(expression, "this") 1384 if self.dialect.BIT_START: 1385 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1386 return f"{int(this, 2)}" 1387 1388 def hexstring_sql( 1389 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1390 ) -> str: 1391 this = self.sql(expression, "this") 1392 is_integer_type = expression.args.get("is_integer") 1393 1394 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1395 not self.dialect.HEX_START and not binary_function_repr 1396 ): 1397 # Integer representation will be returned if: 1398 # - The read dialect treats the hex value as integer literal but not the write 1399 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1400 return f"{int(this, 16)}" 1401 1402 if not is_integer_type: 1403 # Read dialect treats the hex value as BINARY/BLOB 1404 if binary_function_repr: 1405 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1406 return self.func(binary_function_repr, exp.Literal.string(this)) 1407 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1408 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1409 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1410 1411 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1412 1413 def bytestring_sql(self, expression: exp.ByteString) -> str: 1414 this = self.sql(expression, "this") 1415 if self.dialect.BYTE_START: 1416 escaped_byte_string = self.escape_str( 1417 this, 1418 escape_backslash=False, 1419 delimiter=self.dialect.BYTE_END, 1420 escaped_delimiter=self._escaped_byte_quote_end, 1421 ) 1422 is_bytes = expression.args.get("is_bytes", False) 1423 delimited_byte_string = ( 1424 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1425 ) 1426 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1427 return self.sql( 1428 exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect) 1429 ) 1430 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1431 return self.sql( 1432 exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect) 1433 ) 1434 1435 return delimited_byte_string 1436 return this 1437 1438 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1439 this = self.sql(expression, "this") 1440 escape = expression.args.get("escape") 1441 1442 if self.dialect.UNICODE_START: 1443 escape_substitute = r"\\\1" 1444 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1445 else: 1446 escape_substitute = r"\\u\1" 1447 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1448 1449 if escape: 1450 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1451 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1452 else: 1453 escape_pattern = ESCAPED_UNICODE_RE 1454 escape_sql = "" 1455 1456 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1457 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1458 1459 return f"{left_quote}{this}{right_quote}{escape_sql}" 1460 1461 def rawstring_sql(self, expression: exp.RawString) -> str: 1462 string = expression.this 1463 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1464 string = string.replace("\\", "\\\\") 1465 1466 string = self.escape_str(string, escape_backslash=False) 1467 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1468 1469 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1470 this = self.sql(expression, "this") 1471 specifier = self.sql(expression, "expression") 1472 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1473 return f"{this}{specifier}" 1474 1475 def datatype_sql(self, expression: exp.DataType) -> str: 1476 nested = "" 1477 values = "" 1478 1479 expr_nested = expression.args.get("nested") 1480 interior = ( 1481 self.expressions( 1482 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1483 ) 1484 if expr_nested and self.pretty 1485 else self.expressions(expression, flat=True) 1486 ) 1487 1488 type_value = expression.this 1489 if type_value in self.UNSUPPORTED_TYPES: 1490 self.unsupported( 1491 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1492 ) 1493 1494 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1495 type_sql = self.sql(expression, "kind") 1496 else: 1497 type_sql = ( 1498 self.TYPE_MAPPING.get(type_value, type_value.value) 1499 if isinstance(type_value, exp.DataType.Type) 1500 else type_value 1501 ) 1502 1503 if interior: 1504 if expr_nested: 1505 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1506 if expression.args.get("values") is not None: 1507 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1508 values = self.expressions(expression, key="values", flat=True) 1509 values = f"{delimiters[0]}{values}{delimiters[1]}" 1510 elif type_value == exp.DataType.Type.INTERVAL: 1511 nested = f" {interior}" 1512 else: 1513 nested = f"({interior})" 1514 1515 type_sql = f"{type_sql}{nested}{values}" 1516 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1517 exp.DataType.Type.TIMETZ, 1518 exp.DataType.Type.TIMESTAMPTZ, 1519 ): 1520 type_sql = f"{type_sql} WITH TIME ZONE" 1521 1522 return type_sql 1523 1524 def directory_sql(self, expression: exp.Directory) -> str: 1525 local = "LOCAL " if expression.args.get("local") else "" 1526 row_format = self.sql(expression, "row_format") 1527 row_format = f" {row_format}" if row_format else "" 1528 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1529 1530 def delete_sql(self, expression: exp.Delete) -> str: 1531 this = self.sql(expression, "this") 1532 this = f" FROM {this}" if this else "" 1533 using = self.expressions(expression, key="using") 1534 using = f" USING {using}" if using else "" 1535 cluster = self.sql(expression, "cluster") 1536 cluster = f" {cluster}" if cluster else "" 1537 where = self.sql(expression, "where") 1538 returning = self.sql(expression, "returning") 1539 order = self.sql(expression, "order") 1540 limit = self.sql(expression, "limit") 1541 tables = self.expressions(expression, key="tables") 1542 tables = f" {tables}" if tables else "" 1543 if self.RETURNING_END: 1544 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1545 else: 1546 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1547 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1548 1549 def drop_sql(self, expression: exp.Drop) -> str: 1550 this = self.sql(expression, "this") 1551 expressions = self.expressions(expression, flat=True) 1552 expressions = f" ({expressions})" if expressions else "" 1553 kind = expression.args["kind"] 1554 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1555 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1556 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1557 on_cluster = self.sql(expression, "cluster") 1558 on_cluster = f" {on_cluster}" if on_cluster else "" 1559 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1560 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1561 cascade = " CASCADE" if expression.args.get("cascade") else "" 1562 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1563 purge = " PURGE" if expression.args.get("purge") else "" 1564 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1565 1566 def set_operation(self, expression: exp.SetOperation) -> str: 1567 op_type = type(expression) 1568 op_name = op_type.key.upper() 1569 1570 distinct = expression.args.get("distinct") 1571 if ( 1572 distinct is False 1573 and op_type in (exp.Except, exp.Intersect) 1574 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1575 ): 1576 self.unsupported(f"{op_name} ALL is not supported") 1577 1578 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1579 1580 if distinct is None: 1581 distinct = default_distinct 1582 if distinct is None: 1583 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1584 1585 if distinct is default_distinct: 1586 distinct_or_all = "" 1587 else: 1588 distinct_or_all = " DISTINCT" if distinct else " ALL" 1589 1590 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1591 side_kind = f"{side_kind} " if side_kind else "" 1592 1593 by_name = " BY NAME" if expression.args.get("by_name") else "" 1594 on = self.expressions(expression, key="on", flat=True) 1595 on = f" ON ({on})" if on else "" 1596 1597 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1598 1599 def set_operations(self, expression: exp.SetOperation) -> str: 1600 if not self.SET_OP_MODIFIERS: 1601 limit = expression.args.get("limit") 1602 order = expression.args.get("order") 1603 1604 if limit or order: 1605 select = self._move_ctes_to_top_level( 1606 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1607 ) 1608 1609 if limit: 1610 select = select.limit(limit.pop(), copy=False) 1611 if order: 1612 select = select.order_by(order.pop(), copy=False) 1613 return self.sql(select) 1614 1615 sqls: t.List[str] = [] 1616 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1617 1618 while stack: 1619 node = stack.pop() 1620 1621 if isinstance(node, exp.SetOperation): 1622 stack.append(node.expression) 1623 stack.append( 1624 self.maybe_comment( 1625 self.set_operation(node), comments=node.comments, separated=True 1626 ) 1627 ) 1628 stack.append(node.this) 1629 else: 1630 sqls.append(self.sql(node)) 1631 1632 this = self.sep().join(sqls) 1633 this = self.query_modifiers(expression, this) 1634 return self.prepend_ctes(expression, this) 1635 1636 def fetch_sql(self, expression: exp.Fetch) -> str: 1637 direction = expression.args.get("direction") 1638 direction = f" {direction}" if direction else "" 1639 count = self.sql(expression, "count") 1640 count = f" {count}" if count else "" 1641 limit_options = self.sql(expression, "limit_options") 1642 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1643 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1644 1645 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1646 percent = " PERCENT" if expression.args.get("percent") else "" 1647 rows = " ROWS" if expression.args.get("rows") else "" 1648 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1649 if not with_ties and rows: 1650 with_ties = " ONLY" 1651 return f"{percent}{rows}{with_ties}" 1652 1653 def filter_sql(self, expression: exp.Filter) -> str: 1654 if self.AGGREGATE_FILTER_SUPPORTED: 1655 this = self.sql(expression, "this") 1656 where = self.sql(expression, "expression").strip() 1657 return f"{this} FILTER({where})" 1658 1659 agg = expression.this 1660 agg_arg = agg.this 1661 cond = expression.expression.this 1662 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1663 return self.sql(agg) 1664 1665 def hint_sql(self, expression: exp.Hint) -> str: 1666 if not self.QUERY_HINTS: 1667 self.unsupported("Hints are not supported") 1668 return "" 1669 1670 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1671 1672 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1673 using = self.sql(expression, "using") 1674 using = f" USING {using}" if using else "" 1675 columns = self.expressions(expression, key="columns", flat=True) 1676 columns = f"({columns})" if columns else "" 1677 partition_by = self.expressions(expression, key="partition_by", flat=True) 1678 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1679 where = self.sql(expression, "where") 1680 include = self.expressions(expression, key="include", flat=True) 1681 if include: 1682 include = f" INCLUDE ({include})" 1683 with_storage = self.expressions(expression, key="with_storage", flat=True) 1684 with_storage = f" WITH ({with_storage})" if with_storage else "" 1685 tablespace = self.sql(expression, "tablespace") 1686 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1687 on = self.sql(expression, "on") 1688 on = f" ON {on}" if on else "" 1689 1690 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1691 1692 def index_sql(self, expression: exp.Index) -> str: 1693 unique = "UNIQUE " if expression.args.get("unique") else "" 1694 primary = "PRIMARY " if expression.args.get("primary") else "" 1695 amp = "AMP " if expression.args.get("amp") else "" 1696 name = self.sql(expression, "this") 1697 name = f"{name} " if name else "" 1698 table = self.sql(expression, "table") 1699 table = f"{self.INDEX_ON} {table}" if table else "" 1700 1701 index = "INDEX " if not table else "" 1702 1703 params = self.sql(expression, "params") 1704 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1705 1706 def identifier_sql(self, expression: exp.Identifier) -> str: 1707 text = expression.name 1708 lower = text.lower() 1709 text = lower if self.normalize and not expression.quoted else text 1710 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1711 if ( 1712 expression.quoted 1713 or self.dialect.can_quote(expression, self.identify) 1714 or lower in self.RESERVED_KEYWORDS 1715 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1716 ): 1717 text = f"{self._identifier_start}{text}{self._identifier_end}" 1718 return text 1719 1720 def hex_sql(self, expression: exp.Hex) -> str: 1721 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1722 if self.dialect.HEX_LOWERCASE: 1723 text = self.func("LOWER", text) 1724 1725 return text 1726 1727 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1728 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1729 if not self.dialect.HEX_LOWERCASE: 1730 text = self.func("LOWER", text) 1731 return text 1732 1733 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1734 input_format = self.sql(expression, "input_format") 1735 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1736 output_format = self.sql(expression, "output_format") 1737 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1738 return self.sep().join((input_format, output_format)) 1739 1740 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1741 string = self.sql(exp.Literal.string(expression.name)) 1742 return f"{prefix}{string}" 1743 1744 def partition_sql(self, expression: exp.Partition) -> str: 1745 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1746 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1747 1748 def properties_sql(self, expression: exp.Properties) -> str: 1749 root_properties = [] 1750 with_properties = [] 1751 1752 for p in expression.expressions: 1753 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1754 if p_loc == exp.Properties.Location.POST_WITH: 1755 with_properties.append(p) 1756 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1757 root_properties.append(p) 1758 1759 root_props_ast = exp.Properties(expressions=root_properties) 1760 root_props_ast.parent = expression.parent 1761 1762 with_props_ast = exp.Properties(expressions=with_properties) 1763 with_props_ast.parent = expression.parent 1764 1765 root_props = self.root_properties(root_props_ast) 1766 with_props = self.with_properties(with_props_ast) 1767 1768 if root_props and with_props and not self.pretty: 1769 with_props = " " + with_props 1770 1771 return root_props + with_props 1772 1773 def root_properties(self, properties: exp.Properties) -> str: 1774 if properties.expressions: 1775 return self.expressions(properties, indent=False, sep=" ") 1776 return "" 1777 1778 def properties( 1779 self, 1780 properties: exp.Properties, 1781 prefix: str = "", 1782 sep: str = ", ", 1783 suffix: str = "", 1784 wrapped: bool = True, 1785 ) -> str: 1786 if properties.expressions: 1787 expressions = self.expressions(properties, sep=sep, indent=False) 1788 if expressions: 1789 expressions = self.wrap(expressions) if wrapped else expressions 1790 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1791 return "" 1792 1793 def with_properties(self, properties: exp.Properties) -> str: 1794 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1795 1796 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1797 properties_locs = defaultdict(list) 1798 for p in properties.expressions: 1799 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1800 if p_loc != exp.Properties.Location.UNSUPPORTED: 1801 properties_locs[p_loc].append(p) 1802 else: 1803 self.unsupported(f"Unsupported property {p.key}") 1804 1805 return properties_locs 1806 1807 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1808 if isinstance(expression.this, exp.Dot): 1809 return self.sql(expression, "this") 1810 return f"'{expression.name}'" if string_key else expression.name 1811 1812 def property_sql(self, expression: exp.Property) -> str: 1813 property_cls = expression.__class__ 1814 if property_cls == exp.Property: 1815 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1816 1817 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1818 if not property_name: 1819 self.unsupported(f"Unsupported property {expression.key}") 1820 1821 return f"{property_name}={self.sql(expression, 'this')}" 1822 1823 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1824 if self.SUPPORTS_CREATE_TABLE_LIKE: 1825 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1826 options = f" {options}" if options else "" 1827 1828 like = f"LIKE {self.sql(expression, 'this')}{options}" 1829 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1830 like = f"({like})" 1831 1832 return like 1833 1834 if expression.expressions: 1835 self.unsupported("Transpilation of LIKE property options is unsupported") 1836 1837 select = exp.select("*").from_(expression.this).limit(0) 1838 return f"AS {self.sql(select)}" 1839 1840 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1841 no = "NO " if expression.args.get("no") else "" 1842 protection = " PROTECTION" if expression.args.get("protection") else "" 1843 return f"{no}FALLBACK{protection}" 1844 1845 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1846 no = "NO " if expression.args.get("no") else "" 1847 local = expression.args.get("local") 1848 local = f"{local} " if local else "" 1849 dual = "DUAL " if expression.args.get("dual") else "" 1850 before = "BEFORE " if expression.args.get("before") else "" 1851 after = "AFTER " if expression.args.get("after") else "" 1852 return f"{no}{local}{dual}{before}{after}JOURNAL" 1853 1854 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1855 freespace = self.sql(expression, "this") 1856 percent = " PERCENT" if expression.args.get("percent") else "" 1857 return f"FREESPACE={freespace}{percent}" 1858 1859 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1860 if expression.args.get("default"): 1861 property = "DEFAULT" 1862 elif expression.args.get("on"): 1863 property = "ON" 1864 else: 1865 property = "OFF" 1866 return f"CHECKSUM={property}" 1867 1868 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1869 if expression.args.get("no"): 1870 return "NO MERGEBLOCKRATIO" 1871 if expression.args.get("default"): 1872 return "DEFAULT MERGEBLOCKRATIO" 1873 1874 percent = " PERCENT" if expression.args.get("percent") else "" 1875 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1876 1877 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1878 default = expression.args.get("default") 1879 minimum = expression.args.get("minimum") 1880 maximum = expression.args.get("maximum") 1881 if default or minimum or maximum: 1882 if default: 1883 prop = "DEFAULT" 1884 elif minimum: 1885 prop = "MINIMUM" 1886 else: 1887 prop = "MAXIMUM" 1888 return f"{prop} DATABLOCKSIZE" 1889 units = expression.args.get("units") 1890 units = f" {units}" if units else "" 1891 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1892 1893 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1894 autotemp = expression.args.get("autotemp") 1895 always = expression.args.get("always") 1896 default = expression.args.get("default") 1897 manual = expression.args.get("manual") 1898 never = expression.args.get("never") 1899 1900 if autotemp is not None: 1901 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1902 elif always: 1903 prop = "ALWAYS" 1904 elif default: 1905 prop = "DEFAULT" 1906 elif manual: 1907 prop = "MANUAL" 1908 elif never: 1909 prop = "NEVER" 1910 return f"BLOCKCOMPRESSION={prop}" 1911 1912 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1913 no = expression.args.get("no") 1914 no = " NO" if no else "" 1915 concurrent = expression.args.get("concurrent") 1916 concurrent = " CONCURRENT" if concurrent else "" 1917 target = self.sql(expression, "target") 1918 target = f" {target}" if target else "" 1919 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1920 1921 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1922 if isinstance(expression.this, list): 1923 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1924 if expression.this: 1925 modulus = self.sql(expression, "this") 1926 remainder = self.sql(expression, "expression") 1927 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1928 1929 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1930 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1931 return f"FROM ({from_expressions}) TO ({to_expressions})" 1932 1933 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1934 this = self.sql(expression, "this") 1935 1936 for_values_or_default = expression.expression 1937 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1938 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1939 else: 1940 for_values_or_default = " DEFAULT" 1941 1942 return f"PARTITION OF {this}{for_values_or_default}" 1943 1944 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1945 kind = expression.args.get("kind") 1946 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1947 for_or_in = expression.args.get("for_or_in") 1948 for_or_in = f" {for_or_in}" if for_or_in else "" 1949 lock_type = expression.args.get("lock_type") 1950 override = " OVERRIDE" if expression.args.get("override") else "" 1951 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1952 1953 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1954 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1955 statistics = expression.args.get("statistics") 1956 statistics_sql = "" 1957 if statistics is not None: 1958 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1959 return f"{data_sql}{statistics_sql}" 1960 1961 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1962 this = self.sql(expression, "this") 1963 this = f"HISTORY_TABLE={this}" if this else "" 1964 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1965 data_consistency = ( 1966 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1967 ) 1968 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1969 retention_period = ( 1970 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1971 ) 1972 1973 if this: 1974 on_sql = self.func("ON", this, data_consistency, retention_period) 1975 else: 1976 on_sql = "ON" if expression.args.get("on") else "OFF" 1977 1978 sql = f"SYSTEM_VERSIONING={on_sql}" 1979 1980 return f"WITH({sql})" if expression.args.get("with_") else sql 1981 1982 def insert_sql(self, expression: exp.Insert) -> str: 1983 hint = self.sql(expression, "hint") 1984 overwrite = expression.args.get("overwrite") 1985 1986 if isinstance(expression.this, exp.Directory): 1987 this = " OVERWRITE" if overwrite else " INTO" 1988 else: 1989 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1990 1991 stored = self.sql(expression, "stored") 1992 stored = f" {stored}" if stored else "" 1993 alternative = expression.args.get("alternative") 1994 alternative = f" OR {alternative}" if alternative else "" 1995 ignore = " IGNORE" if expression.args.get("ignore") else "" 1996 is_function = expression.args.get("is_function") 1997 if is_function: 1998 this = f"{this} FUNCTION" 1999 this = f"{this} {self.sql(expression, 'this')}" 2000 2001 exists = " IF EXISTS" if expression.args.get("exists") else "" 2002 where = self.sql(expression, "where") 2003 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2004 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2005 on_conflict = self.sql(expression, "conflict") 2006 on_conflict = f" {on_conflict}" if on_conflict else "" 2007 by_name = " BY NAME" if expression.args.get("by_name") else "" 2008 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2009 returning = self.sql(expression, "returning") 2010 2011 if self.RETURNING_END: 2012 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2013 else: 2014 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2015 2016 partition_by = self.sql(expression, "partition") 2017 partition_by = f" {partition_by}" if partition_by else "" 2018 settings = self.sql(expression, "settings") 2019 settings = f" {settings}" if settings else "" 2020 2021 source = self.sql(expression, "source") 2022 source = f"TABLE {source}" if source else "" 2023 2024 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2025 return self.prepend_ctes(expression, sql) 2026 2027 def introducer_sql(self, expression: exp.Introducer) -> str: 2028 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 2029 2030 def kill_sql(self, expression: exp.Kill) -> str: 2031 kind = self.sql(expression, "kind") 2032 kind = f" {kind}" if kind else "" 2033 this = self.sql(expression, "this") 2034 this = f" {this}" if this else "" 2035 return f"KILL{kind}{this}" 2036 2037 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 2038 return expression.name 2039 2040 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 2041 return expression.name 2042 2043 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2044 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2045 2046 constraint = self.sql(expression, "constraint") 2047 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2048 2049 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2050 if conflict_keys: 2051 conflict_keys = f"({conflict_keys})" 2052 2053 index_predicate = self.sql(expression, "index_predicate") 2054 conflict_keys = f"{conflict_keys}{index_predicate} " 2055 2056 action = self.sql(expression, "action") 2057 2058 expressions = self.expressions(expression, flat=True) 2059 if expressions: 2060 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2061 expressions = f" {set_keyword}{expressions}" 2062 2063 where = self.sql(expression, "where") 2064 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 2065 2066 def returning_sql(self, expression: exp.Returning) -> str: 2067 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 2068 2069 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2070 fields = self.sql(expression, "fields") 2071 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2072 escaped = self.sql(expression, "escaped") 2073 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2074 items = self.sql(expression, "collection_items") 2075 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2076 keys = self.sql(expression, "map_keys") 2077 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2078 lines = self.sql(expression, "lines") 2079 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2080 null = self.sql(expression, "null") 2081 null = f" NULL DEFINED AS {null}" if null else "" 2082 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2083 2084 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2085 return f"WITH ({self.expressions(expression, flat=True)})" 2086 2087 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2088 this = f"{self.sql(expression, 'this')} INDEX" 2089 target = self.sql(expression, "target") 2090 target = f" FOR {target}" if target else "" 2091 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2092 2093 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2094 this = self.sql(expression, "this") 2095 kind = self.sql(expression, "kind") 2096 expr = self.sql(expression, "expression") 2097 return f"{this} ({kind} => {expr})" 2098 2099 def table_parts(self, expression: exp.Table) -> str: 2100 return ".".join( 2101 self.sql(part) 2102 for part in ( 2103 expression.args.get("catalog"), 2104 expression.args.get("db"), 2105 expression.args.get("this"), 2106 ) 2107 if part is not None 2108 ) 2109 2110 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2111 table = self.table_parts(expression) 2112 only = "ONLY " if expression.args.get("only") else "" 2113 partition = self.sql(expression, "partition") 2114 partition = f" {partition}" if partition else "" 2115 version = self.sql(expression, "version") 2116 version = f" {version}" if version else "" 2117 alias = self.sql(expression, "alias") 2118 alias = f"{sep}{alias}" if alias else "" 2119 2120 sample = self.sql(expression, "sample") 2121 if self.dialect.ALIAS_POST_TABLESAMPLE: 2122 sample_pre_alias = sample 2123 sample_post_alias = "" 2124 else: 2125 sample_pre_alias = "" 2126 sample_post_alias = sample 2127 2128 hints = self.expressions(expression, key="hints", sep=" ") 2129 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2130 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2131 joins = self.indent( 2132 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2133 ) 2134 laterals = self.expressions(expression, key="laterals", sep="") 2135 2136 file_format = self.sql(expression, "format") 2137 if file_format: 2138 pattern = self.sql(expression, "pattern") 2139 pattern = f", PATTERN => {pattern}" if pattern else "" 2140 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2141 2142 ordinality = expression.args.get("ordinality") or "" 2143 if ordinality: 2144 ordinality = f" WITH ORDINALITY{alias}" 2145 alias = "" 2146 2147 when = self.sql(expression, "when") 2148 if when: 2149 table = f"{table} {when}" 2150 2151 changes = self.sql(expression, "changes") 2152 changes = f" {changes}" if changes else "" 2153 2154 rows_from = self.expressions(expression, key="rows_from") 2155 if rows_from: 2156 table = f"ROWS FROM {self.wrap(rows_from)}" 2157 2158 indexed = expression.args.get("indexed") 2159 if indexed is not None: 2160 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2161 else: 2162 indexed = "" 2163 2164 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2165 2166 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2167 table = self.func("TABLE", expression.this) 2168 alias = self.sql(expression, "alias") 2169 alias = f" AS {alias}" if alias else "" 2170 sample = self.sql(expression, "sample") 2171 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2172 joins = self.indent( 2173 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2174 ) 2175 return f"{table}{alias}{pivots}{sample}{joins}" 2176 2177 def tablesample_sql( 2178 self, 2179 expression: exp.TableSample, 2180 tablesample_keyword: t.Optional[str] = None, 2181 ) -> str: 2182 method = self.sql(expression, "method") 2183 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2184 numerator = self.sql(expression, "bucket_numerator") 2185 denominator = self.sql(expression, "bucket_denominator") 2186 field = self.sql(expression, "bucket_field") 2187 field = f" ON {field}" if field else "" 2188 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2189 seed = self.sql(expression, "seed") 2190 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2191 2192 size = self.sql(expression, "size") 2193 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2194 size = f"{size} ROWS" 2195 2196 percent = self.sql(expression, "percent") 2197 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2198 percent = f"{percent} PERCENT" 2199 2200 expr = f"{bucket}{percent}{size}" 2201 if self.TABLESAMPLE_REQUIRES_PARENS: 2202 expr = f"({expr})" 2203 2204 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2205 2206 def pivot_sql(self, expression: exp.Pivot) -> str: 2207 expressions = self.expressions(expression, flat=True) 2208 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2209 2210 group = self.sql(expression, "group") 2211 2212 if expression.this: 2213 this = self.sql(expression, "this") 2214 if not expressions: 2215 sql = f"UNPIVOT {this}" 2216 else: 2217 on = f"{self.seg('ON')} {expressions}" 2218 into = self.sql(expression, "into") 2219 into = f"{self.seg('INTO')} {into}" if into else "" 2220 using = self.expressions(expression, key="using", flat=True) 2221 using = f"{self.seg('USING')} {using}" if using else "" 2222 sql = f"{direction} {this}{on}{into}{using}{group}" 2223 return self.prepend_ctes(expression, sql) 2224 2225 alias = self.sql(expression, "alias") 2226 alias = f" AS {alias}" if alias else "" 2227 2228 fields = self.expressions( 2229 expression, 2230 "fields", 2231 sep=" ", 2232 dynamic=True, 2233 new_line=True, 2234 skip_first=True, 2235 skip_last=True, 2236 ) 2237 2238 include_nulls = expression.args.get("include_nulls") 2239 if include_nulls is not None: 2240 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2241 else: 2242 nulls = "" 2243 2244 default_on_null = self.sql(expression, "default_on_null") 2245 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2246 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2247 return self.prepend_ctes(expression, sql) 2248 2249 def version_sql(self, expression: exp.Version) -> str: 2250 this = f"FOR {expression.name}" 2251 kind = expression.text("kind") 2252 expr = self.sql(expression, "expression") 2253 return f"{this} {kind} {expr}" 2254 2255 def tuple_sql(self, expression: exp.Tuple) -> str: 2256 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2257 2258 def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]: 2259 """ 2260 Returns (join_sql, from_sql) for UPDATE statements. 2261 - join_sql: placed after UPDATE table, before SET 2262 - from_sql: placed after SET clause (standard position) 2263 Dialects like MySQL need to convert FROM to JOIN syntax. 2264 """ 2265 if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get("from_")): 2266 return ("", self.sql(expression, "from_")) 2267 2268 # Qualify unqualified columns in SET clause with the target table 2269 # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity 2270 target_table = expression.this 2271 if isinstance(target_table, exp.Table): 2272 target_name = exp.to_identifier(target_table.alias_or_name) 2273 for eq in expression.expressions: 2274 col = eq.this 2275 if isinstance(col, exp.Column) and not col.table: 2276 col.set("table", target_name) 2277 2278 table = from_expr.this 2279 if nested_joins := table.args.get("joins", []): 2280 table.set("joins", None) 2281 2282 join_sql = self.sql(exp.Join(this=table, on=exp.true())) 2283 for nested in nested_joins: 2284 if not nested.args.get("on") and not nested.args.get("using"): 2285 nested.set("on", exp.true()) 2286 join_sql += self.sql(nested) 2287 2288 return (join_sql, "") 2289 2290 def update_sql(self, expression: exp.Update) -> str: 2291 this = self.sql(expression, "this") 2292 join_sql, from_sql = self._update_from_joins_sql(expression) 2293 set_sql = self.expressions(expression, flat=True) 2294 where_sql = self.sql(expression, "where") 2295 returning = self.sql(expression, "returning") 2296 order = self.sql(expression, "order") 2297 limit = self.sql(expression, "limit") 2298 if self.RETURNING_END: 2299 expression_sql = f"{from_sql}{where_sql}{returning}" 2300 else: 2301 expression_sql = f"{returning}{from_sql}{where_sql}" 2302 options = self.expressions(expression, key="options") 2303 options = f" OPTION({options})" if options else "" 2304 sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2305 return self.prepend_ctes(expression, sql) 2306 2307 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2308 values_as_table = values_as_table and self.VALUES_AS_TABLE 2309 2310 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2311 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2312 args = self.expressions(expression) 2313 alias = self.sql(expression, "alias") 2314 values = f"VALUES{self.seg('')}{args}" 2315 values = ( 2316 f"({values})" 2317 if self.WRAP_DERIVED_VALUES 2318 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2319 else values 2320 ) 2321 values = self.query_modifiers(expression, values) 2322 return f"{values} AS {alias}" if alias else values 2323 2324 # Converts `VALUES...` expression into a series of select unions. 2325 alias_node = expression.args.get("alias") 2326 column_names = alias_node and alias_node.columns 2327 2328 selects: t.List[exp.Query] = [] 2329 2330 for i, tup in enumerate(expression.expressions): 2331 row = tup.expressions 2332 2333 if i == 0 and column_names: 2334 row = [ 2335 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2336 ] 2337 2338 selects.append(exp.Select(expressions=row)) 2339 2340 if self.pretty: 2341 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2342 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2343 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2344 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2345 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2346 2347 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2348 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2349 return f"({unions}){alias}" 2350 2351 def var_sql(self, expression: exp.Var) -> str: 2352 return self.sql(expression, "this") 2353 2354 @unsupported_args("expressions") 2355 def into_sql(self, expression: exp.Into) -> str: 2356 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2357 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2358 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2359 2360 def from_sql(self, expression: exp.From) -> str: 2361 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2362 2363 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2364 grouping_sets = self.expressions(expression, indent=False) 2365 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2366 2367 def rollup_sql(self, expression: exp.Rollup) -> str: 2368 expressions = self.expressions(expression, indent=False) 2369 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2370 2371 def cube_sql(self, expression: exp.Cube) -> str: 2372 expressions = self.expressions(expression, indent=False) 2373 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2374 2375 def group_sql(self, expression: exp.Group) -> str: 2376 group_by_all = expression.args.get("all") 2377 if group_by_all is True: 2378 modifier = " ALL" 2379 elif group_by_all is False: 2380 modifier = " DISTINCT" 2381 else: 2382 modifier = "" 2383 2384 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2385 2386 grouping_sets = self.expressions(expression, key="grouping_sets") 2387 cube = self.expressions(expression, key="cube") 2388 rollup = self.expressions(expression, key="rollup") 2389 2390 groupings = csv( 2391 self.seg(grouping_sets) if grouping_sets else "", 2392 self.seg(cube) if cube else "", 2393 self.seg(rollup) if rollup else "", 2394 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2395 sep=self.GROUPINGS_SEP, 2396 ) 2397 2398 if ( 2399 expression.expressions 2400 and groupings 2401 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2402 ): 2403 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2404 2405 return f"{group_by}{groupings}" 2406 2407 def having_sql(self, expression: exp.Having) -> str: 2408 this = self.indent(self.sql(expression, "this")) 2409 return f"{self.seg('HAVING')}{self.sep()}{this}" 2410 2411 def connect_sql(self, expression: exp.Connect) -> str: 2412 start = self.sql(expression, "start") 2413 start = self.seg(f"START WITH {start}") if start else "" 2414 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2415 connect = self.sql(expression, "connect") 2416 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2417 return start + connect 2418 2419 def prior_sql(self, expression: exp.Prior) -> str: 2420 return f"PRIOR {self.sql(expression, 'this')}" 2421 2422 def join_sql(self, expression: exp.Join) -> str: 2423 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2424 side = None 2425 else: 2426 side = expression.side 2427 2428 op_sql = " ".join( 2429 op 2430 for op in ( 2431 expression.method, 2432 "GLOBAL" if expression.args.get("global_") else None, 2433 side, 2434 expression.kind, 2435 expression.hint if self.JOIN_HINTS else None, 2436 ) 2437 if op 2438 ) 2439 match_cond = self.sql(expression, "match_condition") 2440 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2441 on_sql = self.sql(expression, "on") 2442 using = expression.args.get("using") 2443 2444 if not on_sql and using: 2445 on_sql = csv(*(self.sql(column) for column in using)) 2446 2447 this = expression.this 2448 this_sql = self.sql(this) 2449 2450 exprs = self.expressions(expression) 2451 if exprs: 2452 this_sql = f"{this_sql},{self.seg(exprs)}" 2453 2454 if on_sql: 2455 on_sql = self.indent(on_sql, skip_first=True) 2456 space = self.seg(" " * self.pad) if self.pretty else " " 2457 if using: 2458 on_sql = f"{space}USING ({on_sql})" 2459 else: 2460 on_sql = f"{space}ON {on_sql}" 2461 elif not op_sql: 2462 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2463 return f" {this_sql}" 2464 2465 return f", {this_sql}" 2466 2467 if op_sql != "STRAIGHT_JOIN": 2468 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2469 2470 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2471 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2472 2473 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2474 args = self.expressions(expression, flat=True) 2475 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2476 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2477 2478 def lateral_op(self, expression: exp.Lateral) -> str: 2479 cross_apply = expression.args.get("cross_apply") 2480 2481 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2482 if cross_apply is True: 2483 op = "INNER JOIN " 2484 elif cross_apply is False: 2485 op = "LEFT JOIN " 2486 else: 2487 op = "" 2488 2489 return f"{op}LATERAL" 2490 2491 def lateral_sql(self, expression: exp.Lateral) -> str: 2492 this = self.sql(expression, "this") 2493 2494 if expression.args.get("view"): 2495 alias = expression.args["alias"] 2496 columns = self.expressions(alias, key="columns", flat=True) 2497 table = f" {alias.name}" if alias.name else "" 2498 columns = f" AS {columns}" if columns else "" 2499 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2500 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2501 2502 alias = self.sql(expression, "alias") 2503 alias = f" AS {alias}" if alias else "" 2504 2505 ordinality = expression.args.get("ordinality") or "" 2506 if ordinality: 2507 ordinality = f" WITH ORDINALITY{alias}" 2508 alias = "" 2509 2510 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2511 2512 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2513 this = self.sql(expression, "this") 2514 2515 args = [ 2516 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2517 for e in (expression.args.get(k) for k in ("offset", "expression")) 2518 if e 2519 ] 2520 2521 args_sql = ", ".join(self.sql(e) for e in args) 2522 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2523 expressions = self.expressions(expression, flat=True) 2524 limit_options = self.sql(expression, "limit_options") 2525 expressions = f" BY {expressions}" if expressions else "" 2526 2527 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2528 2529 def offset_sql(self, expression: exp.Offset) -> str: 2530 this = self.sql(expression, "this") 2531 value = expression.expression 2532 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2533 expressions = self.expressions(expression, flat=True) 2534 expressions = f" BY {expressions}" if expressions else "" 2535 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2536 2537 def setitem_sql(self, expression: exp.SetItem) -> str: 2538 kind = self.sql(expression, "kind") 2539 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2540 kind = "" 2541 else: 2542 kind = f"{kind} " if kind else "" 2543 this = self.sql(expression, "this") 2544 expressions = self.expressions(expression) 2545 collate = self.sql(expression, "collate") 2546 collate = f" COLLATE {collate}" if collate else "" 2547 global_ = "GLOBAL " if expression.args.get("global_") else "" 2548 return f"{global_}{kind}{this}{expressions}{collate}" 2549 2550 def set_sql(self, expression: exp.Set) -> str: 2551 expressions = f" {self.expressions(expression, flat=True)}" 2552 tag = " TAG" if expression.args.get("tag") else "" 2553 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2554 2555 def queryband_sql(self, expression: exp.QueryBand) -> str: 2556 this = self.sql(expression, "this") 2557 update = " UPDATE" if expression.args.get("update") else "" 2558 scope = self.sql(expression, "scope") 2559 scope = f" FOR {scope}" if scope else "" 2560 2561 return f"QUERY_BAND = {this}{update}{scope}" 2562 2563 def pragma_sql(self, expression: exp.Pragma) -> str: 2564 return f"PRAGMA {self.sql(expression, 'this')}" 2565 2566 def lock_sql(self, expression: exp.Lock) -> str: 2567 if not self.LOCKING_READS_SUPPORTED: 2568 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2569 return "" 2570 2571 update = expression.args["update"] 2572 key = expression.args.get("key") 2573 if update: 2574 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2575 else: 2576 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2577 expressions = self.expressions(expression, flat=True) 2578 expressions = f" OF {expressions}" if expressions else "" 2579 wait = expression.args.get("wait") 2580 2581 if wait is not None: 2582 if isinstance(wait, exp.Literal): 2583 wait = f" WAIT {self.sql(wait)}" 2584 else: 2585 wait = " NOWAIT" if wait else " SKIP LOCKED" 2586 2587 return f"{lock_type}{expressions}{wait or ''}" 2588 2589 def literal_sql(self, expression: exp.Literal) -> str: 2590 text = expression.this or "" 2591 if expression.is_string: 2592 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2593 return text 2594 2595 def escape_str( 2596 self, 2597 text: str, 2598 escape_backslash: bool = True, 2599 delimiter: t.Optional[str] = None, 2600 escaped_delimiter: t.Optional[str] = None, 2601 ) -> str: 2602 if self.dialect.ESCAPED_SEQUENCES: 2603 to_escaped = self.dialect.ESCAPED_SEQUENCES 2604 text = "".join( 2605 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2606 ) 2607 2608 delimiter = delimiter or self.dialect.QUOTE_END 2609 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2610 2611 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter) 2612 2613 def loaddata_sql(self, expression: exp.LoadData) -> str: 2614 local = " LOCAL" if expression.args.get("local") else "" 2615 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2616 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2617 this = f" INTO TABLE {self.sql(expression, 'this')}" 2618 partition = self.sql(expression, "partition") 2619 partition = f" {partition}" if partition else "" 2620 input_format = self.sql(expression, "input_format") 2621 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2622 serde = self.sql(expression, "serde") 2623 serde = f" SERDE {serde}" if serde else "" 2624 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2625 2626 def null_sql(self, *_) -> str: 2627 return "NULL" 2628 2629 def boolean_sql(self, expression: exp.Boolean) -> str: 2630 return "TRUE" if expression.this else "FALSE" 2631 2632 def booland_sql(self, expression: exp.Booland) -> str: 2633 return f"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))" 2634 2635 def boolor_sql(self, expression: exp.Boolor) -> str: 2636 return f"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))" 2637 2638 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2639 this = self.sql(expression, "this") 2640 this = f"{this} " if this else this 2641 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2642 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2643 2644 def withfill_sql(self, expression: exp.WithFill) -> str: 2645 from_sql = self.sql(expression, "from_") 2646 from_sql = f" FROM {from_sql}" if from_sql else "" 2647 to_sql = self.sql(expression, "to") 2648 to_sql = f" TO {to_sql}" if to_sql else "" 2649 step_sql = self.sql(expression, "step") 2650 step_sql = f" STEP {step_sql}" if step_sql else "" 2651 interpolated_values = [ 2652 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2653 if isinstance(e, exp.Alias) 2654 else self.sql(e, "this") 2655 for e in expression.args.get("interpolate") or [] 2656 ] 2657 interpolate = ( 2658 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2659 ) 2660 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2661 2662 def cluster_sql(self, expression: exp.Cluster) -> str: 2663 return self.op_expressions("CLUSTER BY", expression) 2664 2665 def distribute_sql(self, expression: exp.Distribute) -> str: 2666 return self.op_expressions("DISTRIBUTE BY", expression) 2667 2668 def sort_sql(self, expression: exp.Sort) -> str: 2669 return self.op_expressions("SORT BY", expression) 2670 2671 def ordered_sql(self, expression: exp.Ordered) -> str: 2672 desc = expression.args.get("desc") 2673 asc = not desc 2674 2675 nulls_first = expression.args.get("nulls_first") 2676 nulls_last = not nulls_first 2677 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2678 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2679 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2680 2681 this = self.sql(expression, "this") 2682 2683 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2684 nulls_sort_change = "" 2685 if nulls_first and ( 2686 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2687 ): 2688 nulls_sort_change = " NULLS FIRST" 2689 elif ( 2690 nulls_last 2691 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2692 and not nulls_are_last 2693 ): 2694 nulls_sort_change = " NULLS LAST" 2695 2696 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2697 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2698 window = expression.find_ancestor(exp.Window, exp.Select) 2699 if isinstance(window, exp.Window) and window.args.get("spec"): 2700 self.unsupported( 2701 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2702 ) 2703 nulls_sort_change = "" 2704 elif self.NULL_ORDERING_SUPPORTED is False and ( 2705 (asc and nulls_sort_change == " NULLS LAST") 2706 or (desc and nulls_sort_change == " NULLS FIRST") 2707 ): 2708 # BigQuery does not allow these ordering/nulls combinations when used under 2709 # an aggregation func or under a window containing one 2710 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2711 2712 if isinstance(ancestor, exp.Window): 2713 ancestor = ancestor.this 2714 if isinstance(ancestor, exp.AggFunc): 2715 self.unsupported( 2716 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2717 ) 2718 nulls_sort_change = "" 2719 elif self.NULL_ORDERING_SUPPORTED is None: 2720 if expression.this.is_int: 2721 self.unsupported( 2722 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2723 ) 2724 elif not isinstance(expression.this, exp.Rand): 2725 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2726 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2727 nulls_sort_change = "" 2728 2729 with_fill = self.sql(expression, "with_fill") 2730 with_fill = f" {with_fill}" if with_fill else "" 2731 2732 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2733 2734 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2735 window_frame = self.sql(expression, "window_frame") 2736 window_frame = f"{window_frame} " if window_frame else "" 2737 2738 this = self.sql(expression, "this") 2739 2740 return f"{window_frame}{this}" 2741 2742 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2743 partition = self.partition_by_sql(expression) 2744 order = self.sql(expression, "order") 2745 measures = self.expressions(expression, key="measures") 2746 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2747 rows = self.sql(expression, "rows") 2748 rows = self.seg(rows) if rows else "" 2749 after = self.sql(expression, "after") 2750 after = self.seg(after) if after else "" 2751 pattern = self.sql(expression, "pattern") 2752 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2753 definition_sqls = [ 2754 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2755 for definition in expression.args.get("define", []) 2756 ] 2757 definitions = self.expressions(sqls=definition_sqls) 2758 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2759 body = "".join( 2760 ( 2761 partition, 2762 order, 2763 measures, 2764 rows, 2765 after, 2766 pattern, 2767 define, 2768 ) 2769 ) 2770 alias = self.sql(expression, "alias") 2771 alias = f" {alias}" if alias else "" 2772 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2773 2774 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2775 limit = expression.args.get("limit") 2776 2777 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2778 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2779 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2780 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2781 2782 return csv( 2783 *sqls, 2784 *[self.sql(join) for join in expression.args.get("joins") or []], 2785 self.sql(expression, "match"), 2786 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2787 self.sql(expression, "prewhere"), 2788 self.sql(expression, "where"), 2789 self.sql(expression, "connect"), 2790 self.sql(expression, "group"), 2791 self.sql(expression, "having"), 2792 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2793 self.sql(expression, "order"), 2794 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2795 *self.after_limit_modifiers(expression), 2796 self.options_modifier(expression), 2797 self.for_modifiers(expression), 2798 sep="", 2799 ) 2800 2801 def options_modifier(self, expression: exp.Expression) -> str: 2802 options = self.expressions(expression, key="options") 2803 return f" {options}" if options else "" 2804 2805 def for_modifiers(self, expression: exp.Expression) -> str: 2806 for_modifiers = self.expressions(expression, key="for_") 2807 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2808 2809 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2810 self.unsupported("Unsupported query option.") 2811 return "" 2812 2813 def offset_limit_modifiers( 2814 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2815 ) -> t.List[str]: 2816 return [ 2817 self.sql(expression, "offset") if fetch else self.sql(limit), 2818 self.sql(limit) if fetch else self.sql(expression, "offset"), 2819 ] 2820 2821 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2822 locks = self.expressions(expression, key="locks", sep=" ") 2823 locks = f" {locks}" if locks else "" 2824 return [locks, self.sql(expression, "sample")] 2825 2826 def select_sql(self, expression: exp.Select) -> str: 2827 into = expression.args.get("into") 2828 if not self.SUPPORTS_SELECT_INTO and into: 2829 into.pop() 2830 2831 hint = self.sql(expression, "hint") 2832 distinct = self.sql(expression, "distinct") 2833 distinct = f" {distinct}" if distinct else "" 2834 kind = self.sql(expression, "kind") 2835 2836 limit = expression.args.get("limit") 2837 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2838 top = self.limit_sql(limit, top=True) 2839 limit.pop() 2840 else: 2841 top = "" 2842 2843 expressions = self.expressions(expression) 2844 2845 if kind: 2846 if kind in self.SELECT_KINDS: 2847 kind = f" AS {kind}" 2848 else: 2849 if kind == "STRUCT": 2850 expressions = self.expressions( 2851 sqls=[ 2852 self.sql( 2853 exp.Struct( 2854 expressions=[ 2855 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2856 if isinstance(e, exp.Alias) 2857 else e 2858 for e in expression.expressions 2859 ] 2860 ) 2861 ) 2862 ] 2863 ) 2864 kind = "" 2865 2866 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2867 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2868 2869 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2870 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2871 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2872 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2873 sql = self.query_modifiers( 2874 expression, 2875 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2876 self.sql(expression, "into", comment=False), 2877 self.sql(expression, "from_", comment=False), 2878 ) 2879 2880 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2881 if expression.args.get("with_"): 2882 sql = self.maybe_comment(sql, expression) 2883 expression.pop_comments() 2884 2885 sql = self.prepend_ctes(expression, sql) 2886 2887 if not self.SUPPORTS_SELECT_INTO and into: 2888 if into.args.get("temporary"): 2889 table_kind = " TEMPORARY" 2890 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2891 table_kind = " UNLOGGED" 2892 else: 2893 table_kind = "" 2894 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2895 2896 return sql 2897 2898 def schema_sql(self, expression: exp.Schema) -> str: 2899 this = self.sql(expression, "this") 2900 sql = self.schema_columns_sql(expression) 2901 return f"{this} {sql}" if this and sql else this or sql 2902 2903 def schema_columns_sql(self, expression: exp.Schema) -> str: 2904 if expression.expressions: 2905 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2906 return "" 2907 2908 def star_sql(self, expression: exp.Star) -> str: 2909 except_ = self.expressions(expression, key="except_", flat=True) 2910 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2911 replace = self.expressions(expression, key="replace", flat=True) 2912 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2913 rename = self.expressions(expression, key="rename", flat=True) 2914 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2915 return f"*{except_}{replace}{rename}" 2916 2917 def parameter_sql(self, expression: exp.Parameter) -> str: 2918 this = self.sql(expression, "this") 2919 return f"{self.PARAMETER_TOKEN}{this}" 2920 2921 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2922 this = self.sql(expression, "this") 2923 kind = expression.text("kind") 2924 if kind: 2925 kind = f"{kind}." 2926 return f"@@{kind}{this}" 2927 2928 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2929 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2930 2931 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2932 alias = self.sql(expression, "alias") 2933 alias = f"{sep}{alias}" if alias else "" 2934 sample = self.sql(expression, "sample") 2935 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2936 alias = f"{sample}{alias}" 2937 2938 # Set to None so it's not generated again by self.query_modifiers() 2939 expression.set("sample", None) 2940 2941 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2942 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2943 return self.prepend_ctes(expression, sql) 2944 2945 def qualify_sql(self, expression: exp.Qualify) -> str: 2946 this = self.indent(self.sql(expression, "this")) 2947 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2948 2949 def unnest_sql(self, expression: exp.Unnest) -> str: 2950 args = self.expressions(expression, flat=True) 2951 2952 alias = expression.args.get("alias") 2953 offset = expression.args.get("offset") 2954 2955 if self.UNNEST_WITH_ORDINALITY: 2956 if alias and isinstance(offset, exp.Expression): 2957 alias.append("columns", offset) 2958 2959 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2960 columns = alias.columns 2961 alias = self.sql(columns[0]) if columns else "" 2962 else: 2963 alias = self.sql(alias) 2964 2965 alias = f" AS {alias}" if alias else alias 2966 if self.UNNEST_WITH_ORDINALITY: 2967 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2968 else: 2969 if isinstance(offset, exp.Expression): 2970 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2971 elif offset: 2972 suffix = f"{alias} WITH OFFSET" 2973 else: 2974 suffix = alias 2975 2976 return f"UNNEST({args}){suffix}" 2977 2978 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2979 return "" 2980 2981 def where_sql(self, expression: exp.Where) -> str: 2982 this = self.indent(self.sql(expression, "this")) 2983 return f"{self.seg('WHERE')}{self.sep()}{this}" 2984 2985 def window_sql(self, expression: exp.Window) -> str: 2986 this = self.sql(expression, "this") 2987 partition = self.partition_by_sql(expression) 2988 order = expression.args.get("order") 2989 order = self.order_sql(order, flat=True) if order else "" 2990 spec = self.sql(expression, "spec") 2991 alias = self.sql(expression, "alias") 2992 over = self.sql(expression, "over") or "OVER" 2993 2994 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2995 2996 first = expression.args.get("first") 2997 if first is None: 2998 first = "" 2999 else: 3000 first = "FIRST" if first else "LAST" 3001 3002 if not partition and not order and not spec and alias: 3003 return f"{this} {alias}" 3004 3005 args = self.format_args( 3006 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3007 ) 3008 return f"{this} ({args})" 3009 3010 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 3011 partition = self.expressions(expression, key="partition_by", flat=True) 3012 return f"PARTITION BY {partition}" if partition else "" 3013 3014 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3015 kind = self.sql(expression, "kind") 3016 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3017 end = ( 3018 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3019 or "CURRENT ROW" 3020 ) 3021 3022 window_spec = f"{kind} BETWEEN {start} AND {end}" 3023 3024 exclude = self.sql(expression, "exclude") 3025 if exclude: 3026 if self.SUPPORTS_WINDOW_EXCLUDE: 3027 window_spec += f" EXCLUDE {exclude}" 3028 else: 3029 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3030 3031 return window_spec 3032 3033 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 3034 this = self.sql(expression, "this") 3035 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 3036 return f"{this} WITHIN GROUP ({expression_sql})" 3037 3038 def between_sql(self, expression: exp.Between) -> str: 3039 this = self.sql(expression, "this") 3040 low = self.sql(expression, "low") 3041 high = self.sql(expression, "high") 3042 symmetric = expression.args.get("symmetric") 3043 3044 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3045 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3046 3047 flag = ( 3048 " SYMMETRIC" 3049 if symmetric 3050 else " ASYMMETRIC" 3051 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3052 else "" # silently drop ASYMMETRIC – semantics identical 3053 ) 3054 return f"{this} BETWEEN{flag} {low} AND {high}" 3055 3056 def bracket_offset_expressions( 3057 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 3058 ) -> t.List[exp.Expression]: 3059 return apply_index_offset( 3060 expression.this, 3061 expression.expressions, 3062 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3063 dialect=self.dialect, 3064 ) 3065 3066 def bracket_sql(self, expression: exp.Bracket) -> str: 3067 expressions = self.bracket_offset_expressions(expression) 3068 expressions_sql = ", ".join(self.sql(e) for e in expressions) 3069 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 3070 3071 def all_sql(self, expression: exp.All) -> str: 3072 this = self.sql(expression, "this") 3073 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 3074 this = self.wrap(this) 3075 return f"ALL {this}" 3076 3077 def any_sql(self, expression: exp.Any) -> str: 3078 this = self.sql(expression, "this") 3079 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3080 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3081 this = self.wrap(this) 3082 return f"ANY{this}" 3083 return f"ANY {this}" 3084 3085 def exists_sql(self, expression: exp.Exists) -> str: 3086 return f"EXISTS{self.wrap(expression)}" 3087 3088 def case_sql(self, expression: exp.Case) -> str: 3089 this = self.sql(expression, "this") 3090 statements = [f"CASE {this}" if this else "CASE"] 3091 3092 for e in expression.args["ifs"]: 3093 statements.append(f"WHEN {self.sql(e, 'this')}") 3094 statements.append(f"THEN {self.sql(e, 'true')}") 3095 3096 default = self.sql(expression, "default") 3097 3098 if default: 3099 statements.append(f"ELSE {default}") 3100 3101 statements.append("END") 3102 3103 if self.pretty and self.too_wide(statements): 3104 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3105 3106 return " ".join(statements) 3107 3108 def constraint_sql(self, expression: exp.Constraint) -> str: 3109 this = self.sql(expression, "this") 3110 expressions = self.expressions(expression, flat=True) 3111 return f"CONSTRAINT {this} {expressions}" 3112 3113 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 3114 order = expression.args.get("order") 3115 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 3116 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 3117 3118 def extract_sql(self, expression: exp.Extract) -> str: 3119 from sqlglot.dialects.dialect import map_date_part 3120 3121 this = ( 3122 map_date_part(expression.this, self.dialect) 3123 if self.NORMALIZE_EXTRACT_DATE_PARTS 3124 else expression.this 3125 ) 3126 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3127 expression_sql = self.sql(expression, "expression") 3128 3129 return f"EXTRACT({this_sql} FROM {expression_sql})" 3130 3131 def trim_sql(self, expression: exp.Trim) -> str: 3132 trim_type = self.sql(expression, "position") 3133 3134 if trim_type == "LEADING": 3135 func_name = "LTRIM" 3136 elif trim_type == "TRAILING": 3137 func_name = "RTRIM" 3138 else: 3139 func_name = "TRIM" 3140 3141 return self.func(func_name, expression.this, expression.expression) 3142 3143 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3144 args = expression.expressions 3145 if isinstance(expression, exp.ConcatWs): 3146 args = args[1:] # Skip the delimiter 3147 3148 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3149 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3150 3151 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3152 3153 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3154 if not e.type: 3155 from sqlglot.optimizer.annotate_types import annotate_types 3156 3157 e = annotate_types(e, dialect=self.dialect) 3158 3159 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3160 return e 3161 3162 return exp.func("coalesce", e, exp.Literal.string("")) 3163 3164 args = [_wrap_with_coalesce(e) for e in args] 3165 3166 return args 3167 3168 def concat_sql(self, expression: exp.Concat) -> str: 3169 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3170 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3171 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3172 # instead of coalescing them to empty string. 3173 from sqlglot.dialects.dialect import concat_to_dpipe_sql 3174 3175 return concat_to_dpipe_sql(self, expression) 3176 3177 expressions = self.convert_concat_args(expression) 3178 3179 # Some dialects don't allow a single-argument CONCAT call 3180 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3181 return self.sql(expressions[0]) 3182 3183 return self.func("CONCAT", *expressions) 3184 3185 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3186 return self.func( 3187 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3188 ) 3189 3190 def check_sql(self, expression: exp.Check) -> str: 3191 this = self.sql(expression, key="this") 3192 return f"CHECK ({this})" 3193 3194 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3195 expressions = self.expressions(expression, flat=True) 3196 expressions = f" ({expressions})" if expressions else "" 3197 reference = self.sql(expression, "reference") 3198 reference = f" {reference}" if reference else "" 3199 delete = self.sql(expression, "delete") 3200 delete = f" ON DELETE {delete}" if delete else "" 3201 update = self.sql(expression, "update") 3202 update = f" ON UPDATE {update}" if update else "" 3203 options = self.expressions(expression, key="options", flat=True, sep=" ") 3204 options = f" {options}" if options else "" 3205 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3206 3207 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3208 this = self.sql(expression, "this") 3209 this = f" {this}" if this else "" 3210 expressions = self.expressions(expression, flat=True) 3211 include = self.sql(expression, "include") 3212 options = self.expressions(expression, key="options", flat=True, sep=" ") 3213 options = f" {options}" if options else "" 3214 return f"PRIMARY KEY{this} ({expressions}){include}{options}" 3215 3216 def if_sql(self, expression: exp.If) -> str: 3217 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3218 3219 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3220 if self.MATCH_AGAINST_TABLE_PREFIX: 3221 expressions = [] 3222 for expr in expression.expressions: 3223 if isinstance(expr, exp.Table): 3224 expressions.append(f"TABLE {self.sql(expr)}") 3225 else: 3226 expressions.append(expr) 3227 else: 3228 expressions = expression.expressions 3229 3230 modifier = expression.args.get("modifier") 3231 modifier = f" {modifier}" if modifier else "" 3232 return ( 3233 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3234 ) 3235 3236 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3237 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3238 3239 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3240 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3241 3242 if expression.args.get("escape"): 3243 path = self.escape_str(path) 3244 3245 if self.QUOTE_JSON_PATH: 3246 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3247 3248 return path 3249 3250 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3251 if isinstance(expression, exp.JSONPathPart): 3252 transform = self.TRANSFORMS.get(expression.__class__) 3253 if not callable(transform): 3254 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3255 return "" 3256 3257 return transform(self, expression) 3258 3259 if isinstance(expression, int): 3260 return str(expression) 3261 3262 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3263 escaped = expression.replace("'", "\\'") 3264 escaped = f"\\'{expression}\\'" 3265 else: 3266 escaped = expression.replace('"', '\\"') 3267 escaped = f'"{escaped}"' 3268 3269 return escaped 3270 3271 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3272 return f"{self.sql(expression, 'this')} FORMAT JSON" 3273 3274 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3275 # Output the Teradata column FORMAT override. 3276 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3277 this = self.sql(expression, "this") 3278 fmt = self.sql(expression, "format") 3279 return f"{this} (FORMAT {fmt})" 3280 3281 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3282 null_handling = expression.args.get("null_handling") 3283 null_handling = f" {null_handling}" if null_handling else "" 3284 3285 unique_keys = expression.args.get("unique_keys") 3286 if unique_keys is not None: 3287 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3288 else: 3289 unique_keys = "" 3290 3291 return_type = self.sql(expression, "return_type") 3292 return_type = f" RETURNING {return_type}" if return_type else "" 3293 encoding = self.sql(expression, "encoding") 3294 encoding = f" ENCODING {encoding}" if encoding else "" 3295 3296 return self.func( 3297 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3298 *expression.expressions, 3299 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3300 ) 3301 3302 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3303 return self.jsonobject_sql(expression) 3304 3305 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3306 null_handling = expression.args.get("null_handling") 3307 null_handling = f" {null_handling}" if null_handling else "" 3308 return_type = self.sql(expression, "return_type") 3309 return_type = f" RETURNING {return_type}" if return_type else "" 3310 strict = " STRICT" if expression.args.get("strict") else "" 3311 return self.func( 3312 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3313 ) 3314 3315 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3316 this = self.sql(expression, "this") 3317 order = self.sql(expression, "order") 3318 null_handling = expression.args.get("null_handling") 3319 null_handling = f" {null_handling}" if null_handling else "" 3320 return_type = self.sql(expression, "return_type") 3321 return_type = f" RETURNING {return_type}" if return_type else "" 3322 strict = " STRICT" if expression.args.get("strict") else "" 3323 return self.func( 3324 "JSON_ARRAYAGG", 3325 this, 3326 suffix=f"{order}{null_handling}{return_type}{strict})", 3327 ) 3328 3329 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3330 path = self.sql(expression, "path") 3331 path = f" PATH {path}" if path else "" 3332 nested_schema = self.sql(expression, "nested_schema") 3333 3334 if nested_schema: 3335 return f"NESTED{path} {nested_schema}" 3336 3337 this = self.sql(expression, "this") 3338 kind = self.sql(expression, "kind") 3339 kind = f" {kind}" if kind else "" 3340 3341 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3342 return f"{this}{kind}{path}{ordinality}" 3343 3344 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3345 return self.func("COLUMNS", *expression.expressions) 3346 3347 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3348 this = self.sql(expression, "this") 3349 path = self.sql(expression, "path") 3350 path = f", {path}" if path else "" 3351 error_handling = expression.args.get("error_handling") 3352 error_handling = f" {error_handling}" if error_handling else "" 3353 empty_handling = expression.args.get("empty_handling") 3354 empty_handling = f" {empty_handling}" if empty_handling else "" 3355 schema = self.sql(expression, "schema") 3356 return self.func( 3357 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3358 ) 3359 3360 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3361 this = self.sql(expression, "this") 3362 kind = self.sql(expression, "kind") 3363 path = self.sql(expression, "path") 3364 path = f" {path}" if path else "" 3365 as_json = " AS JSON" if expression.args.get("as_json") else "" 3366 return f"{this} {kind}{path}{as_json}" 3367 3368 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3369 this = self.sql(expression, "this") 3370 path = self.sql(expression, "path") 3371 path = f", {path}" if path else "" 3372 expressions = self.expressions(expression) 3373 with_ = ( 3374 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3375 if expressions 3376 else "" 3377 ) 3378 return f"OPENJSON({this}{path}){with_}" 3379 3380 def in_sql(self, expression: exp.In) -> str: 3381 query = expression.args.get("query") 3382 unnest = expression.args.get("unnest") 3383 field = expression.args.get("field") 3384 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3385 3386 if query: 3387 in_sql = self.sql(query) 3388 elif unnest: 3389 in_sql = self.in_unnest_op(unnest) 3390 elif field: 3391 in_sql = self.sql(field) 3392 else: 3393 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3394 3395 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3396 3397 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3398 return f"(SELECT {self.sql(unnest)})" 3399 3400 def interval_sql(self, expression: exp.Interval) -> str: 3401 unit_expression = expression.args.get("unit") 3402 unit = self.sql(unit_expression) if unit_expression else "" 3403 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3404 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3405 unit = f" {unit}" if unit else "" 3406 3407 if self.SINGLE_STRING_INTERVAL: 3408 this = expression.this.name if expression.this else "" 3409 if this: 3410 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3411 return f"INTERVAL '{this}'{unit}" 3412 return f"INTERVAL '{this}{unit}'" 3413 return f"INTERVAL{unit}" 3414 3415 this = self.sql(expression, "this") 3416 if this: 3417 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3418 this = f" {this}" if unwrapped else f" ({this})" 3419 3420 return f"INTERVAL{this}{unit}" 3421 3422 def return_sql(self, expression: exp.Return) -> str: 3423 return f"RETURN {self.sql(expression, 'this')}" 3424 3425 def reference_sql(self, expression: exp.Reference) -> str: 3426 this = self.sql(expression, "this") 3427 expressions = self.expressions(expression, flat=True) 3428 expressions = f"({expressions})" if expressions else "" 3429 options = self.expressions(expression, key="options", flat=True, sep=" ") 3430 options = f" {options}" if options else "" 3431 return f"REFERENCES {this}{expressions}{options}" 3432 3433 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3434 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3435 parent = expression.parent 3436 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3437 return self.func( 3438 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3439 ) 3440 3441 def paren_sql(self, expression: exp.Paren) -> str: 3442 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3443 return f"({sql}{self.seg(')', sep='')}" 3444 3445 def neg_sql(self, expression: exp.Neg) -> str: 3446 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3447 this_sql = self.sql(expression, "this") 3448 sep = " " if this_sql[0] == "-" else "" 3449 return f"-{sep}{this_sql}" 3450 3451 def not_sql(self, expression: exp.Not) -> str: 3452 return f"NOT {self.sql(expression, 'this')}" 3453 3454 def alias_sql(self, expression: exp.Alias) -> str: 3455 alias = self.sql(expression, "alias") 3456 alias = f" AS {alias}" if alias else "" 3457 return f"{self.sql(expression, 'this')}{alias}" 3458 3459 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3460 alias = expression.args["alias"] 3461 3462 parent = expression.parent 3463 pivot = parent and parent.parent 3464 3465 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3466 identifier_alias = isinstance(alias, exp.Identifier) 3467 literal_alias = isinstance(alias, exp.Literal) 3468 3469 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3470 alias.replace(exp.Literal.string(alias.output_name)) 3471 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3472 alias.replace(exp.to_identifier(alias.output_name)) 3473 3474 return self.alias_sql(expression) 3475 3476 def aliases_sql(self, expression: exp.Aliases) -> str: 3477 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3478 3479 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3480 this = self.sql(expression, "this") 3481 index = self.sql(expression, "expression") 3482 return f"{this} AT {index}" 3483 3484 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3485 this = self.sql(expression, "this") 3486 zone = self.sql(expression, "zone") 3487 return f"{this} AT TIME ZONE {zone}" 3488 3489 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3490 this = self.sql(expression, "this") 3491 zone = self.sql(expression, "zone") 3492 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3493 3494 def add_sql(self, expression: exp.Add) -> str: 3495 return self.binary(expression, "+") 3496 3497 def and_sql( 3498 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3499 ) -> str: 3500 return self.connector_sql(expression, "AND", stack) 3501 3502 def or_sql( 3503 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3504 ) -> str: 3505 return self.connector_sql(expression, "OR", stack) 3506 3507 def xor_sql( 3508 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3509 ) -> str: 3510 return self.connector_sql(expression, "XOR", stack) 3511 3512 def connector_sql( 3513 self, 3514 expression: exp.Connector, 3515 op: str, 3516 stack: t.Optional[t.List[str | exp.Expression]] = None, 3517 ) -> str: 3518 if stack is not None: 3519 if expression.expressions: 3520 stack.append(self.expressions(expression, sep=f" {op} ")) 3521 else: 3522 stack.append(expression.right) 3523 if expression.comments and self.comments: 3524 for comment in expression.comments: 3525 if comment: 3526 op += f" /*{self.sanitize_comment(comment)}*/" 3527 stack.extend((op, expression.left)) 3528 return op 3529 3530 stack = [expression] 3531 sqls: t.List[str] = [] 3532 ops = set() 3533 3534 while stack: 3535 node = stack.pop() 3536 if isinstance(node, exp.Connector): 3537 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3538 else: 3539 sql = self.sql(node) 3540 if sqls and sqls[-1] in ops: 3541 sqls[-1] += f" {sql}" 3542 else: 3543 sqls.append(sql) 3544 3545 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3546 return sep.join(sqls) 3547 3548 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3549 return self.binary(expression, "&") 3550 3551 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3552 return self.binary(expression, "<<") 3553 3554 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3555 return f"~{self.sql(expression, 'this')}" 3556 3557 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3558 return self.binary(expression, "|") 3559 3560 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3561 return self.binary(expression, ">>") 3562 3563 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3564 return self.binary(expression, "^") 3565 3566 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3567 format_sql = self.sql(expression, "format") 3568 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3569 to_sql = self.sql(expression, "to") 3570 to_sql = f" {to_sql}" if to_sql else "" 3571 action = self.sql(expression, "action") 3572 action = f" {action}" if action else "" 3573 default = self.sql(expression, "default") 3574 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3575 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3576 3577 # Base implementation that excludes safe, zone, and target_type metadata args 3578 def strtotime_sql(self, expression: exp.StrToTime) -> str: 3579 return self.func("STR_TO_TIME", expression.this, expression.args.get("format")) 3580 3581 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3582 zone = self.sql(expression, "this") 3583 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3584 3585 def collate_sql(self, expression: exp.Collate) -> str: 3586 if self.COLLATE_IS_FUNC: 3587 return self.function_fallback_sql(expression) 3588 return self.binary(expression, "COLLATE") 3589 3590 def command_sql(self, expression: exp.Command) -> str: 3591 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3592 3593 def comment_sql(self, expression: exp.Comment) -> str: 3594 this = self.sql(expression, "this") 3595 kind = expression.args["kind"] 3596 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3597 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3598 expression_sql = self.sql(expression, "expression") 3599 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3600 3601 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3602 this = self.sql(expression, "this") 3603 delete = " DELETE" if expression.args.get("delete") else "" 3604 recompress = self.sql(expression, "recompress") 3605 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3606 to_disk = self.sql(expression, "to_disk") 3607 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3608 to_volume = self.sql(expression, "to_volume") 3609 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3610 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3611 3612 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3613 where = self.sql(expression, "where") 3614 group = self.sql(expression, "group") 3615 aggregates = self.expressions(expression, key="aggregates") 3616 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3617 3618 if not (where or group or aggregates) and len(expression.expressions) == 1: 3619 return f"TTL {self.expressions(expression, flat=True)}" 3620 3621 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3622 3623 def transaction_sql(self, expression: exp.Transaction) -> str: 3624 modes = self.expressions(expression, key="modes") 3625 modes = f" {modes}" if modes else "" 3626 return f"BEGIN{modes}" 3627 3628 def commit_sql(self, expression: exp.Commit) -> str: 3629 chain = expression.args.get("chain") 3630 if chain is not None: 3631 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3632 3633 return f"COMMIT{chain or ''}" 3634 3635 def rollback_sql(self, expression: exp.Rollback) -> str: 3636 savepoint = expression.args.get("savepoint") 3637 savepoint = f" TO {savepoint}" if savepoint else "" 3638 return f"ROLLBACK{savepoint}" 3639 3640 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3641 this = self.sql(expression, "this") 3642 3643 dtype = self.sql(expression, "dtype") 3644 if dtype: 3645 collate = self.sql(expression, "collate") 3646 collate = f" COLLATE {collate}" if collate else "" 3647 using = self.sql(expression, "using") 3648 using = f" USING {using}" if using else "" 3649 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3650 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3651 3652 default = self.sql(expression, "default") 3653 if default: 3654 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3655 3656 comment = self.sql(expression, "comment") 3657 if comment: 3658 return f"ALTER COLUMN {this} COMMENT {comment}" 3659 3660 visible = expression.args.get("visible") 3661 if visible: 3662 return f"ALTER COLUMN {this} SET {visible}" 3663 3664 allow_null = expression.args.get("allow_null") 3665 drop = expression.args.get("drop") 3666 3667 if not drop and not allow_null: 3668 self.unsupported("Unsupported ALTER COLUMN syntax") 3669 3670 if allow_null is not None: 3671 keyword = "DROP" if drop else "SET" 3672 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3673 3674 return f"ALTER COLUMN {this} DROP DEFAULT" 3675 3676 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3677 this = self.sql(expression, "this") 3678 3679 visible = expression.args.get("visible") 3680 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3681 3682 return f"ALTER INDEX {this} {visible_sql}" 3683 3684 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3685 this = self.sql(expression, "this") 3686 if not isinstance(expression.this, exp.Var): 3687 this = f"KEY DISTKEY {this}" 3688 return f"ALTER DISTSTYLE {this}" 3689 3690 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3691 compound = " COMPOUND" if expression.args.get("compound") else "" 3692 this = self.sql(expression, "this") 3693 expressions = self.expressions(expression, flat=True) 3694 expressions = f"({expressions})" if expressions else "" 3695 return f"ALTER{compound} SORTKEY {this or expressions}" 3696 3697 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3698 if not self.RENAME_TABLE_WITH_DB: 3699 # Remove db from tables 3700 expression = expression.transform( 3701 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3702 ).assert_is(exp.AlterRename) 3703 this = self.sql(expression, "this") 3704 to_kw = " TO" if include_to else "" 3705 return f"RENAME{to_kw} {this}" 3706 3707 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3708 exists = " IF EXISTS" if expression.args.get("exists") else "" 3709 old_column = self.sql(expression, "this") 3710 new_column = self.sql(expression, "to") 3711 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3712 3713 def alterset_sql(self, expression: exp.AlterSet) -> str: 3714 exprs = self.expressions(expression, flat=True) 3715 if self.ALTER_SET_WRAPPED: 3716 exprs = f"({exprs})" 3717 3718 return f"SET {exprs}" 3719 3720 def alter_sql(self, expression: exp.Alter) -> str: 3721 actions = expression.args["actions"] 3722 3723 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3724 actions[0], exp.ColumnDef 3725 ): 3726 actions_sql = self.expressions(expression, key="actions", flat=True) 3727 actions_sql = f"ADD {actions_sql}" 3728 else: 3729 actions_list = [] 3730 for action in actions: 3731 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3732 action_sql = self.add_column_sql(action) 3733 else: 3734 action_sql = self.sql(action) 3735 if isinstance(action, exp.Query): 3736 action_sql = f"AS {action_sql}" 3737 3738 actions_list.append(action_sql) 3739 3740 actions_sql = self.format_args(*actions_list).lstrip("\n") 3741 3742 exists = " IF EXISTS" if expression.args.get("exists") else "" 3743 on_cluster = self.sql(expression, "cluster") 3744 on_cluster = f" {on_cluster}" if on_cluster else "" 3745 only = " ONLY" if expression.args.get("only") else "" 3746 options = self.expressions(expression, key="options") 3747 options = f", {options}" if options else "" 3748 kind = self.sql(expression, "kind") 3749 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3750 check = " WITH CHECK" if expression.args.get("check") else "" 3751 cascade = ( 3752 " CASCADE" 3753 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3754 else "" 3755 ) 3756 this = self.sql(expression, "this") 3757 this = f" {this}" if this else "" 3758 3759 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}" 3760 3761 def altersession_sql(self, expression: exp.AlterSession) -> str: 3762 items_sql = self.expressions(expression, flat=True) 3763 keyword = "UNSET" if expression.args.get("unset") else "SET" 3764 return f"{keyword} {items_sql}" 3765 3766 def add_column_sql(self, expression: exp.Expression) -> str: 3767 sql = self.sql(expression) 3768 if isinstance(expression, exp.Schema): 3769 column_text = " COLUMNS" 3770 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3771 column_text = " COLUMN" 3772 else: 3773 column_text = "" 3774 3775 return f"ADD{column_text} {sql}" 3776 3777 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3778 expressions = self.expressions(expression) 3779 exists = " IF EXISTS " if expression.args.get("exists") else " " 3780 return f"DROP{exists}{expressions}" 3781 3782 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3783 return f"ADD {self.expressions(expression, indent=False)}" 3784 3785 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3786 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3787 location = self.sql(expression, "location") 3788 location = f" {location}" if location else "" 3789 return f"ADD {exists}{self.sql(expression.this)}{location}" 3790 3791 def distinct_sql(self, expression: exp.Distinct) -> str: 3792 this = self.expressions(expression, flat=True) 3793 3794 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3795 case = exp.case() 3796 for arg in expression.expressions: 3797 case = case.when(arg.is_(exp.null()), exp.null()) 3798 this = self.sql(case.else_(f"({this})")) 3799 3800 this = f" {this}" if this else "" 3801 3802 on = self.sql(expression, "on") 3803 on = f" ON {on}" if on else "" 3804 return f"DISTINCT{this}{on}" 3805 3806 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3807 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3808 3809 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3810 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3811 3812 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3813 this_sql = self.sql(expression, "this") 3814 expression_sql = self.sql(expression, "expression") 3815 kind = "MAX" if expression.args.get("max") else "MIN" 3816 return f"{this_sql} HAVING {kind} {expression_sql}" 3817 3818 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3819 return self.sql( 3820 exp.Cast( 3821 this=exp.Div(this=expression.this, expression=expression.expression), 3822 to=exp.DataType(this=exp.DataType.Type.INT), 3823 ) 3824 ) 3825 3826 def dpipe_sql(self, expression: exp.DPipe) -> str: 3827 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3828 return self.func( 3829 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3830 ) 3831 return self.binary(expression, "||") 3832 3833 def div_sql(self, expression: exp.Div) -> str: 3834 l, r = expression.left, expression.right 3835 3836 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3837 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3838 3839 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3840 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3841 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3842 3843 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3844 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3845 return self.sql( 3846 exp.cast( 3847 l / r, 3848 to=exp.DataType.Type.BIGINT, 3849 ) 3850 ) 3851 3852 return self.binary(expression, "/") 3853 3854 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3855 n = exp._wrap(expression.this, exp.Binary) 3856 d = exp._wrap(expression.expression, exp.Binary) 3857 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3858 3859 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3860 return self.binary(expression, "OVERLAPS") 3861 3862 def distance_sql(self, expression: exp.Distance) -> str: 3863 return self.binary(expression, "<->") 3864 3865 def dot_sql(self, expression: exp.Dot) -> str: 3866 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3867 3868 def eq_sql(self, expression: exp.EQ) -> str: 3869 return self.binary(expression, "=") 3870 3871 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3872 return self.binary(expression, ":=") 3873 3874 def escape_sql(self, expression: exp.Escape) -> str: 3875 return self.binary(expression, "ESCAPE") 3876 3877 def glob_sql(self, expression: exp.Glob) -> str: 3878 return self.binary(expression, "GLOB") 3879 3880 def gt_sql(self, expression: exp.GT) -> str: 3881 return self.binary(expression, ">") 3882 3883 def gte_sql(self, expression: exp.GTE) -> str: 3884 return self.binary(expression, ">=") 3885 3886 def is_sql(self, expression: exp.Is) -> str: 3887 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3888 return self.sql( 3889 expression.this if expression.expression.this else exp.not_(expression.this) 3890 ) 3891 return self.binary(expression, "IS") 3892 3893 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3894 this = expression.this 3895 rhs = expression.expression 3896 3897 if isinstance(expression, exp.Like): 3898 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3899 op = "LIKE" 3900 else: 3901 exp_class = exp.ILike 3902 op = "ILIKE" 3903 3904 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3905 exprs = rhs.this.unnest() 3906 3907 if isinstance(exprs, exp.Tuple): 3908 exprs = exprs.expressions 3909 3910 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3911 3912 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3913 for expr in exprs[1:]: 3914 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3915 3916 parent = expression.parent 3917 if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition): 3918 like_expr = exp.paren(like_expr, copy=False) 3919 3920 return self.sql(like_expr) 3921 3922 return self.binary(expression, op) 3923 3924 def like_sql(self, expression: exp.Like) -> str: 3925 return self._like_sql(expression) 3926 3927 def ilike_sql(self, expression: exp.ILike) -> str: 3928 return self._like_sql(expression) 3929 3930 def match_sql(self, expression: exp.Match) -> str: 3931 return self.binary(expression, "MATCH") 3932 3933 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3934 return self.binary(expression, "SIMILAR TO") 3935 3936 def lt_sql(self, expression: exp.LT) -> str: 3937 return self.binary(expression, "<") 3938 3939 def lte_sql(self, expression: exp.LTE) -> str: 3940 return self.binary(expression, "<=") 3941 3942 def mod_sql(self, expression: exp.Mod) -> str: 3943 return self.binary(expression, "%") 3944 3945 def mul_sql(self, expression: exp.Mul) -> str: 3946 return self.binary(expression, "*") 3947 3948 def neq_sql(self, expression: exp.NEQ) -> str: 3949 return self.binary(expression, "<>") 3950 3951 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3952 return self.binary(expression, "IS NOT DISTINCT FROM") 3953 3954 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3955 return self.binary(expression, "IS DISTINCT FROM") 3956 3957 def sub_sql(self, expression: exp.Sub) -> str: 3958 return self.binary(expression, "-") 3959 3960 def trycast_sql(self, expression: exp.TryCast) -> str: 3961 return self.cast_sql(expression, safe_prefix="TRY_") 3962 3963 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3964 return self.cast_sql(expression) 3965 3966 def try_sql(self, expression: exp.Try) -> str: 3967 if not self.TRY_SUPPORTED: 3968 self.unsupported("Unsupported TRY function") 3969 return self.sql(expression, "this") 3970 3971 return self.func("TRY", expression.this) 3972 3973 def log_sql(self, expression: exp.Log) -> str: 3974 this = expression.this 3975 expr = expression.expression 3976 3977 if self.dialect.LOG_BASE_FIRST is False: 3978 this, expr = expr, this 3979 elif self.dialect.LOG_BASE_FIRST is None and expr: 3980 if this.name in ("2", "10"): 3981 return self.func(f"LOG{this.name}", expr) 3982 3983 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3984 3985 return self.func("LOG", this, expr) 3986 3987 def use_sql(self, expression: exp.Use) -> str: 3988 kind = self.sql(expression, "kind") 3989 kind = f" {kind}" if kind else "" 3990 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3991 this = f" {this}" if this else "" 3992 return f"USE{kind}{this}" 3993 3994 def binary(self, expression: exp.Binary, op: str) -> str: 3995 sqls: t.List[str] = [] 3996 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3997 binary_type = type(expression) 3998 3999 while stack: 4000 node = stack.pop() 4001 4002 if type(node) is binary_type: 4003 op_func = node.args.get("operator") 4004 if op_func: 4005 op = f"OPERATOR({self.sql(op_func)})" 4006 4007 stack.append(node.right) 4008 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4009 stack.append(node.left) 4010 else: 4011 sqls.append(self.sql(node)) 4012 4013 return "".join(sqls) 4014 4015 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 4016 to_clause = self.sql(expression, "to") 4017 if to_clause: 4018 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 4019 4020 return self.function_fallback_sql(expression) 4021 4022 def function_fallback_sql(self, expression: exp.Func) -> str: 4023 args = [] 4024 4025 for key in expression.arg_types: 4026 arg_value = expression.args.get(key) 4027 4028 if isinstance(arg_value, list): 4029 for value in arg_value: 4030 args.append(value) 4031 elif arg_value is not None: 4032 args.append(arg_value) 4033 4034 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4035 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4036 else: 4037 name = expression.sql_name() 4038 4039 return self.func(name, *args) 4040 4041 def func( 4042 self, 4043 name: str, 4044 *args: t.Optional[exp.Expression | str], 4045 prefix: str = "(", 4046 suffix: str = ")", 4047 normalize: bool = True, 4048 ) -> str: 4049 name = self.normalize_func(name) if normalize else name 4050 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 4051 4052 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 4053 arg_sqls = tuple( 4054 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4055 ) 4056 if self.pretty and self.too_wide(arg_sqls): 4057 return self.indent( 4058 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4059 ) 4060 return sep.join(arg_sqls) 4061 4062 def too_wide(self, args: t.Iterable) -> bool: 4063 return sum(len(arg) for arg in args) > self.max_text_width 4064 4065 def format_time( 4066 self, 4067 expression: exp.Expression, 4068 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 4069 inverse_time_trie: t.Optional[t.Dict] = None, 4070 ) -> t.Optional[str]: 4071 return format_time( 4072 self.sql(expression, "format"), 4073 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4074 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4075 ) 4076 4077 def expressions( 4078 self, 4079 expression: t.Optional[exp.Expression] = None, 4080 key: t.Optional[str] = None, 4081 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 4082 flat: bool = False, 4083 indent: bool = True, 4084 skip_first: bool = False, 4085 skip_last: bool = False, 4086 sep: str = ", ", 4087 prefix: str = "", 4088 dynamic: bool = False, 4089 new_line: bool = False, 4090 ) -> str: 4091 expressions = expression.args.get(key or "expressions") if expression else sqls 4092 4093 if not expressions: 4094 return "" 4095 4096 if flat: 4097 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4098 4099 num_sqls = len(expressions) 4100 result_sqls = [] 4101 4102 for i, e in enumerate(expressions): 4103 sql = self.sql(e, comment=False) 4104 if not sql: 4105 continue 4106 4107 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 4108 4109 if self.pretty: 4110 if self.leading_comma: 4111 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4112 else: 4113 result_sqls.append( 4114 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4115 ) 4116 else: 4117 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4118 4119 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4120 if new_line: 4121 result_sqls.insert(0, "") 4122 result_sqls.append("") 4123 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4124 else: 4125 result_sql = "".join(result_sqls) 4126 4127 return ( 4128 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4129 if indent 4130 else result_sql 4131 ) 4132 4133 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4134 flat = flat or isinstance(expression.parent, exp.Properties) 4135 expressions_sql = self.expressions(expression, flat=flat) 4136 if flat: 4137 return f"{op} {expressions_sql}" 4138 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 4139 4140 def naked_property(self, expression: exp.Property) -> str: 4141 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4142 if not property_name: 4143 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4144 return f"{property_name} {self.sql(expression, 'this')}" 4145 4146 def tag_sql(self, expression: exp.Tag) -> str: 4147 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 4148 4149 def token_sql(self, token_type: TokenType) -> str: 4150 return self.TOKEN_MAPPING.get(token_type, token_type.name) 4151 4152 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4153 this = self.sql(expression, "this") 4154 expressions = self.no_identify(self.expressions, expression) 4155 expressions = ( 4156 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4157 ) 4158 return f"{this}{expressions}" if expressions.strip() != "" else this 4159 4160 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4161 this = self.sql(expression, "this") 4162 expressions = self.expressions(expression, flat=True) 4163 return f"{this}({expressions})" 4164 4165 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4166 return self.binary(expression, "=>") 4167 4168 def when_sql(self, expression: exp.When) -> str: 4169 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4170 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4171 condition = self.sql(expression, "condition") 4172 condition = f" AND {condition}" if condition else "" 4173 4174 then_expression = expression.args.get("then") 4175 if isinstance(then_expression, exp.Insert): 4176 this = self.sql(then_expression, "this") 4177 this = f"INSERT {this}" if this else "INSERT" 4178 then = self.sql(then_expression, "expression") 4179 then = f"{this} VALUES {then}" if then else this 4180 elif isinstance(then_expression, exp.Update): 4181 if isinstance(then_expression.args.get("expressions"), exp.Star): 4182 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4183 else: 4184 expressions_sql = self.expressions(then_expression) 4185 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4186 4187 else: 4188 then = self.sql(then_expression) 4189 return f"WHEN {matched}{source}{condition} THEN {then}" 4190 4191 def whens_sql(self, expression: exp.Whens) -> str: 4192 return self.expressions(expression, sep=" ", indent=False) 4193 4194 def merge_sql(self, expression: exp.Merge) -> str: 4195 table = expression.this 4196 table_alias = "" 4197 4198 hints = table.args.get("hints") 4199 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4200 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4201 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4202 4203 this = self.sql(table) 4204 using = f"USING {self.sql(expression, 'using')}" 4205 whens = self.sql(expression, "whens") 4206 4207 on = self.sql(expression, "on") 4208 on = f"ON {on}" if on else "" 4209 4210 if not on: 4211 on = self.expressions(expression, key="using_cond") 4212 on = f"USING ({on})" if on else "" 4213 4214 returning = self.sql(expression, "returning") 4215 if returning: 4216 whens = f"{whens}{returning}" 4217 4218 sep = self.sep() 4219 4220 return self.prepend_ctes( 4221 expression, 4222 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4223 ) 4224 4225 @unsupported_args("format") 4226 def tochar_sql(self, expression: exp.ToChar) -> str: 4227 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4228 4229 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4230 if not self.SUPPORTS_TO_NUMBER: 4231 self.unsupported("Unsupported TO_NUMBER function") 4232 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4233 4234 fmt = expression.args.get("format") 4235 if not fmt: 4236 self.unsupported("Conversion format is required for TO_NUMBER") 4237 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4238 4239 return self.func("TO_NUMBER", expression.this, fmt) 4240 4241 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4242 this = self.sql(expression, "this") 4243 kind = self.sql(expression, "kind") 4244 settings_sql = self.expressions(expression, key="settings", sep=" ") 4245 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4246 return f"{this}({kind}{args})" 4247 4248 def dictrange_sql(self, expression: exp.DictRange) -> str: 4249 this = self.sql(expression, "this") 4250 max = self.sql(expression, "max") 4251 min = self.sql(expression, "min") 4252 return f"{this}(MIN {min} MAX {max})" 4253 4254 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4255 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4256 4257 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4258 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4259 4260 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4261 def uniquekeyproperty_sql( 4262 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4263 ) -> str: 4264 return f"{prefix} ({self.expressions(expression, flat=True)})" 4265 4266 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4267 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4268 expressions = self.expressions(expression, flat=True) 4269 expressions = f" {self.wrap(expressions)}" if expressions else "" 4270 buckets = self.sql(expression, "buckets") 4271 kind = self.sql(expression, "kind") 4272 buckets = f" BUCKETS {buckets}" if buckets else "" 4273 order = self.sql(expression, "order") 4274 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4275 4276 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4277 return "" 4278 4279 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4280 expressions = self.expressions(expression, key="expressions", flat=True) 4281 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4282 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4283 buckets = self.sql(expression, "buckets") 4284 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4285 4286 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4287 this = self.sql(expression, "this") 4288 having = self.sql(expression, "having") 4289 4290 if having: 4291 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4292 4293 return self.func("ANY_VALUE", this) 4294 4295 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4296 transform = self.func("TRANSFORM", *expression.expressions) 4297 row_format_before = self.sql(expression, "row_format_before") 4298 row_format_before = f" {row_format_before}" if row_format_before else "" 4299 record_writer = self.sql(expression, "record_writer") 4300 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4301 using = f" USING {self.sql(expression, 'command_script')}" 4302 schema = self.sql(expression, "schema") 4303 schema = f" AS {schema}" if schema else "" 4304 row_format_after = self.sql(expression, "row_format_after") 4305 row_format_after = f" {row_format_after}" if row_format_after else "" 4306 record_reader = self.sql(expression, "record_reader") 4307 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4308 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4309 4310 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4311 key_block_size = self.sql(expression, "key_block_size") 4312 if key_block_size: 4313 return f"KEY_BLOCK_SIZE = {key_block_size}" 4314 4315 using = self.sql(expression, "using") 4316 if using: 4317 return f"USING {using}" 4318 4319 parser = self.sql(expression, "parser") 4320 if parser: 4321 return f"WITH PARSER {parser}" 4322 4323 comment = self.sql(expression, "comment") 4324 if comment: 4325 return f"COMMENT {comment}" 4326 4327 visible = expression.args.get("visible") 4328 if visible is not None: 4329 return "VISIBLE" if visible else "INVISIBLE" 4330 4331 engine_attr = self.sql(expression, "engine_attr") 4332 if engine_attr: 4333 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4334 4335 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4336 if secondary_engine_attr: 4337 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4338 4339 self.unsupported("Unsupported index constraint option.") 4340 return "" 4341 4342 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4343 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4344 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4345 4346 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4347 kind = self.sql(expression, "kind") 4348 kind = f"{kind} INDEX" if kind else "INDEX" 4349 this = self.sql(expression, "this") 4350 this = f" {this}" if this else "" 4351 index_type = self.sql(expression, "index_type") 4352 index_type = f" USING {index_type}" if index_type else "" 4353 expressions = self.expressions(expression, flat=True) 4354 expressions = f" ({expressions})" if expressions else "" 4355 options = self.expressions(expression, key="options", sep=" ") 4356 options = f" {options}" if options else "" 4357 return f"{kind}{this}{index_type}{expressions}{options}" 4358 4359 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4360 if self.NVL2_SUPPORTED: 4361 return self.function_fallback_sql(expression) 4362 4363 case = exp.Case().when( 4364 expression.this.is_(exp.null()).not_(copy=False), 4365 expression.args["true"], 4366 copy=False, 4367 ) 4368 else_cond = expression.args.get("false") 4369 if else_cond: 4370 case.else_(else_cond, copy=False) 4371 4372 return self.sql(case) 4373 4374 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4375 this = self.sql(expression, "this") 4376 expr = self.sql(expression, "expression") 4377 position = self.sql(expression, "position") 4378 position = f", {position}" if position else "" 4379 iterator = self.sql(expression, "iterator") 4380 condition = self.sql(expression, "condition") 4381 condition = f" IF {condition}" if condition else "" 4382 return f"{this} FOR {expr}{position} IN {iterator}{condition}" 4383 4384 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4385 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4386 4387 def opclass_sql(self, expression: exp.Opclass) -> str: 4388 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4389 4390 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4391 model = self.sql(expression, "this") 4392 model = f"MODEL {model}" 4393 expr = expression.expression 4394 if expr: 4395 expr_sql = self.sql(expression, "expression") 4396 expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql 4397 else: 4398 expr_sql = None 4399 4400 parameters = self.sql(expression, "params_struct") or None 4401 4402 return self.func(name, model, expr_sql, parameters) 4403 4404 def predict_sql(self, expression: exp.Predict) -> str: 4405 return self._ml_sql(expression, "PREDICT") 4406 4407 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4408 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4409 return self._ml_sql(expression, name) 4410 4411 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4412 return self._ml_sql(expression, "TRANSLATE") 4413 4414 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4415 return self._ml_sql(expression, "FORECAST") 4416 4417 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4418 this_sql = self.sql(expression, "this") 4419 if isinstance(expression.this, exp.Table): 4420 this_sql = f"TABLE {this_sql}" 4421 4422 return self.func( 4423 "FEATURES_AT_TIME", 4424 this_sql, 4425 expression.args.get("time"), 4426 expression.args.get("num_rows"), 4427 expression.args.get("ignore_feature_nulls"), 4428 ) 4429 4430 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4431 this_sql = self.sql(expression, "this") 4432 if isinstance(expression.this, exp.Table): 4433 this_sql = f"TABLE {this_sql}" 4434 4435 query_table = self.sql(expression, "query_table") 4436 if isinstance(expression.args["query_table"], exp.Table): 4437 query_table = f"TABLE {query_table}" 4438 4439 return self.func( 4440 "VECTOR_SEARCH", 4441 this_sql, 4442 expression.args.get("column_to_search"), 4443 query_table, 4444 expression.args.get("query_column_to_search"), 4445 expression.args.get("top_k"), 4446 expression.args.get("distance_type"), 4447 expression.args.get("options"), 4448 ) 4449 4450 def forin_sql(self, expression: exp.ForIn) -> str: 4451 this = self.sql(expression, "this") 4452 expression_sql = self.sql(expression, "expression") 4453 return f"FOR {this} DO {expression_sql}" 4454 4455 def refresh_sql(self, expression: exp.Refresh) -> str: 4456 this = self.sql(expression, "this") 4457 kind = "" if isinstance(expression.this, exp.Literal) else f"{expression.text('kind')} " 4458 return f"REFRESH {kind}{this}" 4459 4460 def toarray_sql(self, expression: exp.ToArray) -> str: 4461 arg = expression.this 4462 if not arg.type: 4463 from sqlglot.optimizer.annotate_types import annotate_types 4464 4465 arg = annotate_types(arg, dialect=self.dialect) 4466 4467 if arg.is_type(exp.DataType.Type.ARRAY): 4468 return self.sql(arg) 4469 4470 cond_for_null = arg.is_(exp.null()) 4471 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4472 4473 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4474 this = expression.this 4475 time_format = self.format_time(expression) 4476 4477 if time_format: 4478 return self.sql( 4479 exp.cast( 4480 exp.StrToTime(this=this, format=expression.args["format"]), 4481 exp.DataType.Type.TIME, 4482 ) 4483 ) 4484 4485 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4486 return self.sql(this) 4487 4488 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4489 4490 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4491 this = expression.this 4492 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4493 return self.sql(this) 4494 4495 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4496 4497 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4498 this = expression.this 4499 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4500 return self.sql(this) 4501 4502 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4503 4504 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4505 this = expression.this 4506 time_format = self.format_time(expression) 4507 4508 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4509 return self.sql( 4510 exp.cast( 4511 exp.StrToTime(this=this, format=expression.args["format"]), 4512 exp.DataType.Type.DATE, 4513 ) 4514 ) 4515 4516 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4517 return self.sql(this) 4518 4519 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4520 4521 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4522 return self.sql( 4523 exp.func( 4524 "DATEDIFF", 4525 expression.this, 4526 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4527 "day", 4528 ) 4529 ) 4530 4531 def lastday_sql(self, expression: exp.LastDay) -> str: 4532 if self.LAST_DAY_SUPPORTS_DATE_PART: 4533 return self.function_fallback_sql(expression) 4534 4535 unit = expression.text("unit") 4536 if unit and unit != "MONTH": 4537 self.unsupported("Date parts are not supported in LAST_DAY.") 4538 4539 return self.func("LAST_DAY", expression.this) 4540 4541 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4542 from sqlglot.dialects.dialect import unit_to_str 4543 4544 return self.func( 4545 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4546 ) 4547 4548 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4549 if self.CAN_IMPLEMENT_ARRAY_ANY: 4550 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4551 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4552 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4553 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4554 4555 from sqlglot.dialects import Dialect 4556 4557 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4558 if self.dialect.__class__ != Dialect: 4559 self.unsupported("ARRAY_ANY is unsupported") 4560 4561 return self.function_fallback_sql(expression) 4562 4563 def struct_sql(self, expression: exp.Struct) -> str: 4564 expression.set( 4565 "expressions", 4566 [ 4567 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4568 if isinstance(e, exp.PropertyEQ) 4569 else e 4570 for e in expression.expressions 4571 ], 4572 ) 4573 4574 return self.function_fallback_sql(expression) 4575 4576 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4577 low = self.sql(expression, "this") 4578 high = self.sql(expression, "expression") 4579 4580 return f"{low} TO {high}" 4581 4582 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4583 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4584 tables = f" {self.expressions(expression)}" 4585 4586 exists = " IF EXISTS" if expression.args.get("exists") else "" 4587 4588 on_cluster = self.sql(expression, "cluster") 4589 on_cluster = f" {on_cluster}" if on_cluster else "" 4590 4591 identity = self.sql(expression, "identity") 4592 identity = f" {identity} IDENTITY" if identity else "" 4593 4594 option = self.sql(expression, "option") 4595 option = f" {option}" if option else "" 4596 4597 partition = self.sql(expression, "partition") 4598 partition = f" {partition}" if partition else "" 4599 4600 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4601 4602 # This transpiles T-SQL's CONVERT function 4603 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4604 def convert_sql(self, expression: exp.Convert) -> str: 4605 to = expression.this 4606 value = expression.expression 4607 style = expression.args.get("style") 4608 safe = expression.args.get("safe") 4609 strict = expression.args.get("strict") 4610 4611 if not to or not value: 4612 return "" 4613 4614 # Retrieve length of datatype and override to default if not specified 4615 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4616 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4617 4618 transformed: t.Optional[exp.Expression] = None 4619 cast = exp.Cast if strict else exp.TryCast 4620 4621 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4622 if isinstance(style, exp.Literal) and style.is_int: 4623 from sqlglot.dialects.tsql import TSQL 4624 4625 style_value = style.name 4626 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4627 if not converted_style: 4628 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4629 4630 fmt = exp.Literal.string(converted_style) 4631 4632 if to.this == exp.DataType.Type.DATE: 4633 transformed = exp.StrToDate(this=value, format=fmt) 4634 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4635 transformed = exp.StrToTime(this=value, format=fmt) 4636 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4637 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4638 elif to.this == exp.DataType.Type.TEXT: 4639 transformed = exp.TimeToStr(this=value, format=fmt) 4640 4641 if not transformed: 4642 transformed = cast(this=value, to=to, safe=safe) 4643 4644 return self.sql(transformed) 4645 4646 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4647 this = expression.this 4648 if isinstance(this, exp.JSONPathWildcard): 4649 this = self.json_path_part(this) 4650 return f".{this}" if this else "" 4651 4652 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4653 return f".{this}" 4654 4655 this = self.json_path_part(this) 4656 return ( 4657 f"[{this}]" 4658 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4659 else f".{this}" 4660 ) 4661 4662 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4663 this = self.json_path_part(expression.this) 4664 return f"[{this}]" if this else "" 4665 4666 def _simplify_unless_literal(self, expression: E) -> E: 4667 if not isinstance(expression, exp.Literal): 4668 from sqlglot.optimizer.simplify import simplify 4669 4670 expression = simplify(expression, dialect=self.dialect) 4671 4672 return expression 4673 4674 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4675 this = expression.this 4676 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4677 self.unsupported( 4678 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4679 ) 4680 return self.sql(this) 4681 4682 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4683 # The first modifier here will be the one closest to the AggFunc's arg 4684 mods = sorted( 4685 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4686 key=lambda x: 0 4687 if isinstance(x, exp.HavingMax) 4688 else (1 if isinstance(x, exp.Order) else 2), 4689 ) 4690 4691 if mods: 4692 mod = mods[0] 4693 this = expression.__class__(this=mod.this.copy()) 4694 this.meta["inline"] = True 4695 mod.this.replace(this) 4696 return self.sql(expression.this) 4697 4698 agg_func = expression.find(exp.AggFunc) 4699 4700 if agg_func: 4701 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4702 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4703 4704 return f"{self.sql(expression, 'this')} {text}" 4705 4706 def _replace_line_breaks(self, string: str) -> str: 4707 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4708 if self.pretty: 4709 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4710 return string 4711 4712 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4713 option = self.sql(expression, "this") 4714 4715 if expression.expressions: 4716 upper = option.upper() 4717 4718 # Snowflake FILE_FORMAT options are separated by whitespace 4719 sep = " " if upper == "FILE_FORMAT" else ", " 4720 4721 # Databricks copy/format options do not set their list of values with EQ 4722 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4723 values = self.expressions(expression, flat=True, sep=sep) 4724 return f"{option}{op}({values})" 4725 4726 value = self.sql(expression, "expression") 4727 4728 if not value: 4729 return option 4730 4731 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4732 4733 return f"{option}{op}{value}" 4734 4735 def credentials_sql(self, expression: exp.Credentials) -> str: 4736 cred_expr = expression.args.get("credentials") 4737 if isinstance(cred_expr, exp.Literal): 4738 # Redshift case: CREDENTIALS <string> 4739 credentials = self.sql(expression, "credentials") 4740 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4741 else: 4742 # Snowflake case: CREDENTIALS = (...) 4743 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4744 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4745 4746 storage = self.sql(expression, "storage") 4747 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4748 4749 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4750 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4751 4752 iam_role = self.sql(expression, "iam_role") 4753 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4754 4755 region = self.sql(expression, "region") 4756 region = f" REGION {region}" if region else "" 4757 4758 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4759 4760 def copy_sql(self, expression: exp.Copy) -> str: 4761 this = self.sql(expression, "this") 4762 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4763 4764 credentials = self.sql(expression, "credentials") 4765 credentials = self.seg(credentials) if credentials else "" 4766 files = self.expressions(expression, key="files", flat=True) 4767 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4768 4769 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4770 params = self.expressions( 4771 expression, 4772 key="params", 4773 sep=sep, 4774 new_line=True, 4775 skip_last=True, 4776 skip_first=True, 4777 indent=self.COPY_PARAMS_ARE_WRAPPED, 4778 ) 4779 4780 if params: 4781 if self.COPY_PARAMS_ARE_WRAPPED: 4782 params = f" WITH ({params})" 4783 elif not self.pretty and (files or credentials): 4784 params = f" {params}" 4785 4786 return f"COPY{this}{kind} {files}{credentials}{params}" 4787 4788 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4789 return "" 4790 4791 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4792 on_sql = "ON" if expression.args.get("on") else "OFF" 4793 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4794 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4795 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4796 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4797 4798 if filter_col or retention_period: 4799 on_sql = self.func("ON", filter_col, retention_period) 4800 4801 return f"DATA_DELETION={on_sql}" 4802 4803 def maskingpolicycolumnconstraint_sql( 4804 self, expression: exp.MaskingPolicyColumnConstraint 4805 ) -> str: 4806 this = self.sql(expression, "this") 4807 expressions = self.expressions(expression, flat=True) 4808 expressions = f" USING ({expressions})" if expressions else "" 4809 return f"MASKING POLICY {this}{expressions}" 4810 4811 def gapfill_sql(self, expression: exp.GapFill) -> str: 4812 this = self.sql(expression, "this") 4813 this = f"TABLE {this}" 4814 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4815 4816 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4817 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4818 4819 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4820 this = self.sql(expression, "this") 4821 expr = expression.expression 4822 4823 if isinstance(expr, exp.Func): 4824 # T-SQL's CLR functions are case sensitive 4825 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4826 else: 4827 expr = self.sql(expression, "expression") 4828 4829 return self.scope_resolution(expr, this) 4830 4831 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4832 if self.PARSE_JSON_NAME is None: 4833 return self.sql(expression.this) 4834 4835 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4836 4837 def rand_sql(self, expression: exp.Rand) -> str: 4838 lower = self.sql(expression, "lower") 4839 upper = self.sql(expression, "upper") 4840 4841 if lower and upper: 4842 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4843 return self.func("RAND", expression.this) 4844 4845 def changes_sql(self, expression: exp.Changes) -> str: 4846 information = self.sql(expression, "information") 4847 information = f"INFORMATION => {information}" 4848 at_before = self.sql(expression, "at_before") 4849 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4850 end = self.sql(expression, "end") 4851 end = f"{self.seg('')}{end}" if end else "" 4852 4853 return f"CHANGES ({information}){at_before}{end}" 4854 4855 def pad_sql(self, expression: exp.Pad) -> str: 4856 prefix = "L" if expression.args.get("is_left") else "R" 4857 4858 fill_pattern = self.sql(expression, "fill_pattern") or None 4859 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4860 fill_pattern = "' '" 4861 4862 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4863 4864 def summarize_sql(self, expression: exp.Summarize) -> str: 4865 table = " TABLE" if expression.args.get("table") else "" 4866 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4867 4868 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4869 generate_series = exp.GenerateSeries(**expression.args) 4870 4871 parent = expression.parent 4872 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4873 parent = parent.parent 4874 4875 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4876 return self.sql(exp.Unnest(expressions=[generate_series])) 4877 4878 if isinstance(parent, exp.Select): 4879 self.unsupported("GenerateSeries projection unnesting is not supported.") 4880 4881 return self.sql(generate_series) 4882 4883 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4884 exprs = expression.expressions 4885 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4886 if len(exprs) == 0: 4887 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4888 else: 4889 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4890 else: 4891 rhs = self.expressions(expression) # type: ignore 4892 4893 return self.func(name, expression.this, rhs or None) 4894 4895 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4896 if self.SUPPORTS_CONVERT_TIMEZONE: 4897 return self.function_fallback_sql(expression) 4898 4899 source_tz = expression.args.get("source_tz") 4900 target_tz = expression.args.get("target_tz") 4901 timestamp = expression.args.get("timestamp") 4902 4903 if source_tz and timestamp: 4904 timestamp = exp.AtTimeZone( 4905 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4906 ) 4907 4908 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4909 4910 return self.sql(expr) 4911 4912 def json_sql(self, expression: exp.JSON) -> str: 4913 this = self.sql(expression, "this") 4914 this = f" {this}" if this else "" 4915 4916 _with = expression.args.get("with_") 4917 4918 if _with is None: 4919 with_sql = "" 4920 elif not _with: 4921 with_sql = " WITHOUT" 4922 else: 4923 with_sql = " WITH" 4924 4925 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4926 4927 return f"JSON{this}{with_sql}{unique_sql}" 4928 4929 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4930 def _generate_on_options(arg: t.Any) -> str: 4931 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4932 4933 path = self.sql(expression, "path") 4934 returning = self.sql(expression, "returning") 4935 returning = f" RETURNING {returning}" if returning else "" 4936 4937 on_condition = self.sql(expression, "on_condition") 4938 on_condition = f" {on_condition}" if on_condition else "" 4939 4940 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4941 4942 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4943 else_ = "ELSE " if expression.args.get("else_") else "" 4944 condition = self.sql(expression, "expression") 4945 condition = f"WHEN {condition} THEN " if condition else else_ 4946 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4947 return f"{condition}{insert}" 4948 4949 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4950 kind = self.sql(expression, "kind") 4951 expressions = self.seg(self.expressions(expression, sep=" ")) 4952 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4953 return res 4954 4955 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4956 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4957 empty = expression.args.get("empty") 4958 empty = ( 4959 f"DEFAULT {empty} ON EMPTY" 4960 if isinstance(empty, exp.Expression) 4961 else self.sql(expression, "empty") 4962 ) 4963 4964 error = expression.args.get("error") 4965 error = ( 4966 f"DEFAULT {error} ON ERROR" 4967 if isinstance(error, exp.Expression) 4968 else self.sql(expression, "error") 4969 ) 4970 4971 if error and empty: 4972 error = ( 4973 f"{empty} {error}" 4974 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4975 else f"{error} {empty}" 4976 ) 4977 empty = "" 4978 4979 null = self.sql(expression, "null") 4980 4981 return f"{empty}{error}{null}" 4982 4983 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4984 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4985 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4986 4987 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4988 this = self.sql(expression, "this") 4989 path = self.sql(expression, "path") 4990 4991 passing = self.expressions(expression, "passing") 4992 passing = f" PASSING {passing}" if passing else "" 4993 4994 on_condition = self.sql(expression, "on_condition") 4995 on_condition = f" {on_condition}" if on_condition else "" 4996 4997 path = f"{path}{passing}{on_condition}" 4998 4999 return self.func("JSON_EXISTS", this, path) 5000 5001 def _add_arrayagg_null_filter( 5002 self, 5003 array_agg_sql: str, 5004 array_agg_expr: exp.ArrayAgg, 5005 column_expr: exp.Expression, 5006 ) -> str: 5007 """ 5008 Add NULL filter to ARRAY_AGG if dialect requires it. 5009 5010 Args: 5011 array_agg_sql: The generated ARRAY_AGG SQL string 5012 array_agg_expr: The ArrayAgg expression node 5013 column_expr: The column/expression to filter (before ORDER BY wrapping) 5014 5015 Returns: 5016 SQL string with FILTER clause added if needed 5017 """ 5018 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 5019 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 5020 if not ( 5021 self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get("nulls_excluded") 5022 ): 5023 return array_agg_sql 5024 5025 parent = array_agg_expr.parent 5026 if isinstance(parent, exp.Filter): 5027 parent_cond = parent.expression.this 5028 parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_())) 5029 elif column_expr.find(exp.Column): 5030 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 5031 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 5032 this_sql = ( 5033 self.expressions(column_expr) 5034 if isinstance(column_expr, exp.Distinct) 5035 else self.sql(column_expr) 5036 ) 5037 array_agg_sql = f"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)" 5038 5039 return array_agg_sql 5040 5041 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 5042 array_agg = self.function_fallback_sql(expression) 5043 return self._add_arrayagg_null_filter(array_agg, expression, expression.this) 5044 5045 def slice_sql(self, expression: exp.Slice) -> str: 5046 step = self.sql(expression, "step") 5047 end = self.sql(expression.expression) 5048 begin = self.sql(expression.this) 5049 5050 sql = f"{end}:{step}" if step else end 5051 return f"{begin}:{sql}" if sql else f"{begin}:" 5052 5053 def apply_sql(self, expression: exp.Apply) -> str: 5054 this = self.sql(expression, "this") 5055 expr = self.sql(expression, "expression") 5056 5057 return f"{this} APPLY({expr})" 5058 5059 def _grant_or_revoke_sql( 5060 self, 5061 expression: exp.Grant | exp.Revoke, 5062 keyword: str, 5063 preposition: str, 5064 grant_option_prefix: str = "", 5065 grant_option_suffix: str = "", 5066 ) -> str: 5067 privileges_sql = self.expressions(expression, key="privileges", flat=True) 5068 5069 kind = self.sql(expression, "kind") 5070 kind = f" {kind}" if kind else "" 5071 5072 securable = self.sql(expression, "securable") 5073 securable = f" {securable}" if securable else "" 5074 5075 principals = self.expressions(expression, key="principals", flat=True) 5076 5077 if not expression.args.get("grant_option"): 5078 grant_option_prefix = grant_option_suffix = "" 5079 5080 # cascade for revoke only 5081 cascade = self.sql(expression, "cascade") 5082 cascade = f" {cascade}" if cascade else "" 5083 5084 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 5085 5086 def grant_sql(self, expression: exp.Grant) -> str: 5087 return self._grant_or_revoke_sql( 5088 expression, 5089 keyword="GRANT", 5090 preposition="TO", 5091 grant_option_suffix=" WITH GRANT OPTION", 5092 ) 5093 5094 def revoke_sql(self, expression: exp.Revoke) -> str: 5095 return self._grant_or_revoke_sql( 5096 expression, 5097 keyword="REVOKE", 5098 preposition="FROM", 5099 grant_option_prefix="GRANT OPTION FOR ", 5100 ) 5101 5102 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 5103 this = self.sql(expression, "this") 5104 columns = self.expressions(expression, flat=True) 5105 columns = f"({columns})" if columns else "" 5106 5107 return f"{this}{columns}" 5108 5109 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 5110 this = self.sql(expression, "this") 5111 5112 kind = self.sql(expression, "kind") 5113 kind = f"{kind} " if kind else "" 5114 5115 return f"{kind}{this}" 5116 5117 def columns_sql(self, expression: exp.Columns): 5118 func = self.function_fallback_sql(expression) 5119 if expression.args.get("unpack"): 5120 func = f"*{func}" 5121 5122 return func 5123 5124 def overlay_sql(self, expression: exp.Overlay): 5125 this = self.sql(expression, "this") 5126 expr = self.sql(expression, "expression") 5127 from_sql = self.sql(expression, "from_") 5128 for_sql = self.sql(expression, "for_") 5129 for_sql = f" FOR {for_sql}" if for_sql else "" 5130 5131 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 5132 5133 @unsupported_args("format") 5134 def todouble_sql(self, expression: exp.ToDouble) -> str: 5135 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 5136 5137 def string_sql(self, expression: exp.String) -> str: 5138 this = expression.this 5139 zone = expression.args.get("zone") 5140 5141 if zone: 5142 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5143 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5144 # set for source_tz to transpile the time conversion before the STRING cast 5145 this = exp.ConvertTimezone( 5146 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5147 ) 5148 5149 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 5150 5151 def median_sql(self, expression: exp.Median): 5152 if not self.SUPPORTS_MEDIAN: 5153 return self.sql( 5154 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 5155 ) 5156 5157 return self.function_fallback_sql(expression) 5158 5159 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5160 filler = self.sql(expression, "this") 5161 filler = f" {filler}" if filler else "" 5162 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5163 return f"TRUNCATE{filler} {with_count}" 5164 5165 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5166 if self.SUPPORTS_UNIX_SECONDS: 5167 return self.function_fallback_sql(expression) 5168 5169 start_ts = exp.cast( 5170 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5171 ) 5172 5173 return self.sql( 5174 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5175 ) 5176 5177 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5178 dim = expression.expression 5179 5180 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5181 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5182 if not (dim.is_int and dim.name == "1"): 5183 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5184 dim = None 5185 5186 # If dimension is required but not specified, default initialize it 5187 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5188 dim = exp.Literal.number(1) 5189 5190 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 5191 5192 def attach_sql(self, expression: exp.Attach) -> str: 5193 this = self.sql(expression, "this") 5194 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5195 expressions = self.expressions(expression) 5196 expressions = f" ({expressions})" if expressions else "" 5197 5198 return f"ATTACH{exists_sql} {this}{expressions}" 5199 5200 def detach_sql(self, expression: exp.Detach) -> str: 5201 this = self.sql(expression, "this") 5202 # the DATABASE keyword is required if IF EXISTS is set 5203 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5204 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5205 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5206 5207 return f"DETACH{exists_sql} {this}" 5208 5209 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5210 this = self.sql(expression, "this") 5211 value = self.sql(expression, "expression") 5212 value = f" {value}" if value else "" 5213 return f"{this}{value}" 5214 5215 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5216 return ( 5217 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5218 ) 5219 5220 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5221 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5222 encode = f"{encode} {self.sql(expression, 'this')}" 5223 5224 properties = expression.args.get("properties") 5225 if properties: 5226 encode = f"{encode} {self.properties(properties)}" 5227 5228 return encode 5229 5230 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5231 this = self.sql(expression, "this") 5232 include = f"INCLUDE {this}" 5233 5234 column_def = self.sql(expression, "column_def") 5235 if column_def: 5236 include = f"{include} {column_def}" 5237 5238 alias = self.sql(expression, "alias") 5239 if alias: 5240 include = f"{include} AS {alias}" 5241 5242 return include 5243 5244 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5245 prefix = "EVALNAME" if expression.args.get("evalname") else "NAME" 5246 name = f"{prefix} {self.sql(expression, 'this')}" 5247 return self.func("XMLELEMENT", name, *expression.expressions) 5248 5249 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5250 this = self.sql(expression, "this") 5251 expr = self.sql(expression, "expression") 5252 expr = f"({expr})" if expr else "" 5253 return f"{this}{expr}" 5254 5255 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5256 partitions = self.expressions(expression, "partition_expressions") 5257 create = self.expressions(expression, "create_expressions") 5258 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5259 5260 def partitionbyrangepropertydynamic_sql( 5261 self, expression: exp.PartitionByRangePropertyDynamic 5262 ) -> str: 5263 start = self.sql(expression, "start") 5264 end = self.sql(expression, "end") 5265 5266 every = expression.args["every"] 5267 if isinstance(every, exp.Interval) and every.this.is_string: 5268 every.this.replace(exp.Literal.number(every.name)) 5269 5270 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5271 5272 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5273 name = self.sql(expression, "this") 5274 values = self.expressions(expression, flat=True) 5275 5276 return f"NAME {name} VALUE {values}" 5277 5278 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5279 kind = self.sql(expression, "kind") 5280 sample = self.sql(expression, "sample") 5281 return f"SAMPLE {sample} {kind}" 5282 5283 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5284 kind = self.sql(expression, "kind") 5285 option = self.sql(expression, "option") 5286 option = f" {option}" if option else "" 5287 this = self.sql(expression, "this") 5288 this = f" {this}" if this else "" 5289 columns = self.expressions(expression) 5290 columns = f" {columns}" if columns else "" 5291 return f"{kind}{option} STATISTICS{this}{columns}" 5292 5293 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5294 this = self.sql(expression, "this") 5295 columns = self.expressions(expression) 5296 inner_expression = self.sql(expression, "expression") 5297 inner_expression = f" {inner_expression}" if inner_expression else "" 5298 update_options = self.sql(expression, "update_options") 5299 update_options = f" {update_options} UPDATE" if update_options else "" 5300 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5301 5302 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5303 kind = self.sql(expression, "kind") 5304 kind = f" {kind}" if kind else "" 5305 return f"DELETE{kind} STATISTICS" 5306 5307 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5308 inner_expression = self.sql(expression, "expression") 5309 return f"LIST CHAINED ROWS{inner_expression}" 5310 5311 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5312 kind = self.sql(expression, "kind") 5313 this = self.sql(expression, "this") 5314 this = f" {this}" if this else "" 5315 inner_expression = self.sql(expression, "expression") 5316 return f"VALIDATE {kind}{this}{inner_expression}" 5317 5318 def analyze_sql(self, expression: exp.Analyze) -> str: 5319 options = self.expressions(expression, key="options", sep=" ") 5320 options = f" {options}" if options else "" 5321 kind = self.sql(expression, "kind") 5322 kind = f" {kind}" if kind else "" 5323 this = self.sql(expression, "this") 5324 this = f" {this}" if this else "" 5325 mode = self.sql(expression, "mode") 5326 mode = f" {mode}" if mode else "" 5327 properties = self.sql(expression, "properties") 5328 properties = f" {properties}" if properties else "" 5329 partition = self.sql(expression, "partition") 5330 partition = f" {partition}" if partition else "" 5331 inner_expression = self.sql(expression, "expression") 5332 inner_expression = f" {inner_expression}" if inner_expression else "" 5333 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5334 5335 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5336 this = self.sql(expression, "this") 5337 namespaces = self.expressions(expression, key="namespaces") 5338 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5339 passing = self.expressions(expression, key="passing") 5340 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5341 columns = self.expressions(expression, key="columns") 5342 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5343 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5344 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5345 5346 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5347 this = self.sql(expression, "this") 5348 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5349 5350 def export_sql(self, expression: exp.Export) -> str: 5351 this = self.sql(expression, "this") 5352 connection = self.sql(expression, "connection") 5353 connection = f"WITH CONNECTION {connection} " if connection else "" 5354 options = self.sql(expression, "options") 5355 return f"EXPORT DATA {connection}{options} AS {this}" 5356 5357 def declare_sql(self, expression: exp.Declare) -> str: 5358 return f"DECLARE {self.expressions(expression, flat=True)}" 5359 5360 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5361 variable = self.sql(expression, "this") 5362 default = self.sql(expression, "default") 5363 default = f" = {default}" if default else "" 5364 5365 kind = self.sql(expression, "kind") 5366 if isinstance(expression.args.get("kind"), exp.Schema): 5367 kind = f"TABLE {kind}" 5368 5369 return f"{variable} AS {kind}{default}" 5370 5371 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5372 kind = self.sql(expression, "kind") 5373 this = self.sql(expression, "this") 5374 set = self.sql(expression, "expression") 5375 using = self.sql(expression, "using") 5376 using = f" USING {using}" if using else "" 5377 5378 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5379 5380 return f"{kind_sql} {this} SET {set}{using}" 5381 5382 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5383 params = self.expressions(expression, key="params", flat=True) 5384 return self.func(expression.name, *expression.expressions) + f"({params})" 5385 5386 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5387 return self.func(expression.name, *expression.expressions) 5388 5389 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5390 return self.anonymousaggfunc_sql(expression) 5391 5392 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5393 return self.parameterizedagg_sql(expression) 5394 5395 def show_sql(self, expression: exp.Show) -> str: 5396 self.unsupported("Unsupported SHOW statement") 5397 return "" 5398 5399 def install_sql(self, expression: exp.Install) -> str: 5400 self.unsupported("Unsupported INSTALL statement") 5401 return "" 5402 5403 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5404 # Snowflake GET/PUT statements: 5405 # PUT <file> <internalStage> <properties> 5406 # GET <internalStage> <file> <properties> 5407 props = expression.args.get("properties") 5408 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5409 this = self.sql(expression, "this") 5410 target = self.sql(expression, "target") 5411 5412 if isinstance(expression, exp.Put): 5413 return f"PUT {this} {target}{props_sql}" 5414 else: 5415 return f"GET {target} {this}{props_sql}" 5416 5417 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5418 this = self.sql(expression, "this") 5419 expr = self.sql(expression, "expression") 5420 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5421 return f"TRANSLATE({this} USING {expr}{with_error})" 5422 5423 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5424 if self.SUPPORTS_DECODE_CASE: 5425 return self.func("DECODE", *expression.expressions) 5426 5427 expression, *expressions = expression.expressions 5428 5429 ifs = [] 5430 for search, result in zip(expressions[::2], expressions[1::2]): 5431 if isinstance(search, exp.Literal): 5432 ifs.append(exp.If(this=expression.eq(search), true=result)) 5433 elif isinstance(search, exp.Null): 5434 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5435 else: 5436 if isinstance(search, exp.Binary): 5437 search = exp.paren(search) 5438 5439 cond = exp.or_( 5440 expression.eq(search), 5441 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5442 copy=False, 5443 ) 5444 ifs.append(exp.If(this=cond, true=result)) 5445 5446 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5447 return self.sql(case) 5448 5449 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5450 this = self.sql(expression, "this") 5451 this = self.seg(this, sep="") 5452 dimensions = self.expressions( 5453 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5454 ) 5455 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5456 metrics = self.expressions( 5457 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5458 ) 5459 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5460 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5461 facts = self.seg(f"FACTS {facts}") if facts else "" 5462 where = self.sql(expression, "where") 5463 where = self.seg(f"WHERE {where}") if where else "" 5464 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5465 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5466 5467 def getextract_sql(self, expression: exp.GetExtract) -> str: 5468 this = expression.this 5469 expr = expression.expression 5470 5471 if not this.type or not expression.type: 5472 from sqlglot.optimizer.annotate_types import annotate_types 5473 5474 this = annotate_types(this, dialect=self.dialect) 5475 5476 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5477 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5478 5479 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5480 5481 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5482 return self.sql( 5483 exp.DateAdd( 5484 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5485 expression=expression.this, 5486 unit=exp.var("DAY"), 5487 ) 5488 ) 5489 5490 def space_sql(self: Generator, expression: exp.Space) -> str: 5491 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5492 5493 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5494 return f"BUILD {self.sql(expression, 'this')}" 5495 5496 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5497 method = self.sql(expression, "method") 5498 kind = expression.args.get("kind") 5499 if not kind: 5500 return f"REFRESH {method}" 5501 5502 every = self.sql(expression, "every") 5503 unit = self.sql(expression, "unit") 5504 every = f" EVERY {every} {unit}" if every else "" 5505 starts = self.sql(expression, "starts") 5506 starts = f" STARTS {starts}" if starts else "" 5507 5508 return f"REFRESH {method} ON {kind}{every}{starts}" 5509 5510 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5511 self.unsupported("The model!attribute syntax is not supported") 5512 return "" 5513 5514 def directorystage_sql(self, expression: exp.DirectoryStage) -> str: 5515 return self.func("DIRECTORY", expression.this) 5516 5517 def uuid_sql(self, expression: exp.Uuid) -> str: 5518 is_string = expression.args.get("is_string", False) 5519 uuid_func_sql = self.func("UUID") 5520 5521 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5522 return self.sql( 5523 exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect) 5524 ) 5525 5526 return uuid_func_sql 5527 5528 def initcap_sql(self, expression: exp.Initcap) -> str: 5529 delimiters = expression.expression 5530 5531 if delimiters: 5532 # do not generate delimiters arg if we are round-tripping from default delimiters 5533 if ( 5534 delimiters.is_string 5535 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5536 ): 5537 delimiters = None 5538 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5539 self.unsupported("INITCAP does not support custom delimiters") 5540 delimiters = None 5541 5542 return self.func("INITCAP", expression.this, delimiters) 5543 5544 def localtime_sql(self, expression: exp.Localtime) -> str: 5545 this = expression.this 5546 return self.func("LOCALTIME", this) if this else "LOCALTIME" 5547 5548 def localtimestamp_sql(self, expression: exp.Localtime) -> str: 5549 this = expression.this 5550 return self.func("LOCALTIMESTAMP", this) if this else "LOCALTIMESTAMP" 5551 5552 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5553 this = expression.this.name.upper() 5554 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5555 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5556 return "WEEK" 5557 5558 return self.func("WEEK", expression.this) 5559 5560 def chr_sql(self, expression: exp.Chr, name: str = "CHR") -> str: 5561 this = self.expressions(expression) 5562 charset = self.sql(expression, "charset") 5563 using = f" USING {charset}" if charset else "" 5564 return self.func(name, this + using)
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: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = 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)
750 def __init__( 751 self, 752 pretty: t.Optional[bool] = None, 753 identify: str | bool = False, 754 normalize: bool = False, 755 pad: int = 2, 756 indent: int = 2, 757 normalize_functions: t.Optional[str | bool] = None, 758 unsupported_level: ErrorLevel = ErrorLevel.WARN, 759 max_unsupported: int = 3, 760 leading_comma: bool = False, 761 max_text_width: int = 80, 762 comments: bool = True, 763 dialect: DialectType = None, 764 ): 765 import sqlglot 766 from sqlglot.dialects import Dialect 767 768 self.pretty = pretty if pretty is not None else sqlglot.pretty 769 self.identify = identify 770 self.normalize = normalize 771 self.pad = pad 772 self._indent = indent 773 self.unsupported_level = unsupported_level 774 self.max_unsupported = max_unsupported 775 self.leading_comma = leading_comma 776 self.max_text_width = max_text_width 777 self.comments = comments 778 self.dialect = Dialect.get_or_raise(dialect) 779 780 # This is both a Dialect property and a Generator argument, so we prioritize the latter 781 self.normalize_functions = ( 782 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 783 ) 784 785 self.unsupported_messages: t.List[str] = [] 786 self._escaped_quote_end: str = ( 787 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 788 ) 789 self._escaped_byte_quote_end: str = ( 790 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END 791 if self.dialect.BYTE_END 792 else "" 793 ) 794 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 795 796 self._next_name = name_sequence("_t") 797 798 self._identifier_start = self.dialect.IDENTIFIER_START 799 self._identifier_end = self.dialect.IDENTIFIER_END 800 801 self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] =
{<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.Adjacent'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CurrentCatalog'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SessionUser'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExtendsLeft'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExtendsRight'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ZeroFillColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcDate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcTime'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UtcTimestamp'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.JSONPathFilter'>, <class 'sqlglot.expressions.JSONPathUnion'>, <class 'sqlglot.expressions.JSONPathSubscript'>, <class 'sqlglot.expressions.JSONPathSelector'>, <class 'sqlglot.expressions.JSONPathSlice'>, <class 'sqlglot.expressions.JSONPathScript'>, <class 'sqlglot.expressions.JSONPathRoot'>, <class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathKey'>}
TYPE_MAPPING =
{<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Describe'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Order'>, <class 'sqlglot.expressions.Group'>, <class 'sqlglot.expressions.Having'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NCHAR: 'NCHAR'>, <Type.VARCHAR: 'VARCHAR'>}
803 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 804 """ 805 Generates the SQL string corresponding to the given syntax tree. 806 807 Args: 808 expression: The syntax tree. 809 copy: Whether to copy the expression. The generator performs mutations so 810 it is safer to copy. 811 812 Returns: 813 The SQL string corresponding to `expression`. 814 """ 815 if copy: 816 expression = expression.copy() 817 818 expression = self.preprocess(expression) 819 820 self.unsupported_messages = [] 821 sql = self.sql(expression).strip() 822 823 if self.pretty: 824 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 825 826 if self.unsupported_level == ErrorLevel.IGNORE: 827 return sql 828 829 if self.unsupported_level == ErrorLevel.WARN: 830 for msg in self.unsupported_messages: 831 logger.warning(msg) 832 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 833 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 834 835 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.
def
preprocess( self, expression: sqlglot.expressions.Expression) -> sqlglot.expressions.Expression:
837 def preprocess(self, expression: exp.Expression) -> exp.Expression: 838 """Apply generic preprocessing transformations to a given expression.""" 839 expression = self._move_ctes_to_top_level(expression) 840 841 if self.ENSURE_BOOLS: 842 from sqlglot.transforms import ensure_bools 843 844 expression = ensure_bools(expression) 845 846 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
870 def sanitize_comment(self, comment: str) -> str: 871 comment = " " + comment if comment[0].strip() else comment 872 comment = comment + " " if comment[-1].strip() else comment 873 874 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 875 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 876 comment = comment.replace("*/", "* /") 877 878 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
880 def maybe_comment( 881 self, 882 sql: str, 883 expression: t.Optional[exp.Expression] = None, 884 comments: t.Optional[t.List[str]] = None, 885 separated: bool = False, 886 ) -> str: 887 comments = ( 888 ((expression and expression.comments) if comments is None else comments) # type: ignore 889 if self.comments 890 else None 891 ) 892 893 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 894 return sql 895 896 comments_sql = " ".join( 897 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 898 ) 899 900 if not comments_sql: 901 return sql 902 903 comments_sql = self._replace_line_breaks(comments_sql) 904 905 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 906 return ( 907 f"{self.sep()}{comments_sql}{sql}" 908 if not sql or sql[0].isspace() 909 else f"{comments_sql}{self.sep()}{sql}" 910 ) 911 912 return f"{sql} {comments_sql}"
914 def wrap(self, expression: exp.Expression | str) -> str: 915 this_sql = ( 916 self.sql(expression) 917 if isinstance(expression, exp.UNWRAPPED_QUERIES) 918 else self.sql(expression, "this") 919 ) 920 if not this_sql: 921 return "()" 922 923 this_sql = self.indent(this_sql, level=1, pad=0) 924 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def
indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
940 def indent( 941 self, 942 sql: str, 943 level: int = 0, 944 pad: t.Optional[int] = None, 945 skip_first: bool = False, 946 skip_last: bool = False, 947 ) -> str: 948 if not self.pretty or not sql: 949 return sql 950 951 pad = self.pad if pad is None else pad 952 lines = sql.split("\n") 953 954 return "\n".join( 955 ( 956 line 957 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 958 else f"{' ' * (level * self._indent + pad)}{line}" 959 ) 960 for i, line in enumerate(lines) 961 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
963 def sql( 964 self, 965 expression: t.Optional[str | exp.Expression], 966 key: t.Optional[str] = None, 967 comment: bool = True, 968 ) -> str: 969 if not expression: 970 return "" 971 972 if isinstance(expression, str): 973 return expression 974 975 if key: 976 value = expression.args.get(key) 977 if value: 978 return self.sql(value) 979 return "" 980 981 transform = self.TRANSFORMS.get(expression.__class__) 982 983 if callable(transform): 984 sql = transform(self, expression) 985 elif isinstance(expression, exp.Expression): 986 exp_handler_name = f"{expression.key}_sql" 987 988 if hasattr(self, exp_handler_name): 989 sql = getattr(self, exp_handler_name)(expression) 990 elif isinstance(expression, exp.Func): 991 sql = self.function_fallback_sql(expression) 992 elif isinstance(expression, exp.Property): 993 sql = self.property_sql(expression) 994 else: 995 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 996 else: 997 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 998 999 return self.maybe_comment(sql, expression) if self.comments and comment else sql
1006 def cache_sql(self, expression: exp.Cache) -> str: 1007 lazy = " LAZY" if expression.args.get("lazy") else "" 1008 table = self.sql(expression, "this") 1009 options = expression.args.get("options") 1010 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 1011 sql = self.sql(expression, "expression") 1012 sql = f" AS{self.sep()}{sql}" if sql else "" 1013 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 1014 return self.prepend_ctes(expression, sql)
1016 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1017 if isinstance(expression.parent, exp.Cast): 1018 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1019 default = "DEFAULT " if expression.args.get("default") else "" 1020 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1034 def column_sql(self, expression: exp.Column) -> str: 1035 join_mark = " (+)" if expression.args.get("join_mark") else "" 1036 1037 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1038 join_mark = "" 1039 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1040 1041 return f"{self.column_parts(expression)}{join_mark}"
1052 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1053 column = self.sql(expression, "this") 1054 kind = self.sql(expression, "kind") 1055 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1056 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1057 kind = f"{sep}{kind}" if kind else "" 1058 constraints = f" {constraints}" if constraints else "" 1059 position = self.sql(expression, "position") 1060 position = f" {position}" if position else "" 1061 1062 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1063 kind = "" 1064 1065 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1072 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1073 this = self.sql(expression, "this") 1074 if expression.args.get("not_null"): 1075 persisted = " PERSISTED NOT NULL" 1076 elif expression.args.get("persisted"): 1077 persisted = " PERSISTED" 1078 else: 1079 persisted = "" 1080 1081 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1094 def generatedasidentitycolumnconstraint_sql( 1095 self, expression: exp.GeneratedAsIdentityColumnConstraint 1096 ) -> str: 1097 this = "" 1098 if expression.this is not None: 1099 on_null = " ON NULL" if expression.args.get("on_null") else "" 1100 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1101 1102 start = expression.args.get("start") 1103 start = f"START WITH {start}" if start else "" 1104 increment = expression.args.get("increment") 1105 increment = f" INCREMENT BY {increment}" if increment else "" 1106 minvalue = expression.args.get("minvalue") 1107 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1108 maxvalue = expression.args.get("maxvalue") 1109 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1110 cycle = expression.args.get("cycle") 1111 cycle_sql = "" 1112 1113 if cycle is not None: 1114 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1115 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1116 1117 sequence_opts = "" 1118 if start or increment or cycle_sql: 1119 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1120 sequence_opts = f" ({sequence_opts.strip()})" 1121 1122 expr = self.sql(expression, "expression") 1123 expr = f"({expr})" if expr else "IDENTITY" 1124 1125 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1127 def generatedasrowcolumnconstraint_sql( 1128 self, expression: exp.GeneratedAsRowColumnConstraint 1129 ) -> str: 1130 start = "START" if expression.args.get("start") else "END" 1131 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1132 return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def
periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.PeriodForSystemTimeConstraint) -> str:
def
notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
def
primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
1142 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1143 desc = expression.args.get("desc") 1144 if desc is not None: 1145 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1146 options = self.expressions(expression, key="options", flat=True, sep=" ") 1147 options = f" {options}" if options else "" 1148 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1150 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1151 this = self.sql(expression, "this") 1152 this = f" {this}" if this else "" 1153 index_type = expression.args.get("index_type") 1154 index_type = f" USING {index_type}" if index_type else "" 1155 on_conflict = self.sql(expression, "on_conflict") 1156 on_conflict = f" {on_conflict}" if on_conflict else "" 1157 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1158 options = self.expressions(expression, key="options", flat=True, sep=" ") 1159 options = f" {options}" if options else "" 1160 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1162 def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str: 1163 input_ = expression.args.get("input_") 1164 output = expression.args.get("output") 1165 1166 if input_ and output: 1167 return "IN OUT" 1168 if input_: 1169 return "IN" 1170 if output: 1171 return "OUT" 1172 1173 return ""
1178 def create_sql(self, expression: exp.Create) -> str: 1179 kind = self.sql(expression, "kind") 1180 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1181 properties = expression.args.get("properties") 1182 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1183 1184 this = self.createable_sql(expression, properties_locs) 1185 1186 properties_sql = "" 1187 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1188 exp.Properties.Location.POST_WITH 1189 ): 1190 props_ast = exp.Properties( 1191 expressions=[ 1192 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1193 *properties_locs[exp.Properties.Location.POST_WITH], 1194 ] 1195 ) 1196 props_ast.parent = expression 1197 properties_sql = self.sql(props_ast) 1198 1199 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1200 properties_sql = self.sep() + properties_sql 1201 elif not self.pretty: 1202 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1203 properties_sql = f" {properties_sql}" 1204 1205 begin = " BEGIN" if expression.args.get("begin") else "" 1206 end = " END" if expression.args.get("end") else "" 1207 1208 expression_sql = self.sql(expression, "expression") 1209 if expression_sql: 1210 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1211 1212 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1213 postalias_props_sql = "" 1214 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1215 postalias_props_sql = self.properties( 1216 exp.Properties( 1217 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1218 ), 1219 wrapped=False, 1220 ) 1221 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1222 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1223 1224 postindex_props_sql = "" 1225 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1226 postindex_props_sql = self.properties( 1227 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1228 wrapped=False, 1229 prefix=" ", 1230 ) 1231 1232 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1233 indexes = f" {indexes}" if indexes else "" 1234 index_sql = indexes + postindex_props_sql 1235 1236 replace = " OR REPLACE" if expression.args.get("replace") else "" 1237 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1238 unique = " UNIQUE" if expression.args.get("unique") else "" 1239 1240 clustered = expression.args.get("clustered") 1241 if clustered is None: 1242 clustered_sql = "" 1243 elif clustered: 1244 clustered_sql = " CLUSTERED COLUMNSTORE" 1245 else: 1246 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1247 1248 postcreate_props_sql = "" 1249 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1250 postcreate_props_sql = self.properties( 1251 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1252 sep=" ", 1253 prefix=" ", 1254 wrapped=False, 1255 ) 1256 1257 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1258 1259 postexpression_props_sql = "" 1260 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1261 postexpression_props_sql = self.properties( 1262 exp.Properties( 1263 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1264 ), 1265 sep=" ", 1266 prefix=" ", 1267 wrapped=False, 1268 ) 1269 1270 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1271 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1272 no_schema_binding = ( 1273 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1274 ) 1275 1276 clone = self.sql(expression, "clone") 1277 clone = f" {clone}" if clone else "" 1278 1279 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1280 properties_expression = f"{expression_sql}{properties_sql}" 1281 else: 1282 properties_expression = f"{properties_sql}{expression_sql}" 1283 1284 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1285 return self.prepend_ctes(expression, expression_sql)
1287 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1288 start = self.sql(expression, "start") 1289 start = f"START WITH {start}" if start else "" 1290 increment = self.sql(expression, "increment") 1291 increment = f" INCREMENT BY {increment}" if increment else "" 1292 minvalue = self.sql(expression, "minvalue") 1293 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1294 maxvalue = self.sql(expression, "maxvalue") 1295 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1296 owned = self.sql(expression, "owned") 1297 owned = f" OWNED BY {owned}" if owned else "" 1298 1299 cache = expression.args.get("cache") 1300 if cache is None: 1301 cache_str = "" 1302 elif cache is True: 1303 cache_str = " CACHE" 1304 else: 1305 cache_str = f" CACHE {cache}" 1306 1307 options = self.expressions(expression, key="options", flat=True, sep=" ") 1308 options = f" {options}" if options else "" 1309 1310 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1312 def clone_sql(self, expression: exp.Clone) -> str: 1313 this = self.sql(expression, "this") 1314 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1315 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1316 return f"{shallow}{keyword} {this}"
1318 def describe_sql(self, expression: exp.Describe) -> str: 1319 style = expression.args.get("style") 1320 style = f" {style}" if style else "" 1321 partition = self.sql(expression, "partition") 1322 partition = f" {partition}" if partition else "" 1323 format = self.sql(expression, "format") 1324 format = f" {format}" if format else "" 1325 1326 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1338 def with_sql(self, expression: exp.With) -> str: 1339 sql = self.expressions(expression, flat=True) 1340 recursive = ( 1341 "RECURSIVE " 1342 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1343 else "" 1344 ) 1345 search = self.sql(expression, "search") 1346 search = f" {search}" if search else "" 1347 1348 return f"WITH {recursive}{sql}{search}"
1350 def cte_sql(self, expression: exp.CTE) -> str: 1351 alias = expression.args.get("alias") 1352 if alias: 1353 alias.add_comments(expression.pop_comments()) 1354 1355 alias_sql = self.sql(expression, "alias") 1356 1357 materialized = expression.args.get("materialized") 1358 if materialized is False: 1359 materialized = "NOT MATERIALIZED " 1360 elif materialized: 1361 materialized = "MATERIALIZED " 1362 1363 key_expressions = self.expressions(expression, key="key_expressions", flat=True) 1364 key_expressions = f" USING KEY ({key_expressions})" if key_expressions else "" 1365 1366 return f"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}"
1368 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1369 alias = self.sql(expression, "this") 1370 columns = self.expressions(expression, key="columns", flat=True) 1371 columns = f"({columns})" if columns else "" 1372 1373 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1374 columns = "" 1375 self.unsupported("Named columns are not supported in table alias.") 1376 1377 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1378 alias = self._next_name() 1379 1380 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1388 def hexstring_sql( 1389 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1390 ) -> str: 1391 this = self.sql(expression, "this") 1392 is_integer_type = expression.args.get("is_integer") 1393 1394 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1395 not self.dialect.HEX_START and not binary_function_repr 1396 ): 1397 # Integer representation will be returned if: 1398 # - The read dialect treats the hex value as integer literal but not the write 1399 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1400 return f"{int(this, 16)}" 1401 1402 if not is_integer_type: 1403 # Read dialect treats the hex value as BINARY/BLOB 1404 if binary_function_repr: 1405 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1406 return self.func(binary_function_repr, exp.Literal.string(this)) 1407 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1408 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1409 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1410 1411 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1413 def bytestring_sql(self, expression: exp.ByteString) -> str: 1414 this = self.sql(expression, "this") 1415 if self.dialect.BYTE_START: 1416 escaped_byte_string = self.escape_str( 1417 this, 1418 escape_backslash=False, 1419 delimiter=self.dialect.BYTE_END, 1420 escaped_delimiter=self._escaped_byte_quote_end, 1421 ) 1422 is_bytes = expression.args.get("is_bytes", False) 1423 delimited_byte_string = ( 1424 f"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}" 1425 ) 1426 if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1427 return self.sql( 1428 exp.cast(delimited_byte_string, exp.DataType.Type.BINARY, dialect=self.dialect) 1429 ) 1430 if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE: 1431 return self.sql( 1432 exp.cast(delimited_byte_string, exp.DataType.Type.VARCHAR, dialect=self.dialect) 1433 ) 1434 1435 return delimited_byte_string 1436 return this
1438 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1439 this = self.sql(expression, "this") 1440 escape = expression.args.get("escape") 1441 1442 if self.dialect.UNICODE_START: 1443 escape_substitute = r"\\\1" 1444 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1445 else: 1446 escape_substitute = r"\\u\1" 1447 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1448 1449 if escape: 1450 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1451 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1452 else: 1453 escape_pattern = ESCAPED_UNICODE_RE 1454 escape_sql = "" 1455 1456 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1457 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1458 1459 return f"{left_quote}{this}{right_quote}{escape_sql}"
1461 def rawstring_sql(self, expression: exp.RawString) -> str: 1462 string = expression.this 1463 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1464 string = string.replace("\\", "\\\\") 1465 1466 string = self.escape_str(string, escape_backslash=False) 1467 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1475 def datatype_sql(self, expression: exp.DataType) -> str: 1476 nested = "" 1477 values = "" 1478 1479 expr_nested = expression.args.get("nested") 1480 interior = ( 1481 self.expressions( 1482 expression, dynamic=True, new_line=True, skip_first=True, skip_last=True 1483 ) 1484 if expr_nested and self.pretty 1485 else self.expressions(expression, flat=True) 1486 ) 1487 1488 type_value = expression.this 1489 if type_value in self.UNSUPPORTED_TYPES: 1490 self.unsupported( 1491 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1492 ) 1493 1494 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1495 type_sql = self.sql(expression, "kind") 1496 else: 1497 type_sql = ( 1498 self.TYPE_MAPPING.get(type_value, type_value.value) 1499 if isinstance(type_value, exp.DataType.Type) 1500 else type_value 1501 ) 1502 1503 if interior: 1504 if expr_nested: 1505 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1506 if expression.args.get("values") is not None: 1507 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1508 values = self.expressions(expression, key="values", flat=True) 1509 values = f"{delimiters[0]}{values}{delimiters[1]}" 1510 elif type_value == exp.DataType.Type.INTERVAL: 1511 nested = f" {interior}" 1512 else: 1513 nested = f"({interior})" 1514 1515 type_sql = f"{type_sql}{nested}{values}" 1516 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1517 exp.DataType.Type.TIMETZ, 1518 exp.DataType.Type.TIMESTAMPTZ, 1519 ): 1520 type_sql = f"{type_sql} WITH TIME ZONE" 1521 1522 return type_sql
1524 def directory_sql(self, expression: exp.Directory) -> str: 1525 local = "LOCAL " if expression.args.get("local") else "" 1526 row_format = self.sql(expression, "row_format") 1527 row_format = f" {row_format}" if row_format else "" 1528 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1530 def delete_sql(self, expression: exp.Delete) -> str: 1531 this = self.sql(expression, "this") 1532 this = f" FROM {this}" if this else "" 1533 using = self.expressions(expression, key="using") 1534 using = f" USING {using}" if using else "" 1535 cluster = self.sql(expression, "cluster") 1536 cluster = f" {cluster}" if cluster else "" 1537 where = self.sql(expression, "where") 1538 returning = self.sql(expression, "returning") 1539 order = self.sql(expression, "order") 1540 limit = self.sql(expression, "limit") 1541 tables = self.expressions(expression, key="tables") 1542 tables = f" {tables}" if tables else "" 1543 if self.RETURNING_END: 1544 expression_sql = f"{this}{using}{cluster}{where}{returning}{order}{limit}" 1545 else: 1546 expression_sql = f"{returning}{this}{using}{cluster}{where}{order}{limit}" 1547 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1549 def drop_sql(self, expression: exp.Drop) -> str: 1550 this = self.sql(expression, "this") 1551 expressions = self.expressions(expression, flat=True) 1552 expressions = f" ({expressions})" if expressions else "" 1553 kind = expression.args["kind"] 1554 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1555 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1556 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1557 on_cluster = self.sql(expression, "cluster") 1558 on_cluster = f" {on_cluster}" if on_cluster else "" 1559 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1560 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1561 cascade = " CASCADE" if expression.args.get("cascade") else "" 1562 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1563 purge = " PURGE" if expression.args.get("purge") else "" 1564 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1566 def set_operation(self, expression: exp.SetOperation) -> str: 1567 op_type = type(expression) 1568 op_name = op_type.key.upper() 1569 1570 distinct = expression.args.get("distinct") 1571 if ( 1572 distinct is False 1573 and op_type in (exp.Except, exp.Intersect) 1574 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1575 ): 1576 self.unsupported(f"{op_name} ALL is not supported") 1577 1578 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1579 1580 if distinct is None: 1581 distinct = default_distinct 1582 if distinct is None: 1583 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1584 1585 if distinct is default_distinct: 1586 distinct_or_all = "" 1587 else: 1588 distinct_or_all = " DISTINCT" if distinct else " ALL" 1589 1590 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1591 side_kind = f"{side_kind} " if side_kind else "" 1592 1593 by_name = " BY NAME" if expression.args.get("by_name") else "" 1594 on = self.expressions(expression, key="on", flat=True) 1595 on = f" ON ({on})" if on else "" 1596 1597 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1599 def set_operations(self, expression: exp.SetOperation) -> str: 1600 if not self.SET_OP_MODIFIERS: 1601 limit = expression.args.get("limit") 1602 order = expression.args.get("order") 1603 1604 if limit or order: 1605 select = self._move_ctes_to_top_level( 1606 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1607 ) 1608 1609 if limit: 1610 select = select.limit(limit.pop(), copy=False) 1611 if order: 1612 select = select.order_by(order.pop(), copy=False) 1613 return self.sql(select) 1614 1615 sqls: t.List[str] = [] 1616 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1617 1618 while stack: 1619 node = stack.pop() 1620 1621 if isinstance(node, exp.SetOperation): 1622 stack.append(node.expression) 1623 stack.append( 1624 self.maybe_comment( 1625 self.set_operation(node), comments=node.comments, separated=True 1626 ) 1627 ) 1628 stack.append(node.this) 1629 else: 1630 sqls.append(self.sql(node)) 1631 1632 this = self.sep().join(sqls) 1633 this = self.query_modifiers(expression, this) 1634 return self.prepend_ctes(expression, this)
1636 def fetch_sql(self, expression: exp.Fetch) -> str: 1637 direction = expression.args.get("direction") 1638 direction = f" {direction}" if direction else "" 1639 count = self.sql(expression, "count") 1640 count = f" {count}" if count else "" 1641 limit_options = self.sql(expression, "limit_options") 1642 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1643 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1645 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1646 percent = " PERCENT" if expression.args.get("percent") else "" 1647 rows = " ROWS" if expression.args.get("rows") else "" 1648 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1649 if not with_ties and rows: 1650 with_ties = " ONLY" 1651 return f"{percent}{rows}{with_ties}"
1653 def filter_sql(self, expression: exp.Filter) -> str: 1654 if self.AGGREGATE_FILTER_SUPPORTED: 1655 this = self.sql(expression, "this") 1656 where = self.sql(expression, "expression").strip() 1657 return f"{this} FILTER({where})" 1658 1659 agg = expression.this 1660 agg_arg = agg.this 1661 cond = expression.expression.this 1662 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1663 return self.sql(agg)
1672 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1673 using = self.sql(expression, "using") 1674 using = f" USING {using}" if using else "" 1675 columns = self.expressions(expression, key="columns", flat=True) 1676 columns = f"({columns})" if columns else "" 1677 partition_by = self.expressions(expression, key="partition_by", flat=True) 1678 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1679 where = self.sql(expression, "where") 1680 include = self.expressions(expression, key="include", flat=True) 1681 if include: 1682 include = f" INCLUDE ({include})" 1683 with_storage = self.expressions(expression, key="with_storage", flat=True) 1684 with_storage = f" WITH ({with_storage})" if with_storage else "" 1685 tablespace = self.sql(expression, "tablespace") 1686 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1687 on = self.sql(expression, "on") 1688 on = f" ON {on}" if on else "" 1689 1690 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1692 def index_sql(self, expression: exp.Index) -> str: 1693 unique = "UNIQUE " if expression.args.get("unique") else "" 1694 primary = "PRIMARY " if expression.args.get("primary") else "" 1695 amp = "AMP " if expression.args.get("amp") else "" 1696 name = self.sql(expression, "this") 1697 name = f"{name} " if name else "" 1698 table = self.sql(expression, "table") 1699 table = f"{self.INDEX_ON} {table}" if table else "" 1700 1701 index = "INDEX " if not table else "" 1702 1703 params = self.sql(expression, "params") 1704 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1706 def identifier_sql(self, expression: exp.Identifier) -> str: 1707 text = expression.name 1708 lower = text.lower() 1709 text = lower if self.normalize and not expression.quoted else text 1710 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1711 if ( 1712 expression.quoted 1713 or self.dialect.can_quote(expression, self.identify) 1714 or lower in self.RESERVED_KEYWORDS 1715 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1716 ): 1717 text = f"{self._identifier_start}{text}{self._identifier_end}" 1718 return text
1733 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1734 input_format = self.sql(expression, "input_format") 1735 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1736 output_format = self.sql(expression, "output_format") 1737 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1738 return self.sep().join((input_format, output_format))
1748 def properties_sql(self, expression: exp.Properties) -> str: 1749 root_properties = [] 1750 with_properties = [] 1751 1752 for p in expression.expressions: 1753 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1754 if p_loc == exp.Properties.Location.POST_WITH: 1755 with_properties.append(p) 1756 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1757 root_properties.append(p) 1758 1759 root_props_ast = exp.Properties(expressions=root_properties) 1760 root_props_ast.parent = expression.parent 1761 1762 with_props_ast = exp.Properties(expressions=with_properties) 1763 with_props_ast.parent = expression.parent 1764 1765 root_props = self.root_properties(root_props_ast) 1766 with_props = self.with_properties(with_props_ast) 1767 1768 if root_props and with_props and not self.pretty: 1769 with_props = " " + with_props 1770 1771 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1778 def properties( 1779 self, 1780 properties: exp.Properties, 1781 prefix: str = "", 1782 sep: str = ", ", 1783 suffix: str = "", 1784 wrapped: bool = True, 1785 ) -> str: 1786 if properties.expressions: 1787 expressions = self.expressions(properties, sep=sep, indent=False) 1788 if expressions: 1789 expressions = self.wrap(expressions) if wrapped else expressions 1790 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1791 return ""
1796 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1797 properties_locs = defaultdict(list) 1798 for p in properties.expressions: 1799 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1800 if p_loc != exp.Properties.Location.UNSUPPORTED: 1801 properties_locs[p_loc].append(p) 1802 else: 1803 self.unsupported(f"Unsupported property {p.key}") 1804 1805 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1812 def property_sql(self, expression: exp.Property) -> str: 1813 property_cls = expression.__class__ 1814 if property_cls == exp.Property: 1815 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1816 1817 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1818 if not property_name: 1819 self.unsupported(f"Unsupported property {expression.key}") 1820 1821 return f"{property_name}={self.sql(expression, 'this')}"
1823 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1824 if self.SUPPORTS_CREATE_TABLE_LIKE: 1825 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1826 options = f" {options}" if options else "" 1827 1828 like = f"LIKE {self.sql(expression, 'this')}{options}" 1829 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1830 like = f"({like})" 1831 1832 return like 1833 1834 if expression.expressions: 1835 self.unsupported("Transpilation of LIKE property options is unsupported") 1836 1837 select = exp.select("*").from_(expression.this).limit(0) 1838 return f"AS {self.sql(select)}"
1845 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1846 no = "NO " if expression.args.get("no") else "" 1847 local = expression.args.get("local") 1848 local = f"{local} " if local else "" 1849 dual = "DUAL " if expression.args.get("dual") else "" 1850 before = "BEFORE " if expression.args.get("before") else "" 1851 after = "AFTER " if expression.args.get("after") else "" 1852 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1868 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1869 if expression.args.get("no"): 1870 return "NO MERGEBLOCKRATIO" 1871 if expression.args.get("default"): 1872 return "DEFAULT MERGEBLOCKRATIO" 1873 1874 percent = " PERCENT" if expression.args.get("percent") else "" 1875 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1877 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1878 default = expression.args.get("default") 1879 minimum = expression.args.get("minimum") 1880 maximum = expression.args.get("maximum") 1881 if default or minimum or maximum: 1882 if default: 1883 prop = "DEFAULT" 1884 elif minimum: 1885 prop = "MINIMUM" 1886 else: 1887 prop = "MAXIMUM" 1888 return f"{prop} DATABLOCKSIZE" 1889 units = expression.args.get("units") 1890 units = f" {units}" if units else "" 1891 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1893 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1894 autotemp = expression.args.get("autotemp") 1895 always = expression.args.get("always") 1896 default = expression.args.get("default") 1897 manual = expression.args.get("manual") 1898 never = expression.args.get("never") 1899 1900 if autotemp is not None: 1901 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1902 elif always: 1903 prop = "ALWAYS" 1904 elif default: 1905 prop = "DEFAULT" 1906 elif manual: 1907 prop = "MANUAL" 1908 elif never: 1909 prop = "NEVER" 1910 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1912 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1913 no = expression.args.get("no") 1914 no = " NO" if no else "" 1915 concurrent = expression.args.get("concurrent") 1916 concurrent = " CONCURRENT" if concurrent else "" 1917 target = self.sql(expression, "target") 1918 target = f" {target}" if target else "" 1919 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1921 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1922 if isinstance(expression.this, list): 1923 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1924 if expression.this: 1925 modulus = self.sql(expression, "this") 1926 remainder = self.sql(expression, "expression") 1927 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1928 1929 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1930 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1931 return f"FROM ({from_expressions}) TO ({to_expressions})"
1933 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1934 this = self.sql(expression, "this") 1935 1936 for_values_or_default = expression.expression 1937 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1938 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1939 else: 1940 for_values_or_default = " DEFAULT" 1941 1942 return f"PARTITION OF {this}{for_values_or_default}"
1944 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1945 kind = expression.args.get("kind") 1946 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1947 for_or_in = expression.args.get("for_or_in") 1948 for_or_in = f" {for_or_in}" if for_or_in else "" 1949 lock_type = expression.args.get("lock_type") 1950 override = " OVERRIDE" if expression.args.get("override") else "" 1951 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1953 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1954 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1955 statistics = expression.args.get("statistics") 1956 statistics_sql = "" 1957 if statistics is not None: 1958 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1959 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1961 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1962 this = self.sql(expression, "this") 1963 this = f"HISTORY_TABLE={this}" if this else "" 1964 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1965 data_consistency = ( 1966 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1967 ) 1968 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1969 retention_period = ( 1970 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1971 ) 1972 1973 if this: 1974 on_sql = self.func("ON", this, data_consistency, retention_period) 1975 else: 1976 on_sql = "ON" if expression.args.get("on") else "OFF" 1977 1978 sql = f"SYSTEM_VERSIONING={on_sql}" 1979 1980 return f"WITH({sql})" if expression.args.get("with_") else sql
1982 def insert_sql(self, expression: exp.Insert) -> str: 1983 hint = self.sql(expression, "hint") 1984 overwrite = expression.args.get("overwrite") 1985 1986 if isinstance(expression.this, exp.Directory): 1987 this = " OVERWRITE" if overwrite else " INTO" 1988 else: 1989 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1990 1991 stored = self.sql(expression, "stored") 1992 stored = f" {stored}" if stored else "" 1993 alternative = expression.args.get("alternative") 1994 alternative = f" OR {alternative}" if alternative else "" 1995 ignore = " IGNORE" if expression.args.get("ignore") else "" 1996 is_function = expression.args.get("is_function") 1997 if is_function: 1998 this = f"{this} FUNCTION" 1999 this = f"{this} {self.sql(expression, 'this')}" 2000 2001 exists = " IF EXISTS" if expression.args.get("exists") else "" 2002 where = self.sql(expression, "where") 2003 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 2004 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 2005 on_conflict = self.sql(expression, "conflict") 2006 on_conflict = f" {on_conflict}" if on_conflict else "" 2007 by_name = " BY NAME" if expression.args.get("by_name") else "" 2008 default_values = "DEFAULT VALUES" if expression.args.get("default") else "" 2009 returning = self.sql(expression, "returning") 2010 2011 if self.RETURNING_END: 2012 expression_sql = f"{expression_sql}{on_conflict}{default_values}{returning}" 2013 else: 2014 expression_sql = f"{returning}{expression_sql}{on_conflict}" 2015 2016 partition_by = self.sql(expression, "partition") 2017 partition_by = f" {partition_by}" if partition_by else "" 2018 settings = self.sql(expression, "settings") 2019 settings = f" {settings}" if settings else "" 2020 2021 source = self.sql(expression, "source") 2022 source = f"TABLE {source}" if source else "" 2023 2024 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 2025 return self.prepend_ctes(expression, sql)
2043 def onconflict_sql(self, expression: exp.OnConflict) -> str: 2044 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 2045 2046 constraint = self.sql(expression, "constraint") 2047 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 2048 2049 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 2050 if conflict_keys: 2051 conflict_keys = f"({conflict_keys})" 2052 2053 index_predicate = self.sql(expression, "index_predicate") 2054 conflict_keys = f"{conflict_keys}{index_predicate} " 2055 2056 action = self.sql(expression, "action") 2057 2058 expressions = self.expressions(expression, flat=True) 2059 if expressions: 2060 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 2061 expressions = f" {set_keyword}{expressions}" 2062 2063 where = self.sql(expression, "where") 2064 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
2069 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2070 fields = self.sql(expression, "fields") 2071 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2072 escaped = self.sql(expression, "escaped") 2073 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2074 items = self.sql(expression, "collection_items") 2075 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2076 keys = self.sql(expression, "map_keys") 2077 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2078 lines = self.sql(expression, "lines") 2079 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2080 null = self.sql(expression, "null") 2081 null = f" NULL DEFINED AS {null}" if null else "" 2082 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2110 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2111 table = self.table_parts(expression) 2112 only = "ONLY " if expression.args.get("only") else "" 2113 partition = self.sql(expression, "partition") 2114 partition = f" {partition}" if partition else "" 2115 version = self.sql(expression, "version") 2116 version = f" {version}" if version else "" 2117 alias = self.sql(expression, "alias") 2118 alias = f"{sep}{alias}" if alias else "" 2119 2120 sample = self.sql(expression, "sample") 2121 if self.dialect.ALIAS_POST_TABLESAMPLE: 2122 sample_pre_alias = sample 2123 sample_post_alias = "" 2124 else: 2125 sample_pre_alias = "" 2126 sample_post_alias = sample 2127 2128 hints = self.expressions(expression, key="hints", sep=" ") 2129 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2130 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2131 joins = self.indent( 2132 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2133 ) 2134 laterals = self.expressions(expression, key="laterals", sep="") 2135 2136 file_format = self.sql(expression, "format") 2137 if file_format: 2138 pattern = self.sql(expression, "pattern") 2139 pattern = f", PATTERN => {pattern}" if pattern else "" 2140 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2141 2142 ordinality = expression.args.get("ordinality") or "" 2143 if ordinality: 2144 ordinality = f" WITH ORDINALITY{alias}" 2145 alias = "" 2146 2147 when = self.sql(expression, "when") 2148 if when: 2149 table = f"{table} {when}" 2150 2151 changes = self.sql(expression, "changes") 2152 changes = f" {changes}" if changes else "" 2153 2154 rows_from = self.expressions(expression, key="rows_from") 2155 if rows_from: 2156 table = f"ROWS FROM {self.wrap(rows_from)}" 2157 2158 indexed = expression.args.get("indexed") 2159 if indexed is not None: 2160 indexed = f" INDEXED BY {self.sql(indexed)}" if indexed else " NOT INDEXED" 2161 else: 2162 indexed = "" 2163 2164 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{indexed}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2166 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2167 table = self.func("TABLE", expression.this) 2168 alias = self.sql(expression, "alias") 2169 alias = f" AS {alias}" if alias else "" 2170 sample = self.sql(expression, "sample") 2171 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2172 joins = self.indent( 2173 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2174 ) 2175 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2177 def tablesample_sql( 2178 self, 2179 expression: exp.TableSample, 2180 tablesample_keyword: t.Optional[str] = None, 2181 ) -> str: 2182 method = self.sql(expression, "method") 2183 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2184 numerator = self.sql(expression, "bucket_numerator") 2185 denominator = self.sql(expression, "bucket_denominator") 2186 field = self.sql(expression, "bucket_field") 2187 field = f" ON {field}" if field else "" 2188 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2189 seed = self.sql(expression, "seed") 2190 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2191 2192 size = self.sql(expression, "size") 2193 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2194 size = f"{size} ROWS" 2195 2196 percent = self.sql(expression, "percent") 2197 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2198 percent = f"{percent} PERCENT" 2199 2200 expr = f"{bucket}{percent}{size}" 2201 if self.TABLESAMPLE_REQUIRES_PARENS: 2202 expr = f"({expr})" 2203 2204 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2206 def pivot_sql(self, expression: exp.Pivot) -> str: 2207 expressions = self.expressions(expression, flat=True) 2208 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2209 2210 group = self.sql(expression, "group") 2211 2212 if expression.this: 2213 this = self.sql(expression, "this") 2214 if not expressions: 2215 sql = f"UNPIVOT {this}" 2216 else: 2217 on = f"{self.seg('ON')} {expressions}" 2218 into = self.sql(expression, "into") 2219 into = f"{self.seg('INTO')} {into}" if into else "" 2220 using = self.expressions(expression, key="using", flat=True) 2221 using = f"{self.seg('USING')} {using}" if using else "" 2222 sql = f"{direction} {this}{on}{into}{using}{group}" 2223 return self.prepend_ctes(expression, sql) 2224 2225 alias = self.sql(expression, "alias") 2226 alias = f" AS {alias}" if alias else "" 2227 2228 fields = self.expressions( 2229 expression, 2230 "fields", 2231 sep=" ", 2232 dynamic=True, 2233 new_line=True, 2234 skip_first=True, 2235 skip_last=True, 2236 ) 2237 2238 include_nulls = expression.args.get("include_nulls") 2239 if include_nulls is not None: 2240 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2241 else: 2242 nulls = "" 2243 2244 default_on_null = self.sql(expression, "default_on_null") 2245 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2246 sql = f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2247 return self.prepend_ctes(expression, sql)
2290 def update_sql(self, expression: exp.Update) -> str: 2291 this = self.sql(expression, "this") 2292 join_sql, from_sql = self._update_from_joins_sql(expression) 2293 set_sql = self.expressions(expression, flat=True) 2294 where_sql = self.sql(expression, "where") 2295 returning = self.sql(expression, "returning") 2296 order = self.sql(expression, "order") 2297 limit = self.sql(expression, "limit") 2298 if self.RETURNING_END: 2299 expression_sql = f"{from_sql}{where_sql}{returning}" 2300 else: 2301 expression_sql = f"{returning}{from_sql}{where_sql}" 2302 options = self.expressions(expression, key="options") 2303 options = f" OPTION({options})" if options else "" 2304 sql = f"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}" 2305 return self.prepend_ctes(expression, sql)
2307 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2308 values_as_table = values_as_table and self.VALUES_AS_TABLE 2309 2310 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2311 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2312 args = self.expressions(expression) 2313 alias = self.sql(expression, "alias") 2314 values = f"VALUES{self.seg('')}{args}" 2315 values = ( 2316 f"({values})" 2317 if self.WRAP_DERIVED_VALUES 2318 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2319 else values 2320 ) 2321 values = self.query_modifiers(expression, values) 2322 return f"{values} AS {alias}" if alias else values 2323 2324 # Converts `VALUES...` expression into a series of select unions. 2325 alias_node = expression.args.get("alias") 2326 column_names = alias_node and alias_node.columns 2327 2328 selects: t.List[exp.Query] = [] 2329 2330 for i, tup in enumerate(expression.expressions): 2331 row = tup.expressions 2332 2333 if i == 0 and column_names: 2334 row = [ 2335 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2336 ] 2337 2338 selects.append(exp.Select(expressions=row)) 2339 2340 if self.pretty: 2341 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2342 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2343 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2344 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2345 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2346 2347 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2348 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2349 return f"({unions}){alias}"
2354 @unsupported_args("expressions") 2355 def into_sql(self, expression: exp.Into) -> str: 2356 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2357 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2358 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2375 def group_sql(self, expression: exp.Group) -> str: 2376 group_by_all = expression.args.get("all") 2377 if group_by_all is True: 2378 modifier = " ALL" 2379 elif group_by_all is False: 2380 modifier = " DISTINCT" 2381 else: 2382 modifier = "" 2383 2384 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2385 2386 grouping_sets = self.expressions(expression, key="grouping_sets") 2387 cube = self.expressions(expression, key="cube") 2388 rollup = self.expressions(expression, key="rollup") 2389 2390 groupings = csv( 2391 self.seg(grouping_sets) if grouping_sets else "", 2392 self.seg(cube) if cube else "", 2393 self.seg(rollup) if rollup else "", 2394 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2395 sep=self.GROUPINGS_SEP, 2396 ) 2397 2398 if ( 2399 expression.expressions 2400 and groupings 2401 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2402 ): 2403 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2404 2405 return f"{group_by}{groupings}"
2411 def connect_sql(self, expression: exp.Connect) -> str: 2412 start = self.sql(expression, "start") 2413 start = self.seg(f"START WITH {start}") if start else "" 2414 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2415 connect = self.sql(expression, "connect") 2416 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2417 return start + connect
2422 def join_sql(self, expression: exp.Join) -> str: 2423 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2424 side = None 2425 else: 2426 side = expression.side 2427 2428 op_sql = " ".join( 2429 op 2430 for op in ( 2431 expression.method, 2432 "GLOBAL" if expression.args.get("global_") else None, 2433 side, 2434 expression.kind, 2435 expression.hint if self.JOIN_HINTS else None, 2436 ) 2437 if op 2438 ) 2439 match_cond = self.sql(expression, "match_condition") 2440 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2441 on_sql = self.sql(expression, "on") 2442 using = expression.args.get("using") 2443 2444 if not on_sql and using: 2445 on_sql = csv(*(self.sql(column) for column in using)) 2446 2447 this = expression.this 2448 this_sql = self.sql(this) 2449 2450 exprs = self.expressions(expression) 2451 if exprs: 2452 this_sql = f"{this_sql},{self.seg(exprs)}" 2453 2454 if on_sql: 2455 on_sql = self.indent(on_sql, skip_first=True) 2456 space = self.seg(" " * self.pad) if self.pretty else " " 2457 if using: 2458 on_sql = f"{space}USING ({on_sql})" 2459 else: 2460 on_sql = f"{space}ON {on_sql}" 2461 elif not op_sql: 2462 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2463 return f" {this_sql}" 2464 2465 return f", {this_sql}" 2466 2467 if op_sql != "STRAIGHT_JOIN": 2468 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2469 2470 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2471 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
def
lambda_sql( self, expression: sqlglot.expressions.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
2478 def lateral_op(self, expression: exp.Lateral) -> str: 2479 cross_apply = expression.args.get("cross_apply") 2480 2481 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2482 if cross_apply is True: 2483 op = "INNER JOIN " 2484 elif cross_apply is False: 2485 op = "LEFT JOIN " 2486 else: 2487 op = "" 2488 2489 return f"{op}LATERAL"
2491 def lateral_sql(self, expression: exp.Lateral) -> str: 2492 this = self.sql(expression, "this") 2493 2494 if expression.args.get("view"): 2495 alias = expression.args["alias"] 2496 columns = self.expressions(alias, key="columns", flat=True) 2497 table = f" {alias.name}" if alias.name else "" 2498 columns = f" AS {columns}" if columns else "" 2499 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2500 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2501 2502 alias = self.sql(expression, "alias") 2503 alias = f" AS {alias}" if alias else "" 2504 2505 ordinality = expression.args.get("ordinality") or "" 2506 if ordinality: 2507 ordinality = f" WITH ORDINALITY{alias}" 2508 alias = "" 2509 2510 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2512 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2513 this = self.sql(expression, "this") 2514 2515 args = [ 2516 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2517 for e in (expression.args.get(k) for k in ("offset", "expression")) 2518 if e 2519 ] 2520 2521 args_sql = ", ".join(self.sql(e) for e in args) 2522 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2523 expressions = self.expressions(expression, flat=True) 2524 limit_options = self.sql(expression, "limit_options") 2525 expressions = f" BY {expressions}" if expressions else "" 2526 2527 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2529 def offset_sql(self, expression: exp.Offset) -> str: 2530 this = self.sql(expression, "this") 2531 value = expression.expression 2532 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2533 expressions = self.expressions(expression, flat=True) 2534 expressions = f" BY {expressions}" if expressions else "" 2535 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2537 def setitem_sql(self, expression: exp.SetItem) -> str: 2538 kind = self.sql(expression, "kind") 2539 if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == "VARIABLE": 2540 kind = "" 2541 else: 2542 kind = f"{kind} " if kind else "" 2543 this = self.sql(expression, "this") 2544 expressions = self.expressions(expression) 2545 collate = self.sql(expression, "collate") 2546 collate = f" COLLATE {collate}" if collate else "" 2547 global_ = "GLOBAL " if expression.args.get("global_") else "" 2548 return f"{global_}{kind}{this}{expressions}{collate}"
2555 def queryband_sql(self, expression: exp.QueryBand) -> str: 2556 this = self.sql(expression, "this") 2557 update = " UPDATE" if expression.args.get("update") else "" 2558 scope = self.sql(expression, "scope") 2559 scope = f" FOR {scope}" if scope else "" 2560 2561 return f"QUERY_BAND = {this}{update}{scope}"
2566 def lock_sql(self, expression: exp.Lock) -> str: 2567 if not self.LOCKING_READS_SUPPORTED: 2568 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2569 return "" 2570 2571 update = expression.args["update"] 2572 key = expression.args.get("key") 2573 if update: 2574 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2575 else: 2576 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2577 expressions = self.expressions(expression, flat=True) 2578 expressions = f" OF {expressions}" if expressions else "" 2579 wait = expression.args.get("wait") 2580 2581 if wait is not None: 2582 if isinstance(wait, exp.Literal): 2583 wait = f" WAIT {self.sql(wait)}" 2584 else: 2585 wait = " NOWAIT" if wait else " SKIP LOCKED" 2586 2587 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str( self, text: str, escape_backslash: bool = True, delimiter: Optional[str] = None, escaped_delimiter: Optional[str] = None) -> str:
2595 def escape_str( 2596 self, 2597 text: str, 2598 escape_backslash: bool = True, 2599 delimiter: t.Optional[str] = None, 2600 escaped_delimiter: t.Optional[str] = None, 2601 ) -> str: 2602 if self.dialect.ESCAPED_SEQUENCES: 2603 to_escaped = self.dialect.ESCAPED_SEQUENCES 2604 text = "".join( 2605 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2606 ) 2607 2608 delimiter = delimiter or self.dialect.QUOTE_END 2609 escaped_delimiter = escaped_delimiter or self._escaped_quote_end 2610 2611 return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)
2613 def loaddata_sql(self, expression: exp.LoadData) -> str: 2614 local = " LOCAL" if expression.args.get("local") else "" 2615 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2616 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2617 this = f" INTO TABLE {self.sql(expression, 'this')}" 2618 partition = self.sql(expression, "partition") 2619 partition = f" {partition}" if partition else "" 2620 input_format = self.sql(expression, "input_format") 2621 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2622 serde = self.sql(expression, "serde") 2623 serde = f" SERDE {serde}" if serde else "" 2624 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2638 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2639 this = self.sql(expression, "this") 2640 this = f"{this} " if this else this 2641 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2642 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2644 def withfill_sql(self, expression: exp.WithFill) -> str: 2645 from_sql = self.sql(expression, "from_") 2646 from_sql = f" FROM {from_sql}" if from_sql else "" 2647 to_sql = self.sql(expression, "to") 2648 to_sql = f" TO {to_sql}" if to_sql else "" 2649 step_sql = self.sql(expression, "step") 2650 step_sql = f" STEP {step_sql}" if step_sql else "" 2651 interpolated_values = [ 2652 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2653 if isinstance(e, exp.Alias) 2654 else self.sql(e, "this") 2655 for e in expression.args.get("interpolate") or [] 2656 ] 2657 interpolate = ( 2658 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2659 ) 2660 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2671 def ordered_sql(self, expression: exp.Ordered) -> str: 2672 desc = expression.args.get("desc") 2673 asc = not desc 2674 2675 nulls_first = expression.args.get("nulls_first") 2676 nulls_last = not nulls_first 2677 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2678 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2679 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2680 2681 this = self.sql(expression, "this") 2682 2683 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2684 nulls_sort_change = "" 2685 if nulls_first and ( 2686 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2687 ): 2688 nulls_sort_change = " NULLS FIRST" 2689 elif ( 2690 nulls_last 2691 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2692 and not nulls_are_last 2693 ): 2694 nulls_sort_change = " NULLS LAST" 2695 2696 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2697 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2698 window = expression.find_ancestor(exp.Window, exp.Select) 2699 if isinstance(window, exp.Window) and window.args.get("spec"): 2700 self.unsupported( 2701 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2702 ) 2703 nulls_sort_change = "" 2704 elif self.NULL_ORDERING_SUPPORTED is False and ( 2705 (asc and nulls_sort_change == " NULLS LAST") 2706 or (desc and nulls_sort_change == " NULLS FIRST") 2707 ): 2708 # BigQuery does not allow these ordering/nulls combinations when used under 2709 # an aggregation func or under a window containing one 2710 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2711 2712 if isinstance(ancestor, exp.Window): 2713 ancestor = ancestor.this 2714 if isinstance(ancestor, exp.AggFunc): 2715 self.unsupported( 2716 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2717 ) 2718 nulls_sort_change = "" 2719 elif self.NULL_ORDERING_SUPPORTED is None: 2720 if expression.this.is_int: 2721 self.unsupported( 2722 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2723 ) 2724 elif not isinstance(expression.this, exp.Rand): 2725 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2726 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2727 nulls_sort_change = "" 2728 2729 with_fill = self.sql(expression, "with_fill") 2730 with_fill = f" {with_fill}" if with_fill else "" 2731 2732 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2742 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2743 partition = self.partition_by_sql(expression) 2744 order = self.sql(expression, "order") 2745 measures = self.expressions(expression, key="measures") 2746 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2747 rows = self.sql(expression, "rows") 2748 rows = self.seg(rows) if rows else "" 2749 after = self.sql(expression, "after") 2750 after = self.seg(after) if after else "" 2751 pattern = self.sql(expression, "pattern") 2752 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2753 definition_sqls = [ 2754 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2755 for definition in expression.args.get("define", []) 2756 ] 2757 definitions = self.expressions(sqls=definition_sqls) 2758 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2759 body = "".join( 2760 ( 2761 partition, 2762 order, 2763 measures, 2764 rows, 2765 after, 2766 pattern, 2767 define, 2768 ) 2769 ) 2770 alias = self.sql(expression, "alias") 2771 alias = f" {alias}" if alias else "" 2772 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2774 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2775 limit = expression.args.get("limit") 2776 2777 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2778 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2779 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2780 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2781 2782 return csv( 2783 *sqls, 2784 *[self.sql(join) for join in expression.args.get("joins") or []], 2785 self.sql(expression, "match"), 2786 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2787 self.sql(expression, "prewhere"), 2788 self.sql(expression, "where"), 2789 self.sql(expression, "connect"), 2790 self.sql(expression, "group"), 2791 self.sql(expression, "having"), 2792 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2793 self.sql(expression, "order"), 2794 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2795 *self.after_limit_modifiers(expression), 2796 self.options_modifier(expression), 2797 self.for_modifiers(expression), 2798 sep="", 2799 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2813 def offset_limit_modifiers( 2814 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2815 ) -> t.List[str]: 2816 return [ 2817 self.sql(expression, "offset") if fetch else self.sql(limit), 2818 self.sql(limit) if fetch else self.sql(expression, "offset"), 2819 ]
2826 def select_sql(self, expression: exp.Select) -> str: 2827 into = expression.args.get("into") 2828 if not self.SUPPORTS_SELECT_INTO and into: 2829 into.pop() 2830 2831 hint = self.sql(expression, "hint") 2832 distinct = self.sql(expression, "distinct") 2833 distinct = f" {distinct}" if distinct else "" 2834 kind = self.sql(expression, "kind") 2835 2836 limit = expression.args.get("limit") 2837 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2838 top = self.limit_sql(limit, top=True) 2839 limit.pop() 2840 else: 2841 top = "" 2842 2843 expressions = self.expressions(expression) 2844 2845 if kind: 2846 if kind in self.SELECT_KINDS: 2847 kind = f" AS {kind}" 2848 else: 2849 if kind == "STRUCT": 2850 expressions = self.expressions( 2851 sqls=[ 2852 self.sql( 2853 exp.Struct( 2854 expressions=[ 2855 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2856 if isinstance(e, exp.Alias) 2857 else e 2858 for e in expression.expressions 2859 ] 2860 ) 2861 ) 2862 ] 2863 ) 2864 kind = "" 2865 2866 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2867 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2868 2869 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2870 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2871 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2872 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2873 sql = self.query_modifiers( 2874 expression, 2875 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2876 self.sql(expression, "into", comment=False), 2877 self.sql(expression, "from_", comment=False), 2878 ) 2879 2880 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2881 if expression.args.get("with_"): 2882 sql = self.maybe_comment(sql, expression) 2883 expression.pop_comments() 2884 2885 sql = self.prepend_ctes(expression, sql) 2886 2887 if not self.SUPPORTS_SELECT_INTO and into: 2888 if into.args.get("temporary"): 2889 table_kind = " TEMPORARY" 2890 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2891 table_kind = " UNLOGGED" 2892 else: 2893 table_kind = "" 2894 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2895 2896 return sql
2908 def star_sql(self, expression: exp.Star) -> str: 2909 except_ = self.expressions(expression, key="except_", flat=True) 2910 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2911 replace = self.expressions(expression, key="replace", flat=True) 2912 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2913 rename = self.expressions(expression, key="rename", flat=True) 2914 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2915 return f"*{except_}{replace}{rename}"
2931 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2932 alias = self.sql(expression, "alias") 2933 alias = f"{sep}{alias}" if alias else "" 2934 sample = self.sql(expression, "sample") 2935 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2936 alias = f"{sample}{alias}" 2937 2938 # Set to None so it's not generated again by self.query_modifiers() 2939 expression.set("sample", None) 2940 2941 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2942 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2943 return self.prepend_ctes(expression, sql)
2949 def unnest_sql(self, expression: exp.Unnest) -> str: 2950 args = self.expressions(expression, flat=True) 2951 2952 alias = expression.args.get("alias") 2953 offset = expression.args.get("offset") 2954 2955 if self.UNNEST_WITH_ORDINALITY: 2956 if alias and isinstance(offset, exp.Expression): 2957 alias.append("columns", offset) 2958 2959 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2960 columns = alias.columns 2961 alias = self.sql(columns[0]) if columns else "" 2962 else: 2963 alias = self.sql(alias) 2964 2965 alias = f" AS {alias}" if alias else alias 2966 if self.UNNEST_WITH_ORDINALITY: 2967 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2968 else: 2969 if isinstance(offset, exp.Expression): 2970 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2971 elif offset: 2972 suffix = f"{alias} WITH OFFSET" 2973 else: 2974 suffix = alias 2975 2976 return f"UNNEST({args}){suffix}"
2985 def window_sql(self, expression: exp.Window) -> str: 2986 this = self.sql(expression, "this") 2987 partition = self.partition_by_sql(expression) 2988 order = expression.args.get("order") 2989 order = self.order_sql(order, flat=True) if order else "" 2990 spec = self.sql(expression, "spec") 2991 alias = self.sql(expression, "alias") 2992 over = self.sql(expression, "over") or "OVER" 2993 2994 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2995 2996 first = expression.args.get("first") 2997 if first is None: 2998 first = "" 2999 else: 3000 first = "FIRST" if first else "LAST" 3001 3002 if not partition and not order and not spec and alias: 3003 return f"{this} {alias}" 3004 3005 args = self.format_args( 3006 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 3007 ) 3008 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
3014 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 3015 kind = self.sql(expression, "kind") 3016 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 3017 end = ( 3018 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 3019 or "CURRENT ROW" 3020 ) 3021 3022 window_spec = f"{kind} BETWEEN {start} AND {end}" 3023 3024 exclude = self.sql(expression, "exclude") 3025 if exclude: 3026 if self.SUPPORTS_WINDOW_EXCLUDE: 3027 window_spec += f" EXCLUDE {exclude}" 3028 else: 3029 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 3030 3031 return window_spec
3038 def between_sql(self, expression: exp.Between) -> str: 3039 this = self.sql(expression, "this") 3040 low = self.sql(expression, "low") 3041 high = self.sql(expression, "high") 3042 symmetric = expression.args.get("symmetric") 3043 3044 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 3045 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 3046 3047 flag = ( 3048 " SYMMETRIC" 3049 if symmetric 3050 else " ASYMMETRIC" 3051 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 3052 else "" # silently drop ASYMMETRIC – semantics identical 3053 ) 3054 return f"{this} BETWEEN{flag} {low} AND {high}"
def
bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket, index_offset: Optional[int] = None) -> List[sqlglot.expressions.Expression]:
3056 def bracket_offset_expressions( 3057 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 3058 ) -> t.List[exp.Expression]: 3059 return apply_index_offset( 3060 expression.this, 3061 expression.expressions, 3062 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 3063 dialect=self.dialect, 3064 )
3077 def any_sql(self, expression: exp.Any) -> str: 3078 this = self.sql(expression, "this") 3079 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 3080 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 3081 this = self.wrap(this) 3082 return f"ANY{this}" 3083 return f"ANY {this}"
3088 def case_sql(self, expression: exp.Case) -> str: 3089 this = self.sql(expression, "this") 3090 statements = [f"CASE {this}" if this else "CASE"] 3091 3092 for e in expression.args["ifs"]: 3093 statements.append(f"WHEN {self.sql(e, 'this')}") 3094 statements.append(f"THEN {self.sql(e, 'true')}") 3095 3096 default = self.sql(expression, "default") 3097 3098 if default: 3099 statements.append(f"ELSE {default}") 3100 3101 statements.append("END") 3102 3103 if self.pretty and self.too_wide(statements): 3104 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 3105 3106 return " ".join(statements)
3118 def extract_sql(self, expression: exp.Extract) -> str: 3119 from sqlglot.dialects.dialect import map_date_part 3120 3121 this = ( 3122 map_date_part(expression.this, self.dialect) 3123 if self.NORMALIZE_EXTRACT_DATE_PARTS 3124 else expression.this 3125 ) 3126 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 3127 expression_sql = self.sql(expression, "expression") 3128 3129 return f"EXTRACT({this_sql} FROM {expression_sql})"
3131 def trim_sql(self, expression: exp.Trim) -> str: 3132 trim_type = self.sql(expression, "position") 3133 3134 if trim_type == "LEADING": 3135 func_name = "LTRIM" 3136 elif trim_type == "TRAILING": 3137 func_name = "RTRIM" 3138 else: 3139 func_name = "TRIM" 3140 3141 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.Concat | sqlglot.expressions.ConcatWs) -> List[sqlglot.expressions.Expression]:
3143 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3144 args = expression.expressions 3145 if isinstance(expression, exp.ConcatWs): 3146 args = args[1:] # Skip the delimiter 3147 3148 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3149 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3150 3151 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3152 3153 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3154 if not e.type: 3155 from sqlglot.optimizer.annotate_types import annotate_types 3156 3157 e = annotate_types(e, dialect=self.dialect) 3158 3159 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3160 return e 3161 3162 return exp.func("coalesce", e, exp.Literal.string("")) 3163 3164 args = [_wrap_with_coalesce(e) for e in args] 3165 3166 return args
3168 def concat_sql(self, expression: exp.Concat) -> str: 3169 if self.dialect.CONCAT_COALESCE and not expression.args.get("coalesce"): 3170 # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not. 3171 # Transpile to double pipe operators, which typically returns NULL if any args are NULL 3172 # instead of coalescing them to empty string. 3173 from sqlglot.dialects.dialect import concat_to_dpipe_sql 3174 3175 return concat_to_dpipe_sql(self, expression) 3176 3177 expressions = self.convert_concat_args(expression) 3178 3179 # Some dialects don't allow a single-argument CONCAT call 3180 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3181 return self.sql(expressions[0]) 3182 3183 return self.func("CONCAT", *expressions)
3194 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3195 expressions = self.expressions(expression, flat=True) 3196 expressions = f" ({expressions})" if expressions else "" 3197 reference = self.sql(expression, "reference") 3198 reference = f" {reference}" if reference else "" 3199 delete = self.sql(expression, "delete") 3200 delete = f" ON DELETE {delete}" if delete else "" 3201 update = self.sql(expression, "update") 3202 update = f" ON UPDATE {update}" if update else "" 3203 options = self.expressions(expression, key="options", flat=True, sep=" ") 3204 options = f" {options}" if options else "" 3205 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3207 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3208 this = self.sql(expression, "this") 3209 this = f" {this}" if this else "" 3210 expressions = self.expressions(expression, flat=True) 3211 include = self.sql(expression, "include") 3212 options = self.expressions(expression, key="options", flat=True, sep=" ") 3213 options = f" {options}" if options else "" 3214 return f"PRIMARY KEY{this} ({expressions}){include}{options}"
3219 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3220 if self.MATCH_AGAINST_TABLE_PREFIX: 3221 expressions = [] 3222 for expr in expression.expressions: 3223 if isinstance(expr, exp.Table): 3224 expressions.append(f"TABLE {self.sql(expr)}") 3225 else: 3226 expressions.append(expr) 3227 else: 3228 expressions = expression.expressions 3229 3230 modifier = expression.args.get("modifier") 3231 modifier = f" {modifier}" if modifier else "" 3232 return ( 3233 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3234 )
3239 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3240 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3241 3242 if expression.args.get("escape"): 3243 path = self.escape_str(path) 3244 3245 if self.QUOTE_JSON_PATH: 3246 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3247 3248 return path
3250 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3251 if isinstance(expression, exp.JSONPathPart): 3252 transform = self.TRANSFORMS.get(expression.__class__) 3253 if not callable(transform): 3254 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3255 return "" 3256 3257 return transform(self, expression) 3258 3259 if isinstance(expression, int): 3260 return str(expression) 3261 3262 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3263 escaped = expression.replace("'", "\\'") 3264 escaped = f"\\'{expression}\\'" 3265 else: 3266 escaped = expression.replace('"', '\\"') 3267 escaped = f'"{escaped}"' 3268 3269 return escaped
3274 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3275 # Output the Teradata column FORMAT override. 3276 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3277 this = self.sql(expression, "this") 3278 fmt = self.sql(expression, "format") 3279 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3281 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3282 null_handling = expression.args.get("null_handling") 3283 null_handling = f" {null_handling}" if null_handling else "" 3284 3285 unique_keys = expression.args.get("unique_keys") 3286 if unique_keys is not None: 3287 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3288 else: 3289 unique_keys = "" 3290 3291 return_type = self.sql(expression, "return_type") 3292 return_type = f" RETURNING {return_type}" if return_type else "" 3293 encoding = self.sql(expression, "encoding") 3294 encoding = f" ENCODING {encoding}" if encoding else "" 3295 3296 return self.func( 3297 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3298 *expression.expressions, 3299 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3300 )
3305 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3306 null_handling = expression.args.get("null_handling") 3307 null_handling = f" {null_handling}" if null_handling else "" 3308 return_type = self.sql(expression, "return_type") 3309 return_type = f" RETURNING {return_type}" if return_type else "" 3310 strict = " STRICT" if expression.args.get("strict") else "" 3311 return self.func( 3312 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3313 )
3315 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3316 this = self.sql(expression, "this") 3317 order = self.sql(expression, "order") 3318 null_handling = expression.args.get("null_handling") 3319 null_handling = f" {null_handling}" if null_handling else "" 3320 return_type = self.sql(expression, "return_type") 3321 return_type = f" RETURNING {return_type}" if return_type else "" 3322 strict = " STRICT" if expression.args.get("strict") else "" 3323 return self.func( 3324 "JSON_ARRAYAGG", 3325 this, 3326 suffix=f"{order}{null_handling}{return_type}{strict})", 3327 )
3329 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3330 path = self.sql(expression, "path") 3331 path = f" PATH {path}" if path else "" 3332 nested_schema = self.sql(expression, "nested_schema") 3333 3334 if nested_schema: 3335 return f"NESTED{path} {nested_schema}" 3336 3337 this = self.sql(expression, "this") 3338 kind = self.sql(expression, "kind") 3339 kind = f" {kind}" if kind else "" 3340 3341 ordinality = " FOR ORDINALITY" if expression.args.get("ordinality") else "" 3342 return f"{this}{kind}{path}{ordinality}"
3347 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3348 this = self.sql(expression, "this") 3349 path = self.sql(expression, "path") 3350 path = f", {path}" if path else "" 3351 error_handling = expression.args.get("error_handling") 3352 error_handling = f" {error_handling}" if error_handling else "" 3353 empty_handling = expression.args.get("empty_handling") 3354 empty_handling = f" {empty_handling}" if empty_handling else "" 3355 schema = self.sql(expression, "schema") 3356 return self.func( 3357 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3358 )
3360 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3361 this = self.sql(expression, "this") 3362 kind = self.sql(expression, "kind") 3363 path = self.sql(expression, "path") 3364 path = f" {path}" if path else "" 3365 as_json = " AS JSON" if expression.args.get("as_json") else "" 3366 return f"{this} {kind}{path}{as_json}"
3368 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3369 this = self.sql(expression, "this") 3370 path = self.sql(expression, "path") 3371 path = f", {path}" if path else "" 3372 expressions = self.expressions(expression) 3373 with_ = ( 3374 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3375 if expressions 3376 else "" 3377 ) 3378 return f"OPENJSON({this}{path}){with_}"
3380 def in_sql(self, expression: exp.In) -> str: 3381 query = expression.args.get("query") 3382 unnest = expression.args.get("unnest") 3383 field = expression.args.get("field") 3384 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3385 3386 if query: 3387 in_sql = self.sql(query) 3388 elif unnest: 3389 in_sql = self.in_unnest_op(unnest) 3390 elif field: 3391 in_sql = self.sql(field) 3392 else: 3393 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3394 3395 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3400 def interval_sql(self, expression: exp.Interval) -> str: 3401 unit_expression = expression.args.get("unit") 3402 unit = self.sql(unit_expression) if unit_expression else "" 3403 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3404 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3405 unit = f" {unit}" if unit else "" 3406 3407 if self.SINGLE_STRING_INTERVAL: 3408 this = expression.this.name if expression.this else "" 3409 if this: 3410 if unit_expression and isinstance(unit_expression, exp.IntervalSpan): 3411 return f"INTERVAL '{this}'{unit}" 3412 return f"INTERVAL '{this}{unit}'" 3413 return f"INTERVAL{unit}" 3414 3415 this = self.sql(expression, "this") 3416 if this: 3417 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3418 this = f" {this}" if unwrapped else f" ({this})" 3419 3420 return f"INTERVAL{this}{unit}"
3425 def reference_sql(self, expression: exp.Reference) -> str: 3426 this = self.sql(expression, "this") 3427 expressions = self.expressions(expression, flat=True) 3428 expressions = f"({expressions})" if expressions else "" 3429 options = self.expressions(expression, key="options", flat=True, sep=" ") 3430 options = f" {options}" if options else "" 3431 return f"REFERENCES {this}{expressions}{options}"
3433 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3434 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3435 parent = expression.parent 3436 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3437 return self.func( 3438 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3439 )
3459 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3460 alias = expression.args["alias"] 3461 3462 parent = expression.parent 3463 pivot = parent and parent.parent 3464 3465 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3466 identifier_alias = isinstance(alias, exp.Identifier) 3467 literal_alias = isinstance(alias, exp.Literal) 3468 3469 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3470 alias.replace(exp.Literal.string(alias.output_name)) 3471 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3472 alias.replace(exp.to_identifier(alias.output_name)) 3473 3474 return self.alias_sql(expression)
def
and_sql( self, expression: sqlglot.expressions.And, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
or_sql( self, expression: sqlglot.expressions.Or, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
xor_sql( self, expression: sqlglot.expressions.Xor, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
connector_sql( self, expression: sqlglot.expressions.Connector, op: str, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3512 def connector_sql( 3513 self, 3514 expression: exp.Connector, 3515 op: str, 3516 stack: t.Optional[t.List[str | exp.Expression]] = None, 3517 ) -> str: 3518 if stack is not None: 3519 if expression.expressions: 3520 stack.append(self.expressions(expression, sep=f" {op} ")) 3521 else: 3522 stack.append(expression.right) 3523 if expression.comments and self.comments: 3524 for comment in expression.comments: 3525 if comment: 3526 op += f" /*{self.sanitize_comment(comment)}*/" 3527 stack.extend((op, expression.left)) 3528 return op 3529 3530 stack = [expression] 3531 sqls: t.List[str] = [] 3532 ops = set() 3533 3534 while stack: 3535 node = stack.pop() 3536 if isinstance(node, exp.Connector): 3537 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3538 else: 3539 sql = self.sql(node) 3540 if sqls and sqls[-1] in ops: 3541 sqls[-1] += f" {sql}" 3542 else: 3543 sqls.append(sql) 3544 3545 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3546 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3566 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3567 format_sql = self.sql(expression, "format") 3568 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3569 to_sql = self.sql(expression, "to") 3570 to_sql = f" {to_sql}" if to_sql else "" 3571 action = self.sql(expression, "action") 3572 action = f" {action}" if action else "" 3573 default = self.sql(expression, "default") 3574 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3575 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3593 def comment_sql(self, expression: exp.Comment) -> str: 3594 this = self.sql(expression, "this") 3595 kind = expression.args["kind"] 3596 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3597 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3598 expression_sql = self.sql(expression, "expression") 3599 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3601 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3602 this = self.sql(expression, "this") 3603 delete = " DELETE" if expression.args.get("delete") else "" 3604 recompress = self.sql(expression, "recompress") 3605 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3606 to_disk = self.sql(expression, "to_disk") 3607 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3608 to_volume = self.sql(expression, "to_volume") 3609 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3610 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3612 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3613 where = self.sql(expression, "where") 3614 group = self.sql(expression, "group") 3615 aggregates = self.expressions(expression, key="aggregates") 3616 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3617 3618 if not (where or group or aggregates) and len(expression.expressions) == 1: 3619 return f"TTL {self.expressions(expression, flat=True)}" 3620 3621 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3640 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3641 this = self.sql(expression, "this") 3642 3643 dtype = self.sql(expression, "dtype") 3644 if dtype: 3645 collate = self.sql(expression, "collate") 3646 collate = f" COLLATE {collate}" if collate else "" 3647 using = self.sql(expression, "using") 3648 using = f" USING {using}" if using else "" 3649 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3650 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3651 3652 default = self.sql(expression, "default") 3653 if default: 3654 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3655 3656 comment = self.sql(expression, "comment") 3657 if comment: 3658 return f"ALTER COLUMN {this} COMMENT {comment}" 3659 3660 visible = expression.args.get("visible") 3661 if visible: 3662 return f"ALTER COLUMN {this} SET {visible}" 3663 3664 allow_null = expression.args.get("allow_null") 3665 drop = expression.args.get("drop") 3666 3667 if not drop and not allow_null: 3668 self.unsupported("Unsupported ALTER COLUMN syntax") 3669 3670 if allow_null is not None: 3671 keyword = "DROP" if drop else "SET" 3672 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3673 3674 return f"ALTER COLUMN {this} DROP DEFAULT"
3690 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3691 compound = " COMPOUND" if expression.args.get("compound") else "" 3692 this = self.sql(expression, "this") 3693 expressions = self.expressions(expression, flat=True) 3694 expressions = f"({expressions})" if expressions else "" 3695 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3697 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3698 if not self.RENAME_TABLE_WITH_DB: 3699 # Remove db from tables 3700 expression = expression.transform( 3701 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3702 ).assert_is(exp.AlterRename) 3703 this = self.sql(expression, "this") 3704 to_kw = " TO" if include_to else "" 3705 return f"RENAME{to_kw} {this}"
3720 def alter_sql(self, expression: exp.Alter) -> str: 3721 actions = expression.args["actions"] 3722 3723 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3724 actions[0], exp.ColumnDef 3725 ): 3726 actions_sql = self.expressions(expression, key="actions", flat=True) 3727 actions_sql = f"ADD {actions_sql}" 3728 else: 3729 actions_list = [] 3730 for action in actions: 3731 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3732 action_sql = self.add_column_sql(action) 3733 else: 3734 action_sql = self.sql(action) 3735 if isinstance(action, exp.Query): 3736 action_sql = f"AS {action_sql}" 3737 3738 actions_list.append(action_sql) 3739 3740 actions_sql = self.format_args(*actions_list).lstrip("\n") 3741 3742 exists = " IF EXISTS" if expression.args.get("exists") else "" 3743 on_cluster = self.sql(expression, "cluster") 3744 on_cluster = f" {on_cluster}" if on_cluster else "" 3745 only = " ONLY" if expression.args.get("only") else "" 3746 options = self.expressions(expression, key="options") 3747 options = f", {options}" if options else "" 3748 kind = self.sql(expression, "kind") 3749 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3750 check = " WITH CHECK" if expression.args.get("check") else "" 3751 cascade = ( 3752 " CASCADE" 3753 if expression.args.get("cascade") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE 3754 else "" 3755 ) 3756 this = self.sql(expression, "this") 3757 this = f" {this}" if this else "" 3758 3759 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}"
3766 def add_column_sql(self, expression: exp.Expression) -> str: 3767 sql = self.sql(expression) 3768 if isinstance(expression, exp.Schema): 3769 column_text = " COLUMNS" 3770 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3771 column_text = " COLUMN" 3772 else: 3773 column_text = "" 3774 3775 return f"ADD{column_text} {sql}"
3785 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3786 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3787 location = self.sql(expression, "location") 3788 location = f" {location}" if location else "" 3789 return f"ADD {exists}{self.sql(expression.this)}{location}"
3791 def distinct_sql(self, expression: exp.Distinct) -> str: 3792 this = self.expressions(expression, flat=True) 3793 3794 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3795 case = exp.case() 3796 for arg in expression.expressions: 3797 case = case.when(arg.is_(exp.null()), exp.null()) 3798 this = self.sql(case.else_(f"({this})")) 3799 3800 this = f" {this}" if this else "" 3801 3802 on = self.sql(expression, "on") 3803 on = f" ON {on}" if on else "" 3804 return f"DISTINCT{this}{on}"
3833 def div_sql(self, expression: exp.Div) -> str: 3834 l, r = expression.left, expression.right 3835 3836 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3837 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3838 3839 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3840 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3841 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3842 3843 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3844 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3845 return self.sql( 3846 exp.cast( 3847 l / r, 3848 to=exp.DataType.Type.BIGINT, 3849 ) 3850 ) 3851 3852 return self.binary(expression, "/")
3973 def log_sql(self, expression: exp.Log) -> str: 3974 this = expression.this 3975 expr = expression.expression 3976 3977 if self.dialect.LOG_BASE_FIRST is False: 3978 this, expr = expr, this 3979 elif self.dialect.LOG_BASE_FIRST is None and expr: 3980 if this.name in ("2", "10"): 3981 return self.func(f"LOG{this.name}", expr) 3982 3983 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3984 3985 return self.func("LOG", this, expr)
3994 def binary(self, expression: exp.Binary, op: str) -> str: 3995 sqls: t.List[str] = [] 3996 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3997 binary_type = type(expression) 3998 3999 while stack: 4000 node = stack.pop() 4001 4002 if type(node) is binary_type: 4003 op_func = node.args.get("operator") 4004 if op_func: 4005 op = f"OPERATOR({self.sql(op_func)})" 4006 4007 stack.append(node.right) 4008 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 4009 stack.append(node.left) 4010 else: 4011 sqls.append(self.sql(node)) 4012 4013 return "".join(sqls)
4022 def function_fallback_sql(self, expression: exp.Func) -> str: 4023 args = [] 4024 4025 for key in expression.arg_types: 4026 arg_value = expression.args.get(key) 4027 4028 if isinstance(arg_value, list): 4029 for value in arg_value: 4030 args.append(value) 4031 elif arg_value is not None: 4032 args.append(arg_value) 4033 4034 if self.dialect.PRESERVE_ORIGINAL_NAMES: 4035 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 4036 else: 4037 name = expression.sql_name() 4038 4039 return self.func(name, *args)
def
func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType], prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
4041 def func( 4042 self, 4043 name: str, 4044 *args: t.Optional[exp.Expression | str], 4045 prefix: str = "(", 4046 suffix: str = ")", 4047 normalize: bool = True, 4048 ) -> str: 4049 name = self.normalize_func(name) if normalize else name 4050 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
4052 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 4053 arg_sqls = tuple( 4054 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 4055 ) 4056 if self.pretty and self.too_wide(arg_sqls): 4057 return self.indent( 4058 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 4059 ) 4060 return sep.join(arg_sqls)
def
format_time( self, expression: sqlglot.expressions.Expression, inverse_time_mapping: Optional[Dict[str, str]] = None, inverse_time_trie: Optional[Dict] = None) -> Optional[str]:
4065 def format_time( 4066 self, 4067 expression: exp.Expression, 4068 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 4069 inverse_time_trie: t.Optional[t.Dict] = None, 4070 ) -> t.Optional[str]: 4071 return format_time( 4072 self.sql(expression, "format"), 4073 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 4074 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 4075 )
def
expressions( self, expression: Optional[sqlglot.expressions.Expression] = None, key: Optional[str] = None, sqls: Optional[Collection[Union[str, sqlglot.expressions.Expression]]] = 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:
4077 def expressions( 4078 self, 4079 expression: t.Optional[exp.Expression] = None, 4080 key: t.Optional[str] = None, 4081 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 4082 flat: bool = False, 4083 indent: bool = True, 4084 skip_first: bool = False, 4085 skip_last: bool = False, 4086 sep: str = ", ", 4087 prefix: str = "", 4088 dynamic: bool = False, 4089 new_line: bool = False, 4090 ) -> str: 4091 expressions = expression.args.get(key or "expressions") if expression else sqls 4092 4093 if not expressions: 4094 return "" 4095 4096 if flat: 4097 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 4098 4099 num_sqls = len(expressions) 4100 result_sqls = [] 4101 4102 for i, e in enumerate(expressions): 4103 sql = self.sql(e, comment=False) 4104 if not sql: 4105 continue 4106 4107 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 4108 4109 if self.pretty: 4110 if self.leading_comma: 4111 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 4112 else: 4113 result_sqls.append( 4114 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 4115 ) 4116 else: 4117 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 4118 4119 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 4120 if new_line: 4121 result_sqls.insert(0, "") 4122 result_sqls.append("") 4123 result_sql = "\n".join(s.rstrip() for s in result_sqls) 4124 else: 4125 result_sql = "".join(result_sqls) 4126 4127 return ( 4128 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 4129 if indent 4130 else result_sql 4131 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
4133 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 4134 flat = flat or isinstance(expression.parent, exp.Properties) 4135 expressions_sql = self.expressions(expression, flat=flat) 4136 if flat: 4137 return f"{op} {expressions_sql}" 4138 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
4140 def naked_property(self, expression: exp.Property) -> str: 4141 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 4142 if not property_name: 4143 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 4144 return f"{property_name} {self.sql(expression, 'this')}"
4152 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 4153 this = self.sql(expression, "this") 4154 expressions = self.no_identify(self.expressions, expression) 4155 expressions = ( 4156 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 4157 ) 4158 return f"{this}{expressions}" if expressions.strip() != "" else this
4168 def when_sql(self, expression: exp.When) -> str: 4169 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4170 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4171 condition = self.sql(expression, "condition") 4172 condition = f" AND {condition}" if condition else "" 4173 4174 then_expression = expression.args.get("then") 4175 if isinstance(then_expression, exp.Insert): 4176 this = self.sql(then_expression, "this") 4177 this = f"INSERT {this}" if this else "INSERT" 4178 then = self.sql(then_expression, "expression") 4179 then = f"{this} VALUES {then}" if then else this 4180 elif isinstance(then_expression, exp.Update): 4181 if isinstance(then_expression.args.get("expressions"), exp.Star): 4182 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4183 else: 4184 expressions_sql = self.expressions(then_expression) 4185 then = f"UPDATE SET{self.sep()}{expressions_sql}" if expressions_sql else "UPDATE" 4186 4187 else: 4188 then = self.sql(then_expression) 4189 return f"WHEN {matched}{source}{condition} THEN {then}"
4194 def merge_sql(self, expression: exp.Merge) -> str: 4195 table = expression.this 4196 table_alias = "" 4197 4198 hints = table.args.get("hints") 4199 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4200 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4201 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4202 4203 this = self.sql(table) 4204 using = f"USING {self.sql(expression, 'using')}" 4205 whens = self.sql(expression, "whens") 4206 4207 on = self.sql(expression, "on") 4208 on = f"ON {on}" if on else "" 4209 4210 if not on: 4211 on = self.expressions(expression, key="using_cond") 4212 on = f"USING ({on})" if on else "" 4213 4214 returning = self.sql(expression, "returning") 4215 if returning: 4216 whens = f"{whens}{returning}" 4217 4218 sep = self.sep() 4219 4220 return self.prepend_ctes( 4221 expression, 4222 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4223 )
4229 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4230 if not self.SUPPORTS_TO_NUMBER: 4231 self.unsupported("Unsupported TO_NUMBER function") 4232 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4233 4234 fmt = expression.args.get("format") 4235 if not fmt: 4236 self.unsupported("Conversion format is required for TO_NUMBER") 4237 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4238 4239 return self.func("TO_NUMBER", expression.this, fmt)
4241 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4242 this = self.sql(expression, "this") 4243 kind = self.sql(expression, "kind") 4244 settings_sql = self.expressions(expression, key="settings", sep=" ") 4245 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4246 return f"{this}({kind}{args})"
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4267 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4268 expressions = self.expressions(expression, flat=True) 4269 expressions = f" {self.wrap(expressions)}" if expressions else "" 4270 buckets = self.sql(expression, "buckets") 4271 kind = self.sql(expression, "kind") 4272 buckets = f" BUCKETS {buckets}" if buckets else "" 4273 order = self.sql(expression, "order") 4274 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4279 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4280 expressions = self.expressions(expression, key="expressions", flat=True) 4281 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4282 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4283 buckets = self.sql(expression, "buckets") 4284 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4286 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4287 this = self.sql(expression, "this") 4288 having = self.sql(expression, "having") 4289 4290 if having: 4291 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4292 4293 return self.func("ANY_VALUE", this)
4295 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4296 transform = self.func("TRANSFORM", *expression.expressions) 4297 row_format_before = self.sql(expression, "row_format_before") 4298 row_format_before = f" {row_format_before}" if row_format_before else "" 4299 record_writer = self.sql(expression, "record_writer") 4300 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4301 using = f" USING {self.sql(expression, 'command_script')}" 4302 schema = self.sql(expression, "schema") 4303 schema = f" AS {schema}" if schema else "" 4304 row_format_after = self.sql(expression, "row_format_after") 4305 row_format_after = f" {row_format_after}" if row_format_after else "" 4306 record_reader = self.sql(expression, "record_reader") 4307 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4308 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4310 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4311 key_block_size = self.sql(expression, "key_block_size") 4312 if key_block_size: 4313 return f"KEY_BLOCK_SIZE = {key_block_size}" 4314 4315 using = self.sql(expression, "using") 4316 if using: 4317 return f"USING {using}" 4318 4319 parser = self.sql(expression, "parser") 4320 if parser: 4321 return f"WITH PARSER {parser}" 4322 4323 comment = self.sql(expression, "comment") 4324 if comment: 4325 return f"COMMENT {comment}" 4326 4327 visible = expression.args.get("visible") 4328 if visible is not None: 4329 return "VISIBLE" if visible else "INVISIBLE" 4330 4331 engine_attr = self.sql(expression, "engine_attr") 4332 if engine_attr: 4333 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4334 4335 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4336 if secondary_engine_attr: 4337 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4338 4339 self.unsupported("Unsupported index constraint option.") 4340 return ""
4346 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4347 kind = self.sql(expression, "kind") 4348 kind = f"{kind} INDEX" if kind else "INDEX" 4349 this = self.sql(expression, "this") 4350 this = f" {this}" if this else "" 4351 index_type = self.sql(expression, "index_type") 4352 index_type = f" USING {index_type}" if index_type else "" 4353 expressions = self.expressions(expression, flat=True) 4354 expressions = f" ({expressions})" if expressions else "" 4355 options = self.expressions(expression, key="options", sep=" ") 4356 options = f" {options}" if options else "" 4357 return f"{kind}{this}{index_type}{expressions}{options}"
4359 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4360 if self.NVL2_SUPPORTED: 4361 return self.function_fallback_sql(expression) 4362 4363 case = exp.Case().when( 4364 expression.this.is_(exp.null()).not_(copy=False), 4365 expression.args["true"], 4366 copy=False, 4367 ) 4368 else_cond = expression.args.get("false") 4369 if else_cond: 4370 case.else_(else_cond, copy=False) 4371 4372 return self.sql(case)
4374 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4375 this = self.sql(expression, "this") 4376 expr = self.sql(expression, "expression") 4377 position = self.sql(expression, "position") 4378 position = f", {position}" if position else "" 4379 iterator = self.sql(expression, "iterator") 4380 condition = self.sql(expression, "condition") 4381 condition = f" IF {condition}" if condition else "" 4382 return f"{this} FOR {expr}{position} IN {iterator}{condition}"
4417 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4418 this_sql = self.sql(expression, "this") 4419 if isinstance(expression.this, exp.Table): 4420 this_sql = f"TABLE {this_sql}" 4421 4422 return self.func( 4423 "FEATURES_AT_TIME", 4424 this_sql, 4425 expression.args.get("time"), 4426 expression.args.get("num_rows"), 4427 expression.args.get("ignore_feature_nulls"), 4428 )
4430 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4431 this_sql = self.sql(expression, "this") 4432 if isinstance(expression.this, exp.Table): 4433 this_sql = f"TABLE {this_sql}" 4434 4435 query_table = self.sql(expression, "query_table") 4436 if isinstance(expression.args["query_table"], exp.Table): 4437 query_table = f"TABLE {query_table}" 4438 4439 return self.func( 4440 "VECTOR_SEARCH", 4441 this_sql, 4442 expression.args.get("column_to_search"), 4443 query_table, 4444 expression.args.get("query_column_to_search"), 4445 expression.args.get("top_k"), 4446 expression.args.get("distance_type"), 4447 expression.args.get("options"), 4448 )
4460 def toarray_sql(self, expression: exp.ToArray) -> str: 4461 arg = expression.this 4462 if not arg.type: 4463 from sqlglot.optimizer.annotate_types import annotate_types 4464 4465 arg = annotate_types(arg, dialect=self.dialect) 4466 4467 if arg.is_type(exp.DataType.Type.ARRAY): 4468 return self.sql(arg) 4469 4470 cond_for_null = arg.is_(exp.null()) 4471 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4473 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4474 this = expression.this 4475 time_format = self.format_time(expression) 4476 4477 if time_format: 4478 return self.sql( 4479 exp.cast( 4480 exp.StrToTime(this=this, format=expression.args["format"]), 4481 exp.DataType.Type.TIME, 4482 ) 4483 ) 4484 4485 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4486 return self.sql(this) 4487 4488 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4490 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4491 this = expression.this 4492 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4493 return self.sql(this) 4494 4495 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4497 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4498 this = expression.this 4499 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4500 return self.sql(this) 4501 4502 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4504 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4505 this = expression.this 4506 time_format = self.format_time(expression) 4507 4508 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4509 return self.sql( 4510 exp.cast( 4511 exp.StrToTime(this=this, format=expression.args["format"]), 4512 exp.DataType.Type.DATE, 4513 ) 4514 ) 4515 4516 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4517 return self.sql(this) 4518 4519 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4531 def lastday_sql(self, expression: exp.LastDay) -> str: 4532 if self.LAST_DAY_SUPPORTS_DATE_PART: 4533 return self.function_fallback_sql(expression) 4534 4535 unit = expression.text("unit") 4536 if unit and unit != "MONTH": 4537 self.unsupported("Date parts are not supported in LAST_DAY.") 4538 4539 return self.func("LAST_DAY", expression.this)
4548 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4549 if self.CAN_IMPLEMENT_ARRAY_ANY: 4550 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4551 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4552 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4553 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4554 4555 from sqlglot.dialects import Dialect 4556 4557 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4558 if self.dialect.__class__ != Dialect: 4559 self.unsupported("ARRAY_ANY is unsupported") 4560 4561 return self.function_fallback_sql(expression)
4563 def struct_sql(self, expression: exp.Struct) -> str: 4564 expression.set( 4565 "expressions", 4566 [ 4567 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4568 if isinstance(e, exp.PropertyEQ) 4569 else e 4570 for e in expression.expressions 4571 ], 4572 ) 4573 4574 return self.function_fallback_sql(expression)
4582 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4583 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4584 tables = f" {self.expressions(expression)}" 4585 4586 exists = " IF EXISTS" if expression.args.get("exists") else "" 4587 4588 on_cluster = self.sql(expression, "cluster") 4589 on_cluster = f" {on_cluster}" if on_cluster else "" 4590 4591 identity = self.sql(expression, "identity") 4592 identity = f" {identity} IDENTITY" if identity else "" 4593 4594 option = self.sql(expression, "option") 4595 option = f" {option}" if option else "" 4596 4597 partition = self.sql(expression, "partition") 4598 partition = f" {partition}" if partition else "" 4599 4600 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4604 def convert_sql(self, expression: exp.Convert) -> str: 4605 to = expression.this 4606 value = expression.expression 4607 style = expression.args.get("style") 4608 safe = expression.args.get("safe") 4609 strict = expression.args.get("strict") 4610 4611 if not to or not value: 4612 return "" 4613 4614 # Retrieve length of datatype and override to default if not specified 4615 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4616 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4617 4618 transformed: t.Optional[exp.Expression] = None 4619 cast = exp.Cast if strict else exp.TryCast 4620 4621 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4622 if isinstance(style, exp.Literal) and style.is_int: 4623 from sqlglot.dialects.tsql import TSQL 4624 4625 style_value = style.name 4626 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4627 if not converted_style: 4628 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4629 4630 fmt = exp.Literal.string(converted_style) 4631 4632 if to.this == exp.DataType.Type.DATE: 4633 transformed = exp.StrToDate(this=value, format=fmt) 4634 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4635 transformed = exp.StrToTime(this=value, format=fmt) 4636 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4637 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4638 elif to.this == exp.DataType.Type.TEXT: 4639 transformed = exp.TimeToStr(this=value, format=fmt) 4640 4641 if not transformed: 4642 transformed = cast(this=value, to=to, safe=safe) 4643 4644 return self.sql(transformed)
4712 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4713 option = self.sql(expression, "this") 4714 4715 if expression.expressions: 4716 upper = option.upper() 4717 4718 # Snowflake FILE_FORMAT options are separated by whitespace 4719 sep = " " if upper == "FILE_FORMAT" else ", " 4720 4721 # Databricks copy/format options do not set their list of values with EQ 4722 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4723 values = self.expressions(expression, flat=True, sep=sep) 4724 return f"{option}{op}({values})" 4725 4726 value = self.sql(expression, "expression") 4727 4728 if not value: 4729 return option 4730 4731 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4732 4733 return f"{option}{op}{value}"
4735 def credentials_sql(self, expression: exp.Credentials) -> str: 4736 cred_expr = expression.args.get("credentials") 4737 if isinstance(cred_expr, exp.Literal): 4738 # Redshift case: CREDENTIALS <string> 4739 credentials = self.sql(expression, "credentials") 4740 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4741 else: 4742 # Snowflake case: CREDENTIALS = (...) 4743 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4744 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4745 4746 storage = self.sql(expression, "storage") 4747 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4748 4749 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4750 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4751 4752 iam_role = self.sql(expression, "iam_role") 4753 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4754 4755 region = self.sql(expression, "region") 4756 region = f" REGION {region}" if region else "" 4757 4758 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4760 def copy_sql(self, expression: exp.Copy) -> str: 4761 this = self.sql(expression, "this") 4762 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4763 4764 credentials = self.sql(expression, "credentials") 4765 credentials = self.seg(credentials) if credentials else "" 4766 files = self.expressions(expression, key="files", flat=True) 4767 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4768 4769 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4770 params = self.expressions( 4771 expression, 4772 key="params", 4773 sep=sep, 4774 new_line=True, 4775 skip_last=True, 4776 skip_first=True, 4777 indent=self.COPY_PARAMS_ARE_WRAPPED, 4778 ) 4779 4780 if params: 4781 if self.COPY_PARAMS_ARE_WRAPPED: 4782 params = f" WITH ({params})" 4783 elif not self.pretty and (files or credentials): 4784 params = f" {params}" 4785 4786 return f"COPY{this}{kind} {files}{credentials}{params}"
4791 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4792 on_sql = "ON" if expression.args.get("on") else "OFF" 4793 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4794 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4795 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4796 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4797 4798 if filter_col or retention_period: 4799 on_sql = self.func("ON", filter_col, retention_period) 4800 4801 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4803 def maskingpolicycolumnconstraint_sql( 4804 self, expression: exp.MaskingPolicyColumnConstraint 4805 ) -> str: 4806 this = self.sql(expression, "this") 4807 expressions = self.expressions(expression, flat=True) 4808 expressions = f" USING ({expressions})" if expressions else "" 4809 return f"MASKING POLICY {this}{expressions}"
4819 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4820 this = self.sql(expression, "this") 4821 expr = expression.expression 4822 4823 if isinstance(expr, exp.Func): 4824 # T-SQL's CLR functions are case sensitive 4825 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4826 else: 4827 expr = self.sql(expression, "expression") 4828 4829 return self.scope_resolution(expr, this)
4837 def rand_sql(self, expression: exp.Rand) -> str: 4838 lower = self.sql(expression, "lower") 4839 upper = self.sql(expression, "upper") 4840 4841 if lower and upper: 4842 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4843 return self.func("RAND", expression.this)
4845 def changes_sql(self, expression: exp.Changes) -> str: 4846 information = self.sql(expression, "information") 4847 information = f"INFORMATION => {information}" 4848 at_before = self.sql(expression, "at_before") 4849 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4850 end = self.sql(expression, "end") 4851 end = f"{self.seg('')}{end}" if end else "" 4852 4853 return f"CHANGES ({information}){at_before}{end}"
4855 def pad_sql(self, expression: exp.Pad) -> str: 4856 prefix = "L" if expression.args.get("is_left") else "R" 4857 4858 fill_pattern = self.sql(expression, "fill_pattern") or None 4859 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4860 fill_pattern = "' '" 4861 4862 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4868 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4869 generate_series = exp.GenerateSeries(**expression.args) 4870 4871 parent = expression.parent 4872 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4873 parent = parent.parent 4874 4875 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4876 return self.sql(exp.Unnest(expressions=[generate_series])) 4877 4878 if isinstance(parent, exp.Select): 4879 self.unsupported("GenerateSeries projection unnesting is not supported.") 4880 4881 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4883 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4884 exprs = expression.expressions 4885 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4886 if len(exprs) == 0: 4887 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4888 else: 4889 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4890 else: 4891 rhs = self.expressions(expression) # type: ignore 4892 4893 return self.func(name, expression.this, rhs or None)
4895 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4896 if self.SUPPORTS_CONVERT_TIMEZONE: 4897 return self.function_fallback_sql(expression) 4898 4899 source_tz = expression.args.get("source_tz") 4900 target_tz = expression.args.get("target_tz") 4901 timestamp = expression.args.get("timestamp") 4902 4903 if source_tz and timestamp: 4904 timestamp = exp.AtTimeZone( 4905 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4906 ) 4907 4908 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4909 4910 return self.sql(expr)
4912 def json_sql(self, expression: exp.JSON) -> str: 4913 this = self.sql(expression, "this") 4914 this = f" {this}" if this else "" 4915 4916 _with = expression.args.get("with_") 4917 4918 if _with is None: 4919 with_sql = "" 4920 elif not _with: 4921 with_sql = " WITHOUT" 4922 else: 4923 with_sql = " WITH" 4924 4925 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4926 4927 return f"JSON{this}{with_sql}{unique_sql}"
4929 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4930 def _generate_on_options(arg: t.Any) -> str: 4931 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4932 4933 path = self.sql(expression, "path") 4934 returning = self.sql(expression, "returning") 4935 returning = f" RETURNING {returning}" if returning else "" 4936 4937 on_condition = self.sql(expression, "on_condition") 4938 on_condition = f" {on_condition}" if on_condition else "" 4939 4940 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4942 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4943 else_ = "ELSE " if expression.args.get("else_") else "" 4944 condition = self.sql(expression, "expression") 4945 condition = f"WHEN {condition} THEN " if condition else else_ 4946 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4947 return f"{condition}{insert}"
4955 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4956 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4957 empty = expression.args.get("empty") 4958 empty = ( 4959 f"DEFAULT {empty} ON EMPTY" 4960 if isinstance(empty, exp.Expression) 4961 else self.sql(expression, "empty") 4962 ) 4963 4964 error = expression.args.get("error") 4965 error = ( 4966 f"DEFAULT {error} ON ERROR" 4967 if isinstance(error, exp.Expression) 4968 else self.sql(expression, "error") 4969 ) 4970 4971 if error and empty: 4972 error = ( 4973 f"{empty} {error}" 4974 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4975 else f"{error} {empty}" 4976 ) 4977 empty = "" 4978 4979 null = self.sql(expression, "null") 4980 4981 return f"{empty}{error}{null}"
4987 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4988 this = self.sql(expression, "this") 4989 path = self.sql(expression, "path") 4990 4991 passing = self.expressions(expression, "passing") 4992 passing = f" PASSING {passing}" if passing else "" 4993 4994 on_condition = self.sql(expression, "on_condition") 4995 on_condition = f" {on_condition}" if on_condition else "" 4996 4997 path = f"{path}{passing}{on_condition}" 4998 4999 return self.func("JSON_EXISTS", this, path)
5124 def overlay_sql(self, expression: exp.Overlay): 5125 this = self.sql(expression, "this") 5126 expr = self.sql(expression, "expression") 5127 from_sql = self.sql(expression, "from_") 5128 for_sql = self.sql(expression, "for_") 5129 for_sql = f" FOR {for_sql}" if for_sql else "" 5130 5131 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
5137 def string_sql(self, expression: exp.String) -> str: 5138 this = expression.this 5139 zone = expression.args.get("zone") 5140 5141 if zone: 5142 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 5143 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 5144 # set for source_tz to transpile the time conversion before the STRING cast 5145 this = exp.ConvertTimezone( 5146 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 5147 ) 5148 5149 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
5159 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 5160 filler = self.sql(expression, "this") 5161 filler = f" {filler}" if filler else "" 5162 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 5163 return f"TRUNCATE{filler} {with_count}"
5165 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 5166 if self.SUPPORTS_UNIX_SECONDS: 5167 return self.function_fallback_sql(expression) 5168 5169 start_ts = exp.cast( 5170 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 5171 ) 5172 5173 return self.sql( 5174 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 5175 )
5177 def arraysize_sql(self, expression: exp.ArraySize) -> str: 5178 dim = expression.expression 5179 5180 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 5181 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 5182 if not (dim.is_int and dim.name == "1"): 5183 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 5184 dim = None 5185 5186 # If dimension is required but not specified, default initialize it 5187 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 5188 dim = exp.Literal.number(1) 5189 5190 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
5192 def attach_sql(self, expression: exp.Attach) -> str: 5193 this = self.sql(expression, "this") 5194 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 5195 expressions = self.expressions(expression) 5196 expressions = f" ({expressions})" if expressions else "" 5197 5198 return f"ATTACH{exists_sql} {this}{expressions}"
5200 def detach_sql(self, expression: exp.Detach) -> str: 5201 this = self.sql(expression, "this") 5202 # the DATABASE keyword is required if IF EXISTS is set 5203 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5204 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5205 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5206 5207 return f"DETACH{exists_sql} {this}"
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
5220 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5221 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5222 encode = f"{encode} {self.sql(expression, 'this')}" 5223 5224 properties = expression.args.get("properties") 5225 if properties: 5226 encode = f"{encode} {self.properties(properties)}" 5227 5228 return encode
5230 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5231 this = self.sql(expression, "this") 5232 include = f"INCLUDE {this}" 5233 5234 column_def = self.sql(expression, "column_def") 5235 if column_def: 5236 include = f"{include} {column_def}" 5237 5238 alias = self.sql(expression, "alias") 5239 if alias: 5240 include = f"{include} AS {alias}" 5241 5242 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
5255 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5256 partitions = self.expressions(expression, "partition_expressions") 5257 create = self.expressions(expression, "create_expressions") 5258 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
5260 def partitionbyrangepropertydynamic_sql( 5261 self, expression: exp.PartitionByRangePropertyDynamic 5262 ) -> str: 5263 start = self.sql(expression, "start") 5264 end = self.sql(expression, "end") 5265 5266 every = expression.args["every"] 5267 if isinstance(every, exp.Interval) and every.this.is_string: 5268 every.this.replace(exp.Literal.number(every.name)) 5269 5270 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5283 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5284 kind = self.sql(expression, "kind") 5285 option = self.sql(expression, "option") 5286 option = f" {option}" if option else "" 5287 this = self.sql(expression, "this") 5288 this = f" {this}" if this else "" 5289 columns = self.expressions(expression) 5290 columns = f" {columns}" if columns else "" 5291 return f"{kind}{option} STATISTICS{this}{columns}"
5293 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5294 this = self.sql(expression, "this") 5295 columns = self.expressions(expression) 5296 inner_expression = self.sql(expression, "expression") 5297 inner_expression = f" {inner_expression}" if inner_expression else "" 5298 update_options = self.sql(expression, "update_options") 5299 update_options = f" {update_options} UPDATE" if update_options else "" 5300 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
5311 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5312 kind = self.sql(expression, "kind") 5313 this = self.sql(expression, "this") 5314 this = f" {this}" if this else "" 5315 inner_expression = self.sql(expression, "expression") 5316 return f"VALIDATE {kind}{this}{inner_expression}"
5318 def analyze_sql(self, expression: exp.Analyze) -> str: 5319 options = self.expressions(expression, key="options", sep=" ") 5320 options = f" {options}" if options else "" 5321 kind = self.sql(expression, "kind") 5322 kind = f" {kind}" if kind else "" 5323 this = self.sql(expression, "this") 5324 this = f" {this}" if this else "" 5325 mode = self.sql(expression, "mode") 5326 mode = f" {mode}" if mode else "" 5327 properties = self.sql(expression, "properties") 5328 properties = f" {properties}" if properties else "" 5329 partition = self.sql(expression, "partition") 5330 partition = f" {partition}" if partition else "" 5331 inner_expression = self.sql(expression, "expression") 5332 inner_expression = f" {inner_expression}" if inner_expression else "" 5333 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5335 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5336 this = self.sql(expression, "this") 5337 namespaces = self.expressions(expression, key="namespaces") 5338 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5339 passing = self.expressions(expression, key="passing") 5340 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5341 columns = self.expressions(expression, key="columns") 5342 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5343 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5344 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5350 def export_sql(self, expression: exp.Export) -> str: 5351 this = self.sql(expression, "this") 5352 connection = self.sql(expression, "connection") 5353 connection = f"WITH CONNECTION {connection} " if connection else "" 5354 options = self.sql(expression, "options") 5355 return f"EXPORT DATA {connection}{options} AS {this}"
5360 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5361 variable = self.sql(expression, "this") 5362 default = self.sql(expression, "default") 5363 default = f" = {default}" if default else "" 5364 5365 kind = self.sql(expression, "kind") 5366 if isinstance(expression.args.get("kind"), exp.Schema): 5367 kind = f"TABLE {kind}" 5368 5369 return f"{variable} AS {kind}{default}"
5371 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5372 kind = self.sql(expression, "kind") 5373 this = self.sql(expression, "this") 5374 set = self.sql(expression, "expression") 5375 using = self.sql(expression, "using") 5376 using = f" USING {using}" if using else "" 5377 5378 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5379 5380 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5403 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5404 # Snowflake GET/PUT statements: 5405 # PUT <file> <internalStage> <properties> 5406 # GET <internalStage> <file> <properties> 5407 props = expression.args.get("properties") 5408 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5409 this = self.sql(expression, "this") 5410 target = self.sql(expression, "target") 5411 5412 if isinstance(expression, exp.Put): 5413 return f"PUT {this} {target}{props_sql}" 5414 else: 5415 return f"GET {target} {this}{props_sql}"
5423 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5424 if self.SUPPORTS_DECODE_CASE: 5425 return self.func("DECODE", *expression.expressions) 5426 5427 expression, *expressions = expression.expressions 5428 5429 ifs = [] 5430 for search, result in zip(expressions[::2], expressions[1::2]): 5431 if isinstance(search, exp.Literal): 5432 ifs.append(exp.If(this=expression.eq(search), true=result)) 5433 elif isinstance(search, exp.Null): 5434 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5435 else: 5436 if isinstance(search, exp.Binary): 5437 search = exp.paren(search) 5438 5439 cond = exp.or_( 5440 expression.eq(search), 5441 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5442 copy=False, 5443 ) 5444 ifs.append(exp.If(this=cond, true=result)) 5445 5446 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5447 return self.sql(case)
5449 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5450 this = self.sql(expression, "this") 5451 this = self.seg(this, sep="") 5452 dimensions = self.expressions( 5453 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5454 ) 5455 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5456 metrics = self.expressions( 5457 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5458 ) 5459 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5460 facts = self.expressions(expression, "facts", dynamic=True, skip_first=True, skip_last=True) 5461 facts = self.seg(f"FACTS {facts}") if facts else "" 5462 where = self.sql(expression, "where") 5463 where = self.seg(f"WHERE {where}") if where else "" 5464 body = self.indent(this + metrics + dimensions + facts + where, skip_first=True) 5465 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5467 def getextract_sql(self, expression: exp.GetExtract) -> str: 5468 this = expression.this 5469 expr = expression.expression 5470 5471 if not this.type or not expression.type: 5472 from sqlglot.optimizer.annotate_types import annotate_types 5473 5474 this = annotate_types(this, dialect=self.dialect) 5475 5476 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5477 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5478 5479 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql(self, expression: sqlglot.expressions.RefreshTriggerProperty) -> str:
5496 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5497 method = self.sql(expression, "method") 5498 kind = expression.args.get("kind") 5499 if not kind: 5500 return f"REFRESH {method}" 5501 5502 every = self.sql(expression, "every") 5503 unit = self.sql(expression, "unit") 5504 every = f" EVERY {every} {unit}" if every else "" 5505 starts = self.sql(expression, "starts") 5506 starts = f" STARTS {starts}" if starts else "" 5507 5508 return f"REFRESH {method} ON {kind}{every}{starts}"
5517 def uuid_sql(self, expression: exp.Uuid) -> str: 5518 is_string = expression.args.get("is_string", False) 5519 uuid_func_sql = self.func("UUID") 5520 5521 if is_string and not self.dialect.UUID_IS_STRING_TYPE: 5522 return self.sql( 5523 exp.cast(uuid_func_sql, exp.DataType.Type.VARCHAR, dialect=self.dialect) 5524 ) 5525 5526 return uuid_func_sql
5528 def initcap_sql(self, expression: exp.Initcap) -> str: 5529 delimiters = expression.expression 5530 5531 if delimiters: 5532 # do not generate delimiters arg if we are round-tripping from default delimiters 5533 if ( 5534 delimiters.is_string 5535 and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS 5536 ): 5537 delimiters = None 5538 elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS: 5539 self.unsupported("INITCAP does not support custom delimiters") 5540 delimiters = None 5541 5542 return self.func("INITCAP", expression.this, delimiters)
5552 def weekstart_sql(self, expression: exp.WeekStart) -> str: 5553 this = expression.this.name.upper() 5554 if self.dialect.WEEK_OFFSET == -1 and this == "SUNDAY": 5555 # BigQuery specific optimization since WEEK(SUNDAY) == WEEK 5556 return "WEEK" 5557 5558 return self.func("WEEK", expression.this)