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 or 'always': Always quote. 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.AllowedValuesProperty: lambda self, 116 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 117 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 118 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 119 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 120 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 121 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 122 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 123 exp.CaseSpecificColumnConstraint: lambda _, 124 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 125 exp.Ceil: lambda self, e: self.ceil_floor(e), 126 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 127 exp.CharacterSetProperty: lambda self, 128 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 129 exp.ClusteredColumnConstraint: lambda self, 130 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 131 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 132 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 133 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 134 exp.ConvertToCharset: lambda self, e: self.func( 135 "CONVERT", e.this, e.args["dest"], e.args.get("source") 136 ), 137 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 138 exp.CredentialsProperty: lambda self, 139 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 140 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 141 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 142 exp.DynamicProperty: lambda *_: "DYNAMIC", 143 exp.EmptyProperty: lambda *_: "EMPTY", 144 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 145 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 146 exp.EphemeralColumnConstraint: lambda self, 147 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 148 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 149 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 150 exp.Except: lambda self, e: self.set_operations(e), 151 exp.ExternalProperty: lambda *_: "EXTERNAL", 152 exp.Floor: lambda self, e: self.ceil_floor(e), 153 exp.Get: lambda self, e: self.get_put_sql(e), 154 exp.GlobalProperty: lambda *_: "GLOBAL", 155 exp.HeapProperty: lambda *_: "HEAP", 156 exp.IcebergProperty: lambda *_: "ICEBERG", 157 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 158 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 159 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 160 exp.Intersect: lambda self, e: self.set_operations(e), 161 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 162 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 163 exp.LanguageProperty: lambda self, e: self.naked_property(e), 164 exp.LocationProperty: lambda self, e: self.naked_property(e), 165 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 166 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 167 exp.NonClusteredColumnConstraint: lambda self, 168 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 169 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 170 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 171 exp.OnCommitProperty: lambda _, 172 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 173 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 174 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 175 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 176 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 177 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 178 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 179 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 180 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 181 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 182 exp.ProjectionPolicyColumnConstraint: lambda self, 183 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 184 exp.Put: lambda self, e: self.get_put_sql(e), 185 exp.RemoteWithConnectionModelProperty: lambda self, 186 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 187 exp.ReturnsProperty: lambda self, e: ( 188 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 189 ), 190 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 191 exp.SecureProperty: lambda *_: "SECURE", 192 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 193 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 194 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 195 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 196 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 197 exp.SqlReadWriteProperty: lambda _, e: e.name, 198 exp.SqlSecurityProperty: lambda _, 199 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 200 exp.StabilityProperty: lambda _, e: e.name, 201 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 202 exp.StreamingTableProperty: lambda *_: "STREAMING", 203 exp.StrictProperty: lambda *_: "STRICT", 204 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 205 exp.TableColumn: lambda self, e: self.sql(e.this), 206 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 207 exp.TemporaryProperty: lambda *_: "TEMPORARY", 208 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 209 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 210 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 211 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 212 exp.TransientProperty: lambda *_: "TRANSIENT", 213 exp.Union: lambda self, e: self.set_operations(e), 214 exp.UnloggedProperty: lambda *_: "UNLOGGED", 215 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 216 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 217 exp.Uuid: lambda *_: "UUID()", 218 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 219 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 220 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 221 exp.UtcTimestamp: lambda self, e: self.sql( 222 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 223 ), 224 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 225 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 226 exp.VolatileProperty: lambda *_: "VOLATILE", 227 exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})", 228 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 229 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 230 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 231 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 232 exp.ForceProperty: lambda *_: "FORCE", 233 } 234 235 # Whether null ordering is supported in order by 236 # True: Full Support, None: No support, False: No support for certain cases 237 # such as window specifications, aggregate functions etc 238 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 239 240 # Whether ignore nulls is inside the agg or outside. 241 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 242 IGNORE_NULLS_IN_FUNC = False 243 244 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 245 LOCKING_READS_SUPPORTED = False 246 247 # Whether the EXCEPT and INTERSECT operations can return duplicates 248 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 249 250 # Wrap derived values in parens, usually standard but spark doesn't support it 251 WRAP_DERIVED_VALUES = True 252 253 # Whether create function uses an AS before the RETURN 254 CREATE_FUNCTION_RETURN_AS = True 255 256 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 257 MATCHED_BY_SOURCE = True 258 259 # Whether the INTERVAL expression works only with values like '1 day' 260 SINGLE_STRING_INTERVAL = False 261 262 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 263 INTERVAL_ALLOWS_PLURAL_FORM = True 264 265 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 266 LIMIT_FETCH = "ALL" 267 268 # Whether limit and fetch allows expresions or just limits 269 LIMIT_ONLY_LITERALS = False 270 271 # Whether a table is allowed to be renamed with a db 272 RENAME_TABLE_WITH_DB = True 273 274 # The separator for grouping sets and rollups 275 GROUPINGS_SEP = "," 276 277 # The string used for creating an index on a table 278 INDEX_ON = "ON" 279 280 # Whether join hints should be generated 281 JOIN_HINTS = True 282 283 # Whether table hints should be generated 284 TABLE_HINTS = True 285 286 # Whether query hints should be generated 287 QUERY_HINTS = True 288 289 # What kind of separator to use for query hints 290 QUERY_HINT_SEP = ", " 291 292 # Whether comparing against booleans (e.g. x IS TRUE) is supported 293 IS_BOOL_ALLOWED = True 294 295 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 296 DUPLICATE_KEY_UPDATE_WITH_SET = True 297 298 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 299 LIMIT_IS_TOP = False 300 301 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 302 RETURNING_END = True 303 304 # Whether to generate an unquoted value for EXTRACT's date part argument 305 EXTRACT_ALLOWS_QUOTES = True 306 307 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 308 TZ_TO_WITH_TIME_ZONE = False 309 310 # Whether the NVL2 function is supported 311 NVL2_SUPPORTED = True 312 313 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 314 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 315 316 # Whether VALUES statements can be used as derived tables. 317 # MySQL 5 and Redshift do not allow this, so when False, it will convert 318 # SELECT * VALUES into SELECT UNION 319 VALUES_AS_TABLE = True 320 321 # Whether the word COLUMN is included when adding a column with ALTER TABLE 322 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 323 324 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 325 UNNEST_WITH_ORDINALITY = True 326 327 # Whether FILTER (WHERE cond) can be used for conditional aggregation 328 AGGREGATE_FILTER_SUPPORTED = True 329 330 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 331 SEMI_ANTI_JOIN_WITH_SIDE = True 332 333 # Whether to include the type of a computed column in the CREATE DDL 334 COMPUTED_COLUMN_WITH_TYPE = True 335 336 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 337 SUPPORTS_TABLE_COPY = True 338 339 # Whether parentheses are required around the table sample's expression 340 TABLESAMPLE_REQUIRES_PARENS = True 341 342 # Whether a table sample clause's size needs to be followed by the ROWS keyword 343 TABLESAMPLE_SIZE_IS_ROWS = True 344 345 # The keyword(s) to use when generating a sample clause 346 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 347 348 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 349 TABLESAMPLE_WITH_METHOD = True 350 351 # The keyword to use when specifying the seed of a sample clause 352 TABLESAMPLE_SEED_KEYWORD = "SEED" 353 354 # Whether COLLATE is a function instead of a binary operator 355 COLLATE_IS_FUNC = False 356 357 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 358 DATA_TYPE_SPECIFIERS_ALLOWED = False 359 360 # Whether conditions require booleans WHERE x = 0 vs WHERE x 361 ENSURE_BOOLS = False 362 363 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 364 CTE_RECURSIVE_KEYWORD_REQUIRED = True 365 366 # Whether CONCAT requires >1 arguments 367 SUPPORTS_SINGLE_ARG_CONCAT = True 368 369 # Whether LAST_DAY function supports a date part argument 370 LAST_DAY_SUPPORTS_DATE_PART = True 371 372 # Whether named columns are allowed in table aliases 373 SUPPORTS_TABLE_ALIAS_COLUMNS = True 374 375 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 376 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 377 378 # What delimiter to use for separating JSON key/value pairs 379 JSON_KEY_VALUE_PAIR_SEP = ":" 380 381 # INSERT OVERWRITE TABLE x override 382 INSERT_OVERWRITE = " OVERWRITE TABLE" 383 384 # Whether the SELECT .. INTO syntax is used instead of CTAS 385 SUPPORTS_SELECT_INTO = False 386 387 # Whether UNLOGGED tables can be created 388 SUPPORTS_UNLOGGED_TABLES = False 389 390 # Whether the CREATE TABLE LIKE statement is supported 391 SUPPORTS_CREATE_TABLE_LIKE = True 392 393 # Whether the LikeProperty needs to be specified inside of the schema clause 394 LIKE_PROPERTY_INSIDE_SCHEMA = False 395 396 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 397 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 398 MULTI_ARG_DISTINCT = True 399 400 # Whether the JSON extraction operators expect a value of type JSON 401 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 402 403 # Whether bracketed keys like ["foo"] are supported in JSON paths 404 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 405 406 # Whether to escape keys using single quotes in JSON paths 407 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 408 409 # The JSONPathPart expressions supported by this dialect 410 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 411 412 # Whether any(f(x) for x in array) can be implemented by this dialect 413 CAN_IMPLEMENT_ARRAY_ANY = False 414 415 # Whether the function TO_NUMBER is supported 416 SUPPORTS_TO_NUMBER = True 417 418 # Whether EXCLUDE in window specification is supported 419 SUPPORTS_WINDOW_EXCLUDE = False 420 421 # Whether or not set op modifiers apply to the outer set op or select. 422 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 423 # True means limit 1 happens after the set op, False means it it happens on y. 424 SET_OP_MODIFIERS = True 425 426 # Whether parameters from COPY statement are wrapped in parentheses 427 COPY_PARAMS_ARE_WRAPPED = True 428 429 # Whether values of params are set with "=" token or empty space 430 COPY_PARAMS_EQ_REQUIRED = False 431 432 # Whether COPY statement has INTO keyword 433 COPY_HAS_INTO_KEYWORD = True 434 435 # Whether the conditional TRY(expression) function is supported 436 TRY_SUPPORTED = True 437 438 # Whether the UESCAPE syntax in unicode strings is supported 439 SUPPORTS_UESCAPE = True 440 441 # Function used to replace escaped unicode codes in unicode strings 442 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 443 444 # The keyword to use when generating a star projection with excluded columns 445 STAR_EXCEPT = "EXCEPT" 446 447 # The HEX function name 448 HEX_FUNC = "HEX" 449 450 # The keywords to use when prefixing & separating WITH based properties 451 WITH_PROPERTIES_PREFIX = "WITH" 452 453 # Whether to quote the generated expression of exp.JsonPath 454 QUOTE_JSON_PATH = True 455 456 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 457 PAD_FILL_PATTERN_IS_REQUIRED = False 458 459 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 460 SUPPORTS_EXPLODING_PROJECTIONS = True 461 462 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 463 ARRAY_CONCAT_IS_VAR_LEN = True 464 465 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 466 SUPPORTS_CONVERT_TIMEZONE = False 467 468 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 469 SUPPORTS_MEDIAN = True 470 471 # Whether UNIX_SECONDS(timestamp) is supported 472 SUPPORTS_UNIX_SECONDS = False 473 474 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 475 ALTER_SET_WRAPPED = False 476 477 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 478 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 479 # TODO: The normalization should be done by default once we've tested it across all dialects. 480 NORMALIZE_EXTRACT_DATE_PARTS = False 481 482 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 483 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 484 485 # The function name of the exp.ArraySize expression 486 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 487 488 # The syntax to use when altering the type of a column 489 ALTER_SET_TYPE = "SET DATA TYPE" 490 491 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 492 # None -> Doesn't support it at all 493 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 494 # True (Postgres) -> Explicitly requires it 495 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 496 497 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 498 SUPPORTS_DECODE_CASE = True 499 500 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 501 SUPPORTS_BETWEEN_FLAGS = False 502 503 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 504 SUPPORTS_LIKE_QUANTIFIERS = True 505 506 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 507 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 508 509 TYPE_MAPPING = { 510 exp.DataType.Type.DATETIME2: "TIMESTAMP", 511 exp.DataType.Type.NCHAR: "CHAR", 512 exp.DataType.Type.NVARCHAR: "VARCHAR", 513 exp.DataType.Type.MEDIUMTEXT: "TEXT", 514 exp.DataType.Type.LONGTEXT: "TEXT", 515 exp.DataType.Type.TINYTEXT: "TEXT", 516 exp.DataType.Type.BLOB: "VARBINARY", 517 exp.DataType.Type.MEDIUMBLOB: "BLOB", 518 exp.DataType.Type.LONGBLOB: "BLOB", 519 exp.DataType.Type.TINYBLOB: "BLOB", 520 exp.DataType.Type.INET: "INET", 521 exp.DataType.Type.ROWVERSION: "VARBINARY", 522 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 523 } 524 525 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 526 527 TIME_PART_SINGULARS = { 528 "MICROSECONDS": "MICROSECOND", 529 "SECONDS": "SECOND", 530 "MINUTES": "MINUTE", 531 "HOURS": "HOUR", 532 "DAYS": "DAY", 533 "WEEKS": "WEEK", 534 "MONTHS": "MONTH", 535 "QUARTERS": "QUARTER", 536 "YEARS": "YEAR", 537 } 538 539 AFTER_HAVING_MODIFIER_TRANSFORMS = { 540 "cluster": lambda self, e: self.sql(e, "cluster"), 541 "distribute": lambda self, e: self.sql(e, "distribute"), 542 "sort": lambda self, e: self.sql(e, "sort"), 543 "windows": lambda self, e: ( 544 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 545 if e.args.get("windows") 546 else "" 547 ), 548 "qualify": lambda self, e: self.sql(e, "qualify"), 549 } 550 551 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 552 553 STRUCT_DELIMITER = ("<", ">") 554 555 PARAMETER_TOKEN = "@" 556 NAMED_PLACEHOLDER_TOKEN = ":" 557 558 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 559 560 PROPERTIES_LOCATION = { 561 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 562 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 563 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 567 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 569 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 572 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 573 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 576 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 578 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 579 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 581 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 585 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 589 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 590 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 591 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 592 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 593 exp.HeapProperty: exp.Properties.Location.POST_WITH, 594 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 595 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 596 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 599 exp.JournalProperty: exp.Properties.Location.POST_NAME, 600 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 605 exp.LogProperty: exp.Properties.Location.POST_NAME, 606 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 607 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 608 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 609 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 611 exp.Order: exp.Properties.Location.POST_SCHEMA, 612 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 614 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 616 exp.Property: exp.Properties.Location.POST_WITH, 617 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 620 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 625 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 627 exp.Set: exp.Properties.Location.POST_SCHEMA, 628 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.SetProperty: exp.Properties.Location.POST_CREATE, 630 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 631 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 632 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 633 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 636 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 639 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 640 exp.Tags: exp.Properties.Location.POST_WITH, 641 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 642 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 644 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 645 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 646 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 647 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 648 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 649 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 650 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 651 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 652 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 653 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 654 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 655 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 656 } 657 658 # Keywords that can't be used as unquoted identifier names 659 RESERVED_KEYWORDS: t.Set[str] = set() 660 661 # Expressions whose comments are separated from them for better formatting 662 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 663 exp.Command, 664 exp.Create, 665 exp.Describe, 666 exp.Delete, 667 exp.Drop, 668 exp.From, 669 exp.Insert, 670 exp.Join, 671 exp.MultitableInserts, 672 exp.Order, 673 exp.Group, 674 exp.Having, 675 exp.Select, 676 exp.SetOperation, 677 exp.Update, 678 exp.Where, 679 exp.With, 680 ) 681 682 # Expressions that should not have their comments generated in maybe_comment 683 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 684 exp.Binary, 685 exp.SetOperation, 686 ) 687 688 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 689 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 690 exp.Column, 691 exp.Literal, 692 exp.Neg, 693 exp.Paren, 694 ) 695 696 PARAMETERIZABLE_TEXT_TYPES = { 697 exp.DataType.Type.NVARCHAR, 698 exp.DataType.Type.VARCHAR, 699 exp.DataType.Type.CHAR, 700 exp.DataType.Type.NCHAR, 701 } 702 703 # Expressions that need to have all CTEs under them bubbled up to them 704 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 705 706 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 707 708 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 709 710 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 711 712 __slots__ = ( 713 "pretty", 714 "identify", 715 "normalize", 716 "pad", 717 "_indent", 718 "normalize_functions", 719 "unsupported_level", 720 "max_unsupported", 721 "leading_comma", 722 "max_text_width", 723 "comments", 724 "dialect", 725 "unsupported_messages", 726 "_escaped_quote_end", 727 "_escaped_identifier_end", 728 "_next_name", 729 "_identifier_start", 730 "_identifier_end", 731 "_quote_json_path_key_using_brackets", 732 ) 733 734 def __init__( 735 self, 736 pretty: t.Optional[bool] = None, 737 identify: str | bool = False, 738 normalize: bool = False, 739 pad: int = 2, 740 indent: int = 2, 741 normalize_functions: t.Optional[str | bool] = None, 742 unsupported_level: ErrorLevel = ErrorLevel.WARN, 743 max_unsupported: int = 3, 744 leading_comma: bool = False, 745 max_text_width: int = 80, 746 comments: bool = True, 747 dialect: DialectType = None, 748 ): 749 import sqlglot 750 from sqlglot.dialects import Dialect 751 752 self.pretty = pretty if pretty is not None else sqlglot.pretty 753 self.identify = identify 754 self.normalize = normalize 755 self.pad = pad 756 self._indent = indent 757 self.unsupported_level = unsupported_level 758 self.max_unsupported = max_unsupported 759 self.leading_comma = leading_comma 760 self.max_text_width = max_text_width 761 self.comments = comments 762 self.dialect = Dialect.get_or_raise(dialect) 763 764 # This is both a Dialect property and a Generator argument, so we prioritize the latter 765 self.normalize_functions = ( 766 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 767 ) 768 769 self.unsupported_messages: t.List[str] = [] 770 self._escaped_quote_end: str = ( 771 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 772 ) 773 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 774 775 self._next_name = name_sequence("_t") 776 777 self._identifier_start = self.dialect.IDENTIFIER_START 778 self._identifier_end = self.dialect.IDENTIFIER_END 779 780 self._quote_json_path_key_using_brackets = True 781 782 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 783 """ 784 Generates the SQL string corresponding to the given syntax tree. 785 786 Args: 787 expression: The syntax tree. 788 copy: Whether to copy the expression. The generator performs mutations so 789 it is safer to copy. 790 791 Returns: 792 The SQL string corresponding to `expression`. 793 """ 794 if copy: 795 expression = expression.copy() 796 797 expression = self.preprocess(expression) 798 799 self.unsupported_messages = [] 800 sql = self.sql(expression).strip() 801 802 if self.pretty: 803 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 804 805 if self.unsupported_level == ErrorLevel.IGNORE: 806 return sql 807 808 if self.unsupported_level == ErrorLevel.WARN: 809 for msg in self.unsupported_messages: 810 logger.warning(msg) 811 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 812 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 813 814 return sql 815 816 def preprocess(self, expression: exp.Expression) -> exp.Expression: 817 """Apply generic preprocessing transformations to a given expression.""" 818 expression = self._move_ctes_to_top_level(expression) 819 820 if self.ENSURE_BOOLS: 821 from sqlglot.transforms import ensure_bools 822 823 expression = ensure_bools(expression) 824 825 return expression 826 827 def _move_ctes_to_top_level(self, expression: E) -> E: 828 if ( 829 not expression.parent 830 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 831 and any(node.parent is not expression for node in expression.find_all(exp.With)) 832 ): 833 from sqlglot.transforms import move_ctes_to_top_level 834 835 expression = move_ctes_to_top_level(expression) 836 return expression 837 838 def unsupported(self, message: str) -> None: 839 if self.unsupported_level == ErrorLevel.IMMEDIATE: 840 raise UnsupportedError(message) 841 self.unsupported_messages.append(message) 842 843 def sep(self, sep: str = " ") -> str: 844 return f"{sep.strip()}\n" if self.pretty else sep 845 846 def seg(self, sql: str, sep: str = " ") -> str: 847 return f"{self.sep(sep)}{sql}" 848 849 def sanitize_comment(self, comment: str) -> str: 850 comment = " " + comment if comment[0].strip() else comment 851 comment = comment + " " if comment[-1].strip() else comment 852 853 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 854 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 855 comment = comment.replace("*/", "* /") 856 857 return comment 858 859 def maybe_comment( 860 self, 861 sql: str, 862 expression: t.Optional[exp.Expression] = None, 863 comments: t.Optional[t.List[str]] = None, 864 separated: bool = False, 865 ) -> str: 866 comments = ( 867 ((expression and expression.comments) if comments is None else comments) # type: ignore 868 if self.comments 869 else None 870 ) 871 872 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 873 return sql 874 875 comments_sql = " ".join( 876 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 877 ) 878 879 if not comments_sql: 880 return sql 881 882 comments_sql = self._replace_line_breaks(comments_sql) 883 884 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 885 return ( 886 f"{self.sep()}{comments_sql}{sql}" 887 if not sql or sql[0].isspace() 888 else f"{comments_sql}{self.sep()}{sql}" 889 ) 890 891 return f"{sql} {comments_sql}" 892 893 def wrap(self, expression: exp.Expression | str) -> str: 894 this_sql = ( 895 self.sql(expression) 896 if isinstance(expression, exp.UNWRAPPED_QUERIES) 897 else self.sql(expression, "this") 898 ) 899 if not this_sql: 900 return "()" 901 902 this_sql = self.indent(this_sql, level=1, pad=0) 903 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 904 905 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 906 original = self.identify 907 self.identify = False 908 result = func(*args, **kwargs) 909 self.identify = original 910 return result 911 912 def normalize_func(self, name: str) -> str: 913 if self.normalize_functions == "upper" or self.normalize_functions is True: 914 return name.upper() 915 if self.normalize_functions == "lower": 916 return name.lower() 917 return name 918 919 def indent( 920 self, 921 sql: str, 922 level: int = 0, 923 pad: t.Optional[int] = None, 924 skip_first: bool = False, 925 skip_last: bool = False, 926 ) -> str: 927 if not self.pretty or not sql: 928 return sql 929 930 pad = self.pad if pad is None else pad 931 lines = sql.split("\n") 932 933 return "\n".join( 934 ( 935 line 936 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 937 else f"{' ' * (level * self._indent + pad)}{line}" 938 ) 939 for i, line in enumerate(lines) 940 ) 941 942 def sql( 943 self, 944 expression: t.Optional[str | exp.Expression], 945 key: t.Optional[str] = None, 946 comment: bool = True, 947 ) -> str: 948 if not expression: 949 return "" 950 951 if isinstance(expression, str): 952 return expression 953 954 if key: 955 value = expression.args.get(key) 956 if value: 957 return self.sql(value) 958 return "" 959 960 transform = self.TRANSFORMS.get(expression.__class__) 961 962 if callable(transform): 963 sql = transform(self, expression) 964 elif isinstance(expression, exp.Expression): 965 exp_handler_name = f"{expression.key}_sql" 966 967 if hasattr(self, exp_handler_name): 968 sql = getattr(self, exp_handler_name)(expression) 969 elif isinstance(expression, exp.Func): 970 sql = self.function_fallback_sql(expression) 971 elif isinstance(expression, exp.Property): 972 sql = self.property_sql(expression) 973 else: 974 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 975 else: 976 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 977 978 return self.maybe_comment(sql, expression) if self.comments and comment else sql 979 980 def uncache_sql(self, expression: exp.Uncache) -> str: 981 table = self.sql(expression, "this") 982 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 983 return f"UNCACHE TABLE{exists_sql} {table}" 984 985 def cache_sql(self, expression: exp.Cache) -> str: 986 lazy = " LAZY" if expression.args.get("lazy") else "" 987 table = self.sql(expression, "this") 988 options = expression.args.get("options") 989 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 990 sql = self.sql(expression, "expression") 991 sql = f" AS{self.sep()}{sql}" if sql else "" 992 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 993 return self.prepend_ctes(expression, sql) 994 995 def characterset_sql(self, expression: exp.CharacterSet) -> str: 996 if isinstance(expression.parent, exp.Cast): 997 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 998 default = "DEFAULT " if expression.args.get("default") else "" 999 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1000 1001 def column_parts(self, expression: exp.Column) -> str: 1002 return ".".join( 1003 self.sql(part) 1004 for part in ( 1005 expression.args.get("catalog"), 1006 expression.args.get("db"), 1007 expression.args.get("table"), 1008 expression.args.get("this"), 1009 ) 1010 if part 1011 ) 1012 1013 def column_sql(self, expression: exp.Column) -> str: 1014 join_mark = " (+)" if expression.args.get("join_mark") else "" 1015 1016 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1017 join_mark = "" 1018 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1019 1020 return f"{self.column_parts(expression)}{join_mark}" 1021 1022 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1023 this = self.sql(expression, "this") 1024 this = f" {this}" if this else "" 1025 position = self.sql(expression, "position") 1026 return f"{position}{this}" 1027 1028 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1029 column = self.sql(expression, "this") 1030 kind = self.sql(expression, "kind") 1031 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1032 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1033 kind = f"{sep}{kind}" if kind else "" 1034 constraints = f" {constraints}" if constraints else "" 1035 position = self.sql(expression, "position") 1036 position = f" {position}" if position else "" 1037 1038 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1039 kind = "" 1040 1041 return f"{exists}{column}{kind}{constraints}{position}" 1042 1043 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1044 this = self.sql(expression, "this") 1045 kind_sql = self.sql(expression, "kind").strip() 1046 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1047 1048 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1049 this = self.sql(expression, "this") 1050 if expression.args.get("not_null"): 1051 persisted = " PERSISTED NOT NULL" 1052 elif expression.args.get("persisted"): 1053 persisted = " PERSISTED" 1054 else: 1055 persisted = "" 1056 1057 return f"AS {this}{persisted}" 1058 1059 def autoincrementcolumnconstraint_sql(self, _) -> str: 1060 return self.token_sql(TokenType.AUTO_INCREMENT) 1061 1062 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1063 if isinstance(expression.this, list): 1064 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1065 else: 1066 this = self.sql(expression, "this") 1067 1068 return f"COMPRESS {this}" 1069 1070 def generatedasidentitycolumnconstraint_sql( 1071 self, expression: exp.GeneratedAsIdentityColumnConstraint 1072 ) -> str: 1073 this = "" 1074 if expression.this is not None: 1075 on_null = " ON NULL" if expression.args.get("on_null") else "" 1076 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1077 1078 start = expression.args.get("start") 1079 start = f"START WITH {start}" if start else "" 1080 increment = expression.args.get("increment") 1081 increment = f" INCREMENT BY {increment}" if increment else "" 1082 minvalue = expression.args.get("minvalue") 1083 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1084 maxvalue = expression.args.get("maxvalue") 1085 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1086 cycle = expression.args.get("cycle") 1087 cycle_sql = "" 1088 1089 if cycle is not None: 1090 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1091 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1092 1093 sequence_opts = "" 1094 if start or increment or cycle_sql: 1095 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1096 sequence_opts = f" ({sequence_opts.strip()})" 1097 1098 expr = self.sql(expression, "expression") 1099 expr = f"({expr})" if expr else "IDENTITY" 1100 1101 return f"GENERATED{this} AS {expr}{sequence_opts}" 1102 1103 def generatedasrowcolumnconstraint_sql( 1104 self, expression: exp.GeneratedAsRowColumnConstraint 1105 ) -> str: 1106 start = "START" if expression.args.get("start") else "END" 1107 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1108 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1109 1110 def periodforsystemtimeconstraint_sql( 1111 self, expression: exp.PeriodForSystemTimeConstraint 1112 ) -> str: 1113 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1114 1115 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1116 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1117 1118 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1119 desc = expression.args.get("desc") 1120 if desc is not None: 1121 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1122 options = self.expressions(expression, key="options", flat=True, sep=" ") 1123 options = f" {options}" if options else "" 1124 return f"PRIMARY KEY{options}" 1125 1126 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1127 this = self.sql(expression, "this") 1128 this = f" {this}" if this else "" 1129 index_type = expression.args.get("index_type") 1130 index_type = f" USING {index_type}" if index_type else "" 1131 on_conflict = self.sql(expression, "on_conflict") 1132 on_conflict = f" {on_conflict}" if on_conflict else "" 1133 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1134 options = self.expressions(expression, key="options", flat=True, sep=" ") 1135 options = f" {options}" if options else "" 1136 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1137 1138 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1139 return self.sql(expression, "this") 1140 1141 def create_sql(self, expression: exp.Create) -> str: 1142 kind = self.sql(expression, "kind") 1143 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1144 properties = expression.args.get("properties") 1145 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1146 1147 this = self.createable_sql(expression, properties_locs) 1148 1149 properties_sql = "" 1150 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1151 exp.Properties.Location.POST_WITH 1152 ): 1153 props_ast = exp.Properties( 1154 expressions=[ 1155 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1156 *properties_locs[exp.Properties.Location.POST_WITH], 1157 ] 1158 ) 1159 props_ast.parent = expression 1160 properties_sql = self.sql(props_ast) 1161 1162 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1163 properties_sql = self.sep() + properties_sql 1164 elif not self.pretty: 1165 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1166 properties_sql = f" {properties_sql}" 1167 1168 begin = " BEGIN" if expression.args.get("begin") else "" 1169 end = " END" if expression.args.get("end") else "" 1170 1171 expression_sql = self.sql(expression, "expression") 1172 if expression_sql: 1173 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1174 1175 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1176 postalias_props_sql = "" 1177 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1178 postalias_props_sql = self.properties( 1179 exp.Properties( 1180 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1181 ), 1182 wrapped=False, 1183 ) 1184 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1185 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1186 1187 postindex_props_sql = "" 1188 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1189 postindex_props_sql = self.properties( 1190 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1191 wrapped=False, 1192 prefix=" ", 1193 ) 1194 1195 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1196 indexes = f" {indexes}" if indexes else "" 1197 index_sql = indexes + postindex_props_sql 1198 1199 replace = " OR REPLACE" if expression.args.get("replace") else "" 1200 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1201 unique = " UNIQUE" if expression.args.get("unique") else "" 1202 1203 clustered = expression.args.get("clustered") 1204 if clustered is None: 1205 clustered_sql = "" 1206 elif clustered: 1207 clustered_sql = " CLUSTERED COLUMNSTORE" 1208 else: 1209 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1210 1211 postcreate_props_sql = "" 1212 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1213 postcreate_props_sql = self.properties( 1214 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1215 sep=" ", 1216 prefix=" ", 1217 wrapped=False, 1218 ) 1219 1220 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1221 1222 postexpression_props_sql = "" 1223 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1224 postexpression_props_sql = self.properties( 1225 exp.Properties( 1226 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1227 ), 1228 sep=" ", 1229 prefix=" ", 1230 wrapped=False, 1231 ) 1232 1233 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1234 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1235 no_schema_binding = ( 1236 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1237 ) 1238 1239 clone = self.sql(expression, "clone") 1240 clone = f" {clone}" if clone else "" 1241 1242 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1243 properties_expression = f"{expression_sql}{properties_sql}" 1244 else: 1245 properties_expression = f"{properties_sql}{expression_sql}" 1246 1247 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1248 return self.prepend_ctes(expression, expression_sql) 1249 1250 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1251 start = self.sql(expression, "start") 1252 start = f"START WITH {start}" if start else "" 1253 increment = self.sql(expression, "increment") 1254 increment = f" INCREMENT BY {increment}" if increment else "" 1255 minvalue = self.sql(expression, "minvalue") 1256 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1257 maxvalue = self.sql(expression, "maxvalue") 1258 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1259 owned = self.sql(expression, "owned") 1260 owned = f" OWNED BY {owned}" if owned else "" 1261 1262 cache = expression.args.get("cache") 1263 if cache is None: 1264 cache_str = "" 1265 elif cache is True: 1266 cache_str = " CACHE" 1267 else: 1268 cache_str = f" CACHE {cache}" 1269 1270 options = self.expressions(expression, key="options", flat=True, sep=" ") 1271 options = f" {options}" if options else "" 1272 1273 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1274 1275 def clone_sql(self, expression: exp.Clone) -> str: 1276 this = self.sql(expression, "this") 1277 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1278 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1279 return f"{shallow}{keyword} {this}" 1280 1281 def describe_sql(self, expression: exp.Describe) -> str: 1282 style = expression.args.get("style") 1283 style = f" {style}" if style else "" 1284 partition = self.sql(expression, "partition") 1285 partition = f" {partition}" if partition else "" 1286 format = self.sql(expression, "format") 1287 format = f" {format}" if format else "" 1288 1289 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1290 1291 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1292 tag = self.sql(expression, "tag") 1293 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1294 1295 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1296 with_ = self.sql(expression, "with") 1297 if with_: 1298 sql = f"{with_}{self.sep()}{sql}" 1299 return sql 1300 1301 def with_sql(self, expression: exp.With) -> str: 1302 sql = self.expressions(expression, flat=True) 1303 recursive = ( 1304 "RECURSIVE " 1305 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1306 else "" 1307 ) 1308 search = self.sql(expression, "search") 1309 search = f" {search}" if search else "" 1310 1311 return f"WITH {recursive}{sql}{search}" 1312 1313 def cte_sql(self, expression: exp.CTE) -> str: 1314 alias = expression.args.get("alias") 1315 if alias: 1316 alias.add_comments(expression.pop_comments()) 1317 1318 alias_sql = self.sql(expression, "alias") 1319 1320 materialized = expression.args.get("materialized") 1321 if materialized is False: 1322 materialized = "NOT MATERIALIZED " 1323 elif materialized: 1324 materialized = "MATERIALIZED " 1325 1326 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1327 1328 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1329 alias = self.sql(expression, "this") 1330 columns = self.expressions(expression, key="columns", flat=True) 1331 columns = f"({columns})" if columns else "" 1332 1333 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1334 columns = "" 1335 self.unsupported("Named columns are not supported in table alias.") 1336 1337 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1338 alias = self._next_name() 1339 1340 return f"{alias}{columns}" 1341 1342 def bitstring_sql(self, expression: exp.BitString) -> str: 1343 this = self.sql(expression, "this") 1344 if self.dialect.BIT_START: 1345 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1346 return f"{int(this, 2)}" 1347 1348 def hexstring_sql( 1349 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1350 ) -> str: 1351 this = self.sql(expression, "this") 1352 is_integer_type = expression.args.get("is_integer") 1353 1354 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1355 not self.dialect.HEX_START and not binary_function_repr 1356 ): 1357 # Integer representation will be returned if: 1358 # - The read dialect treats the hex value as integer literal but not the write 1359 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1360 return f"{int(this, 16)}" 1361 1362 if not is_integer_type: 1363 # Read dialect treats the hex value as BINARY/BLOB 1364 if binary_function_repr: 1365 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1366 return self.func(binary_function_repr, exp.Literal.string(this)) 1367 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1368 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1369 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1370 1371 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1372 1373 def bytestring_sql(self, expression: exp.ByteString) -> str: 1374 this = self.sql(expression, "this") 1375 if self.dialect.BYTE_START: 1376 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1377 return this 1378 1379 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1380 this = self.sql(expression, "this") 1381 escape = expression.args.get("escape") 1382 1383 if self.dialect.UNICODE_START: 1384 escape_substitute = r"\\\1" 1385 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1386 else: 1387 escape_substitute = r"\\u\1" 1388 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1389 1390 if escape: 1391 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1392 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1393 else: 1394 escape_pattern = ESCAPED_UNICODE_RE 1395 escape_sql = "" 1396 1397 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1398 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1399 1400 return f"{left_quote}{this}{right_quote}{escape_sql}" 1401 1402 def rawstring_sql(self, expression: exp.RawString) -> str: 1403 string = expression.this 1404 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1405 string = string.replace("\\", "\\\\") 1406 1407 string = self.escape_str(string, escape_backslash=False) 1408 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1409 1410 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1411 this = self.sql(expression, "this") 1412 specifier = self.sql(expression, "expression") 1413 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1414 return f"{this}{specifier}" 1415 1416 def datatype_sql(self, expression: exp.DataType) -> str: 1417 nested = "" 1418 values = "" 1419 interior = self.expressions(expression, flat=True) 1420 1421 type_value = expression.this 1422 if type_value in self.UNSUPPORTED_TYPES: 1423 self.unsupported( 1424 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1425 ) 1426 1427 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1428 type_sql = self.sql(expression, "kind") 1429 else: 1430 type_sql = ( 1431 self.TYPE_MAPPING.get(type_value, type_value.value) 1432 if isinstance(type_value, exp.DataType.Type) 1433 else type_value 1434 ) 1435 1436 if interior: 1437 if expression.args.get("nested"): 1438 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1439 if expression.args.get("values") is not None: 1440 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1441 values = self.expressions(expression, key="values", flat=True) 1442 values = f"{delimiters[0]}{values}{delimiters[1]}" 1443 elif type_value == exp.DataType.Type.INTERVAL: 1444 nested = f" {interior}" 1445 else: 1446 nested = f"({interior})" 1447 1448 type_sql = f"{type_sql}{nested}{values}" 1449 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1450 exp.DataType.Type.TIMETZ, 1451 exp.DataType.Type.TIMESTAMPTZ, 1452 ): 1453 type_sql = f"{type_sql} WITH TIME ZONE" 1454 1455 return type_sql 1456 1457 def directory_sql(self, expression: exp.Directory) -> str: 1458 local = "LOCAL " if expression.args.get("local") else "" 1459 row_format = self.sql(expression, "row_format") 1460 row_format = f" {row_format}" if row_format else "" 1461 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1462 1463 def delete_sql(self, expression: exp.Delete) -> str: 1464 this = self.sql(expression, "this") 1465 this = f" FROM {this}" if this else "" 1466 using = self.sql(expression, "using") 1467 using = f" USING {using}" if using else "" 1468 cluster = self.sql(expression, "cluster") 1469 cluster = f" {cluster}" if cluster else "" 1470 where = self.sql(expression, "where") 1471 returning = self.sql(expression, "returning") 1472 limit = self.sql(expression, "limit") 1473 tables = self.expressions(expression, key="tables") 1474 tables = f" {tables}" if tables else "" 1475 if self.RETURNING_END: 1476 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1477 else: 1478 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1479 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1480 1481 def drop_sql(self, expression: exp.Drop) -> str: 1482 this = self.sql(expression, "this") 1483 expressions = self.expressions(expression, flat=True) 1484 expressions = f" ({expressions})" if expressions else "" 1485 kind = expression.args["kind"] 1486 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1487 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1488 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1489 on_cluster = self.sql(expression, "cluster") 1490 on_cluster = f" {on_cluster}" if on_cluster else "" 1491 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1492 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1493 cascade = " CASCADE" if expression.args.get("cascade") else "" 1494 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1495 purge = " PURGE" if expression.args.get("purge") else "" 1496 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1497 1498 def set_operation(self, expression: exp.SetOperation) -> str: 1499 op_type = type(expression) 1500 op_name = op_type.key.upper() 1501 1502 distinct = expression.args.get("distinct") 1503 if ( 1504 distinct is False 1505 and op_type in (exp.Except, exp.Intersect) 1506 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1507 ): 1508 self.unsupported(f"{op_name} ALL is not supported") 1509 1510 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1511 1512 if distinct is None: 1513 distinct = default_distinct 1514 if distinct is None: 1515 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1516 1517 if distinct is default_distinct: 1518 distinct_or_all = "" 1519 else: 1520 distinct_or_all = " DISTINCT" if distinct else " ALL" 1521 1522 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1523 side_kind = f"{side_kind} " if side_kind else "" 1524 1525 by_name = " BY NAME" if expression.args.get("by_name") else "" 1526 on = self.expressions(expression, key="on", flat=True) 1527 on = f" ON ({on})" if on else "" 1528 1529 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1530 1531 def set_operations(self, expression: exp.SetOperation) -> str: 1532 if not self.SET_OP_MODIFIERS: 1533 limit = expression.args.get("limit") 1534 order = expression.args.get("order") 1535 1536 if limit or order: 1537 select = self._move_ctes_to_top_level( 1538 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1539 ) 1540 1541 if limit: 1542 select = select.limit(limit.pop(), copy=False) 1543 if order: 1544 select = select.order_by(order.pop(), copy=False) 1545 return self.sql(select) 1546 1547 sqls: t.List[str] = [] 1548 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1549 1550 while stack: 1551 node = stack.pop() 1552 1553 if isinstance(node, exp.SetOperation): 1554 stack.append(node.expression) 1555 stack.append( 1556 self.maybe_comment( 1557 self.set_operation(node), comments=node.comments, separated=True 1558 ) 1559 ) 1560 stack.append(node.this) 1561 else: 1562 sqls.append(self.sql(node)) 1563 1564 this = self.sep().join(sqls) 1565 this = self.query_modifiers(expression, this) 1566 return self.prepend_ctes(expression, this) 1567 1568 def fetch_sql(self, expression: exp.Fetch) -> str: 1569 direction = expression.args.get("direction") 1570 direction = f" {direction}" if direction else "" 1571 count = self.sql(expression, "count") 1572 count = f" {count}" if count else "" 1573 limit_options = self.sql(expression, "limit_options") 1574 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1575 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1576 1577 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1578 percent = " PERCENT" if expression.args.get("percent") else "" 1579 rows = " ROWS" if expression.args.get("rows") else "" 1580 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1581 if not with_ties and rows: 1582 with_ties = " ONLY" 1583 return f"{percent}{rows}{with_ties}" 1584 1585 def filter_sql(self, expression: exp.Filter) -> str: 1586 if self.AGGREGATE_FILTER_SUPPORTED: 1587 this = self.sql(expression, "this") 1588 where = self.sql(expression, "expression").strip() 1589 return f"{this} FILTER({where})" 1590 1591 agg = expression.this 1592 agg_arg = agg.this 1593 cond = expression.expression.this 1594 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1595 return self.sql(agg) 1596 1597 def hint_sql(self, expression: exp.Hint) -> str: 1598 if not self.QUERY_HINTS: 1599 self.unsupported("Hints are not supported") 1600 return "" 1601 1602 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1603 1604 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1605 using = self.sql(expression, "using") 1606 using = f" USING {using}" if using else "" 1607 columns = self.expressions(expression, key="columns", flat=True) 1608 columns = f"({columns})" if columns else "" 1609 partition_by = self.expressions(expression, key="partition_by", flat=True) 1610 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1611 where = self.sql(expression, "where") 1612 include = self.expressions(expression, key="include", flat=True) 1613 if include: 1614 include = f" INCLUDE ({include})" 1615 with_storage = self.expressions(expression, key="with_storage", flat=True) 1616 with_storage = f" WITH ({with_storage})" if with_storage else "" 1617 tablespace = self.sql(expression, "tablespace") 1618 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1619 on = self.sql(expression, "on") 1620 on = f" ON {on}" if on else "" 1621 1622 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1623 1624 def index_sql(self, expression: exp.Index) -> str: 1625 unique = "UNIQUE " if expression.args.get("unique") else "" 1626 primary = "PRIMARY " if expression.args.get("primary") else "" 1627 amp = "AMP " if expression.args.get("amp") else "" 1628 name = self.sql(expression, "this") 1629 name = f"{name} " if name else "" 1630 table = self.sql(expression, "table") 1631 table = f"{self.INDEX_ON} {table}" if table else "" 1632 1633 index = "INDEX " if not table else "" 1634 1635 params = self.sql(expression, "params") 1636 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1637 1638 def identifier_sql(self, expression: exp.Identifier) -> str: 1639 text = expression.name 1640 lower = text.lower() 1641 text = lower if self.normalize and not expression.quoted else text 1642 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1643 if ( 1644 expression.quoted 1645 or self.dialect.can_identify(text, self.identify) 1646 or lower in self.RESERVED_KEYWORDS 1647 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1648 ): 1649 text = f"{self._identifier_start}{text}{self._identifier_end}" 1650 return text 1651 1652 def hex_sql(self, expression: exp.Hex) -> str: 1653 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1654 if self.dialect.HEX_LOWERCASE: 1655 text = self.func("LOWER", text) 1656 1657 return text 1658 1659 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1660 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1661 if not self.dialect.HEX_LOWERCASE: 1662 text = self.func("LOWER", text) 1663 return text 1664 1665 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1666 input_format = self.sql(expression, "input_format") 1667 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1668 output_format = self.sql(expression, "output_format") 1669 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1670 return self.sep().join((input_format, output_format)) 1671 1672 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1673 string = self.sql(exp.Literal.string(expression.name)) 1674 return f"{prefix}{string}" 1675 1676 def partition_sql(self, expression: exp.Partition) -> str: 1677 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1678 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1679 1680 def properties_sql(self, expression: exp.Properties) -> str: 1681 root_properties = [] 1682 with_properties = [] 1683 1684 for p in expression.expressions: 1685 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1686 if p_loc == exp.Properties.Location.POST_WITH: 1687 with_properties.append(p) 1688 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1689 root_properties.append(p) 1690 1691 root_props_ast = exp.Properties(expressions=root_properties) 1692 root_props_ast.parent = expression.parent 1693 1694 with_props_ast = exp.Properties(expressions=with_properties) 1695 with_props_ast.parent = expression.parent 1696 1697 root_props = self.root_properties(root_props_ast) 1698 with_props = self.with_properties(with_props_ast) 1699 1700 if root_props and with_props and not self.pretty: 1701 with_props = " " + with_props 1702 1703 return root_props + with_props 1704 1705 def root_properties(self, properties: exp.Properties) -> str: 1706 if properties.expressions: 1707 return self.expressions(properties, indent=False, sep=" ") 1708 return "" 1709 1710 def properties( 1711 self, 1712 properties: exp.Properties, 1713 prefix: str = "", 1714 sep: str = ", ", 1715 suffix: str = "", 1716 wrapped: bool = True, 1717 ) -> str: 1718 if properties.expressions: 1719 expressions = self.expressions(properties, sep=sep, indent=False) 1720 if expressions: 1721 expressions = self.wrap(expressions) if wrapped else expressions 1722 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1723 return "" 1724 1725 def with_properties(self, properties: exp.Properties) -> str: 1726 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1727 1728 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1729 properties_locs = defaultdict(list) 1730 for p in properties.expressions: 1731 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1732 if p_loc != exp.Properties.Location.UNSUPPORTED: 1733 properties_locs[p_loc].append(p) 1734 else: 1735 self.unsupported(f"Unsupported property {p.key}") 1736 1737 return properties_locs 1738 1739 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1740 if isinstance(expression.this, exp.Dot): 1741 return self.sql(expression, "this") 1742 return f"'{expression.name}'" if string_key else expression.name 1743 1744 def property_sql(self, expression: exp.Property) -> str: 1745 property_cls = expression.__class__ 1746 if property_cls == exp.Property: 1747 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1748 1749 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1750 if not property_name: 1751 self.unsupported(f"Unsupported property {expression.key}") 1752 1753 return f"{property_name}={self.sql(expression, 'this')}" 1754 1755 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1756 if self.SUPPORTS_CREATE_TABLE_LIKE: 1757 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1758 options = f" {options}" if options else "" 1759 1760 like = f"LIKE {self.sql(expression, 'this')}{options}" 1761 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1762 like = f"({like})" 1763 1764 return like 1765 1766 if expression.expressions: 1767 self.unsupported("Transpilation of LIKE property options is unsupported") 1768 1769 select = exp.select("*").from_(expression.this).limit(0) 1770 return f"AS {self.sql(select)}" 1771 1772 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1773 no = "NO " if expression.args.get("no") else "" 1774 protection = " PROTECTION" if expression.args.get("protection") else "" 1775 return f"{no}FALLBACK{protection}" 1776 1777 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1778 no = "NO " if expression.args.get("no") else "" 1779 local = expression.args.get("local") 1780 local = f"{local} " if local else "" 1781 dual = "DUAL " if expression.args.get("dual") else "" 1782 before = "BEFORE " if expression.args.get("before") else "" 1783 after = "AFTER " if expression.args.get("after") else "" 1784 return f"{no}{local}{dual}{before}{after}JOURNAL" 1785 1786 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1787 freespace = self.sql(expression, "this") 1788 percent = " PERCENT" if expression.args.get("percent") else "" 1789 return f"FREESPACE={freespace}{percent}" 1790 1791 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1792 if expression.args.get("default"): 1793 property = "DEFAULT" 1794 elif expression.args.get("on"): 1795 property = "ON" 1796 else: 1797 property = "OFF" 1798 return f"CHECKSUM={property}" 1799 1800 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1801 if expression.args.get("no"): 1802 return "NO MERGEBLOCKRATIO" 1803 if expression.args.get("default"): 1804 return "DEFAULT MERGEBLOCKRATIO" 1805 1806 percent = " PERCENT" if expression.args.get("percent") else "" 1807 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1808 1809 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1810 default = expression.args.get("default") 1811 minimum = expression.args.get("minimum") 1812 maximum = expression.args.get("maximum") 1813 if default or minimum or maximum: 1814 if default: 1815 prop = "DEFAULT" 1816 elif minimum: 1817 prop = "MINIMUM" 1818 else: 1819 prop = "MAXIMUM" 1820 return f"{prop} DATABLOCKSIZE" 1821 units = expression.args.get("units") 1822 units = f" {units}" if units else "" 1823 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1824 1825 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1826 autotemp = expression.args.get("autotemp") 1827 always = expression.args.get("always") 1828 default = expression.args.get("default") 1829 manual = expression.args.get("manual") 1830 never = expression.args.get("never") 1831 1832 if autotemp is not None: 1833 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1834 elif always: 1835 prop = "ALWAYS" 1836 elif default: 1837 prop = "DEFAULT" 1838 elif manual: 1839 prop = "MANUAL" 1840 elif never: 1841 prop = "NEVER" 1842 return f"BLOCKCOMPRESSION={prop}" 1843 1844 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1845 no = expression.args.get("no") 1846 no = " NO" if no else "" 1847 concurrent = expression.args.get("concurrent") 1848 concurrent = " CONCURRENT" if concurrent else "" 1849 target = self.sql(expression, "target") 1850 target = f" {target}" if target else "" 1851 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1852 1853 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1854 if isinstance(expression.this, list): 1855 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1856 if expression.this: 1857 modulus = self.sql(expression, "this") 1858 remainder = self.sql(expression, "expression") 1859 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1860 1861 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1862 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1863 return f"FROM ({from_expressions}) TO ({to_expressions})" 1864 1865 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1866 this = self.sql(expression, "this") 1867 1868 for_values_or_default = expression.expression 1869 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1870 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1871 else: 1872 for_values_or_default = " DEFAULT" 1873 1874 return f"PARTITION OF {this}{for_values_or_default}" 1875 1876 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1877 kind = expression.args.get("kind") 1878 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1879 for_or_in = expression.args.get("for_or_in") 1880 for_or_in = f" {for_or_in}" if for_or_in else "" 1881 lock_type = expression.args.get("lock_type") 1882 override = " OVERRIDE" if expression.args.get("override") else "" 1883 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1884 1885 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1886 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1887 statistics = expression.args.get("statistics") 1888 statistics_sql = "" 1889 if statistics is not None: 1890 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1891 return f"{data_sql}{statistics_sql}" 1892 1893 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1894 this = self.sql(expression, "this") 1895 this = f"HISTORY_TABLE={this}" if this else "" 1896 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1897 data_consistency = ( 1898 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1899 ) 1900 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1901 retention_period = ( 1902 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1903 ) 1904 1905 if this: 1906 on_sql = self.func("ON", this, data_consistency, retention_period) 1907 else: 1908 on_sql = "ON" if expression.args.get("on") else "OFF" 1909 1910 sql = f"SYSTEM_VERSIONING={on_sql}" 1911 1912 return f"WITH({sql})" if expression.args.get("with") else sql 1913 1914 def insert_sql(self, expression: exp.Insert) -> str: 1915 hint = self.sql(expression, "hint") 1916 overwrite = expression.args.get("overwrite") 1917 1918 if isinstance(expression.this, exp.Directory): 1919 this = " OVERWRITE" if overwrite else " INTO" 1920 else: 1921 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1922 1923 stored = self.sql(expression, "stored") 1924 stored = f" {stored}" if stored else "" 1925 alternative = expression.args.get("alternative") 1926 alternative = f" OR {alternative}" if alternative else "" 1927 ignore = " IGNORE" if expression.args.get("ignore") else "" 1928 is_function = expression.args.get("is_function") 1929 if is_function: 1930 this = f"{this} FUNCTION" 1931 this = f"{this} {self.sql(expression, 'this')}" 1932 1933 exists = " IF EXISTS" if expression.args.get("exists") else "" 1934 where = self.sql(expression, "where") 1935 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1936 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1937 on_conflict = self.sql(expression, "conflict") 1938 on_conflict = f" {on_conflict}" if on_conflict else "" 1939 by_name = " BY NAME" if expression.args.get("by_name") else "" 1940 returning = self.sql(expression, "returning") 1941 1942 if self.RETURNING_END: 1943 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1944 else: 1945 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1946 1947 partition_by = self.sql(expression, "partition") 1948 partition_by = f" {partition_by}" if partition_by else "" 1949 settings = self.sql(expression, "settings") 1950 settings = f" {settings}" if settings else "" 1951 1952 source = self.sql(expression, "source") 1953 source = f"TABLE {source}" if source else "" 1954 1955 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1956 return self.prepend_ctes(expression, sql) 1957 1958 def introducer_sql(self, expression: exp.Introducer) -> str: 1959 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1960 1961 def kill_sql(self, expression: exp.Kill) -> str: 1962 kind = self.sql(expression, "kind") 1963 kind = f" {kind}" if kind else "" 1964 this = self.sql(expression, "this") 1965 this = f" {this}" if this else "" 1966 return f"KILL{kind}{this}" 1967 1968 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1969 return expression.name 1970 1971 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1972 return expression.name 1973 1974 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1975 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1976 1977 constraint = self.sql(expression, "constraint") 1978 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1979 1980 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1981 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1982 action = self.sql(expression, "action") 1983 1984 expressions = self.expressions(expression, flat=True) 1985 if expressions: 1986 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1987 expressions = f" {set_keyword}{expressions}" 1988 1989 where = self.sql(expression, "where") 1990 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1991 1992 def returning_sql(self, expression: exp.Returning) -> str: 1993 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1994 1995 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1996 fields = self.sql(expression, "fields") 1997 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1998 escaped = self.sql(expression, "escaped") 1999 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2000 items = self.sql(expression, "collection_items") 2001 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2002 keys = self.sql(expression, "map_keys") 2003 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2004 lines = self.sql(expression, "lines") 2005 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2006 null = self.sql(expression, "null") 2007 null = f" NULL DEFINED AS {null}" if null else "" 2008 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2009 2010 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2011 return f"WITH ({self.expressions(expression, flat=True)})" 2012 2013 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2014 this = f"{self.sql(expression, 'this')} INDEX" 2015 target = self.sql(expression, "target") 2016 target = f" FOR {target}" if target else "" 2017 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2018 2019 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2020 this = self.sql(expression, "this") 2021 kind = self.sql(expression, "kind") 2022 expr = self.sql(expression, "expression") 2023 return f"{this} ({kind} => {expr})" 2024 2025 def table_parts(self, expression: exp.Table) -> str: 2026 return ".".join( 2027 self.sql(part) 2028 for part in ( 2029 expression.args.get("catalog"), 2030 expression.args.get("db"), 2031 expression.args.get("this"), 2032 ) 2033 if part is not None 2034 ) 2035 2036 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2037 table = self.table_parts(expression) 2038 only = "ONLY " if expression.args.get("only") else "" 2039 partition = self.sql(expression, "partition") 2040 partition = f" {partition}" if partition else "" 2041 version = self.sql(expression, "version") 2042 version = f" {version}" if version else "" 2043 alias = self.sql(expression, "alias") 2044 alias = f"{sep}{alias}" if alias else "" 2045 2046 sample = self.sql(expression, "sample") 2047 if self.dialect.ALIAS_POST_TABLESAMPLE: 2048 sample_pre_alias = sample 2049 sample_post_alias = "" 2050 else: 2051 sample_pre_alias = "" 2052 sample_post_alias = sample 2053 2054 hints = self.expressions(expression, key="hints", sep=" ") 2055 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2056 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2057 joins = self.indent( 2058 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2059 ) 2060 laterals = self.expressions(expression, key="laterals", sep="") 2061 2062 file_format = self.sql(expression, "format") 2063 if file_format: 2064 pattern = self.sql(expression, "pattern") 2065 pattern = f", PATTERN => {pattern}" if pattern else "" 2066 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2067 2068 ordinality = expression.args.get("ordinality") or "" 2069 if ordinality: 2070 ordinality = f" WITH ORDINALITY{alias}" 2071 alias = "" 2072 2073 when = self.sql(expression, "when") 2074 if when: 2075 table = f"{table} {when}" 2076 2077 changes = self.sql(expression, "changes") 2078 changes = f" {changes}" if changes else "" 2079 2080 rows_from = self.expressions(expression, key="rows_from") 2081 if rows_from: 2082 table = f"ROWS FROM {self.wrap(rows_from)}" 2083 2084 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2085 2086 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2087 table = self.func("TABLE", expression.this) 2088 alias = self.sql(expression, "alias") 2089 alias = f" AS {alias}" if alias else "" 2090 sample = self.sql(expression, "sample") 2091 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2092 joins = self.indent( 2093 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2094 ) 2095 return f"{table}{alias}{pivots}{sample}{joins}" 2096 2097 def tablesample_sql( 2098 self, 2099 expression: exp.TableSample, 2100 tablesample_keyword: t.Optional[str] = None, 2101 ) -> str: 2102 method = self.sql(expression, "method") 2103 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2104 numerator = self.sql(expression, "bucket_numerator") 2105 denominator = self.sql(expression, "bucket_denominator") 2106 field = self.sql(expression, "bucket_field") 2107 field = f" ON {field}" if field else "" 2108 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2109 seed = self.sql(expression, "seed") 2110 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2111 2112 size = self.sql(expression, "size") 2113 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2114 size = f"{size} ROWS" 2115 2116 percent = self.sql(expression, "percent") 2117 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2118 percent = f"{percent} PERCENT" 2119 2120 expr = f"{bucket}{percent}{size}" 2121 if self.TABLESAMPLE_REQUIRES_PARENS: 2122 expr = f"({expr})" 2123 2124 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2125 2126 def pivot_sql(self, expression: exp.Pivot) -> str: 2127 expressions = self.expressions(expression, flat=True) 2128 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2129 2130 group = self.sql(expression, "group") 2131 2132 if expression.this: 2133 this = self.sql(expression, "this") 2134 if not expressions: 2135 return f"UNPIVOT {this}" 2136 2137 on = f"{self.seg('ON')} {expressions}" 2138 into = self.sql(expression, "into") 2139 into = f"{self.seg('INTO')} {into}" if into else "" 2140 using = self.expressions(expression, key="using", flat=True) 2141 using = f"{self.seg('USING')} {using}" if using else "" 2142 return f"{direction} {this}{on}{into}{using}{group}" 2143 2144 alias = self.sql(expression, "alias") 2145 alias = f" AS {alias}" if alias else "" 2146 2147 fields = self.expressions( 2148 expression, 2149 "fields", 2150 sep=" ", 2151 dynamic=True, 2152 new_line=True, 2153 skip_first=True, 2154 skip_last=True, 2155 ) 2156 2157 include_nulls = expression.args.get("include_nulls") 2158 if include_nulls is not None: 2159 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2160 else: 2161 nulls = "" 2162 2163 default_on_null = self.sql(expression, "default_on_null") 2164 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2165 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2166 2167 def version_sql(self, expression: exp.Version) -> str: 2168 this = f"FOR {expression.name}" 2169 kind = expression.text("kind") 2170 expr = self.sql(expression, "expression") 2171 return f"{this} {kind} {expr}" 2172 2173 def tuple_sql(self, expression: exp.Tuple) -> str: 2174 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2175 2176 def update_sql(self, expression: exp.Update) -> str: 2177 this = self.sql(expression, "this") 2178 set_sql = self.expressions(expression, flat=True) 2179 from_sql = self.sql(expression, "from") 2180 where_sql = self.sql(expression, "where") 2181 returning = self.sql(expression, "returning") 2182 order = self.sql(expression, "order") 2183 limit = self.sql(expression, "limit") 2184 if self.RETURNING_END: 2185 expression_sql = f"{from_sql}{where_sql}{returning}" 2186 else: 2187 expression_sql = f"{returning}{from_sql}{where_sql}" 2188 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2189 return self.prepend_ctes(expression, sql) 2190 2191 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2192 values_as_table = values_as_table and self.VALUES_AS_TABLE 2193 2194 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2195 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2196 args = self.expressions(expression) 2197 alias = self.sql(expression, "alias") 2198 values = f"VALUES{self.seg('')}{args}" 2199 values = ( 2200 f"({values})" 2201 if self.WRAP_DERIVED_VALUES 2202 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2203 else values 2204 ) 2205 return f"{values} AS {alias}" if alias else values 2206 2207 # Converts `VALUES...` expression into a series of select unions. 2208 alias_node = expression.args.get("alias") 2209 column_names = alias_node and alias_node.columns 2210 2211 selects: t.List[exp.Query] = [] 2212 2213 for i, tup in enumerate(expression.expressions): 2214 row = tup.expressions 2215 2216 if i == 0 and column_names: 2217 row = [ 2218 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2219 ] 2220 2221 selects.append(exp.Select(expressions=row)) 2222 2223 if self.pretty: 2224 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2225 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2226 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2227 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2228 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2229 2230 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2231 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2232 return f"({unions}){alias}" 2233 2234 def var_sql(self, expression: exp.Var) -> str: 2235 return self.sql(expression, "this") 2236 2237 @unsupported_args("expressions") 2238 def into_sql(self, expression: exp.Into) -> str: 2239 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2240 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2241 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2242 2243 def from_sql(self, expression: exp.From) -> str: 2244 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2245 2246 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2247 grouping_sets = self.expressions(expression, indent=False) 2248 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2249 2250 def rollup_sql(self, expression: exp.Rollup) -> str: 2251 expressions = self.expressions(expression, indent=False) 2252 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2253 2254 def cube_sql(self, expression: exp.Cube) -> str: 2255 expressions = self.expressions(expression, indent=False) 2256 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2257 2258 def group_sql(self, expression: exp.Group) -> str: 2259 group_by_all = expression.args.get("all") 2260 if group_by_all is True: 2261 modifier = " ALL" 2262 elif group_by_all is False: 2263 modifier = " DISTINCT" 2264 else: 2265 modifier = "" 2266 2267 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2268 2269 grouping_sets = self.expressions(expression, key="grouping_sets") 2270 cube = self.expressions(expression, key="cube") 2271 rollup = self.expressions(expression, key="rollup") 2272 2273 groupings = csv( 2274 self.seg(grouping_sets) if grouping_sets else "", 2275 self.seg(cube) if cube else "", 2276 self.seg(rollup) if rollup else "", 2277 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2278 sep=self.GROUPINGS_SEP, 2279 ) 2280 2281 if ( 2282 expression.expressions 2283 and groupings 2284 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2285 ): 2286 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2287 2288 return f"{group_by}{groupings}" 2289 2290 def having_sql(self, expression: exp.Having) -> str: 2291 this = self.indent(self.sql(expression, "this")) 2292 return f"{self.seg('HAVING')}{self.sep()}{this}" 2293 2294 def connect_sql(self, expression: exp.Connect) -> str: 2295 start = self.sql(expression, "start") 2296 start = self.seg(f"START WITH {start}") if start else "" 2297 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2298 connect = self.sql(expression, "connect") 2299 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2300 return start + connect 2301 2302 def prior_sql(self, expression: exp.Prior) -> str: 2303 return f"PRIOR {self.sql(expression, 'this')}" 2304 2305 def join_sql(self, expression: exp.Join) -> str: 2306 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2307 side = None 2308 else: 2309 side = expression.side 2310 2311 op_sql = " ".join( 2312 op 2313 for op in ( 2314 expression.method, 2315 "GLOBAL" if expression.args.get("global") else None, 2316 side, 2317 expression.kind, 2318 expression.hint if self.JOIN_HINTS else None, 2319 ) 2320 if op 2321 ) 2322 match_cond = self.sql(expression, "match_condition") 2323 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2324 on_sql = self.sql(expression, "on") 2325 using = expression.args.get("using") 2326 2327 if not on_sql and using: 2328 on_sql = csv(*(self.sql(column) for column in using)) 2329 2330 this = expression.this 2331 this_sql = self.sql(this) 2332 2333 exprs = self.expressions(expression) 2334 if exprs: 2335 this_sql = f"{this_sql},{self.seg(exprs)}" 2336 2337 if on_sql: 2338 on_sql = self.indent(on_sql, skip_first=True) 2339 space = self.seg(" " * self.pad) if self.pretty else " " 2340 if using: 2341 on_sql = f"{space}USING ({on_sql})" 2342 else: 2343 on_sql = f"{space}ON {on_sql}" 2344 elif not op_sql: 2345 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2346 return f" {this_sql}" 2347 2348 return f", {this_sql}" 2349 2350 if op_sql != "STRAIGHT_JOIN": 2351 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2352 2353 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2354 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2355 2356 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2357 args = self.expressions(expression, flat=True) 2358 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2359 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2360 2361 def lateral_op(self, expression: exp.Lateral) -> str: 2362 cross_apply = expression.args.get("cross_apply") 2363 2364 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2365 if cross_apply is True: 2366 op = "INNER JOIN " 2367 elif cross_apply is False: 2368 op = "LEFT JOIN " 2369 else: 2370 op = "" 2371 2372 return f"{op}LATERAL" 2373 2374 def lateral_sql(self, expression: exp.Lateral) -> str: 2375 this = self.sql(expression, "this") 2376 2377 if expression.args.get("view"): 2378 alias = expression.args["alias"] 2379 columns = self.expressions(alias, key="columns", flat=True) 2380 table = f" {alias.name}" if alias.name else "" 2381 columns = f" AS {columns}" if columns else "" 2382 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2383 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2384 2385 alias = self.sql(expression, "alias") 2386 alias = f" AS {alias}" if alias else "" 2387 2388 ordinality = expression.args.get("ordinality") or "" 2389 if ordinality: 2390 ordinality = f" WITH ORDINALITY{alias}" 2391 alias = "" 2392 2393 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2394 2395 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2396 this = self.sql(expression, "this") 2397 2398 args = [ 2399 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2400 for e in (expression.args.get(k) for k in ("offset", "expression")) 2401 if e 2402 ] 2403 2404 args_sql = ", ".join(self.sql(e) for e in args) 2405 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2406 expressions = self.expressions(expression, flat=True) 2407 limit_options = self.sql(expression, "limit_options") 2408 expressions = f" BY {expressions}" if expressions else "" 2409 2410 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2411 2412 def offset_sql(self, expression: exp.Offset) -> str: 2413 this = self.sql(expression, "this") 2414 value = expression.expression 2415 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2416 expressions = self.expressions(expression, flat=True) 2417 expressions = f" BY {expressions}" if expressions else "" 2418 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2419 2420 def setitem_sql(self, expression: exp.SetItem) -> str: 2421 kind = self.sql(expression, "kind") 2422 kind = f"{kind} " if kind else "" 2423 this = self.sql(expression, "this") 2424 expressions = self.expressions(expression) 2425 collate = self.sql(expression, "collate") 2426 collate = f" COLLATE {collate}" if collate else "" 2427 global_ = "GLOBAL " if expression.args.get("global") else "" 2428 return f"{global_}{kind}{this}{expressions}{collate}" 2429 2430 def set_sql(self, expression: exp.Set) -> str: 2431 expressions = f" {self.expressions(expression, flat=True)}" 2432 tag = " TAG" if expression.args.get("tag") else "" 2433 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2434 2435 def queryband_sql(self, expression: exp.QueryBand) -> str: 2436 this = self.sql(expression, "this") 2437 update = " UPDATE" if expression.args.get("update") else "" 2438 scope = self.sql(expression, "scope") 2439 scope = f" FOR {scope}" if scope else "" 2440 2441 return f"QUERY_BAND = {this}{update}{scope}" 2442 2443 def pragma_sql(self, expression: exp.Pragma) -> str: 2444 return f"PRAGMA {self.sql(expression, 'this')}" 2445 2446 def lock_sql(self, expression: exp.Lock) -> str: 2447 if not self.LOCKING_READS_SUPPORTED: 2448 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2449 return "" 2450 2451 update = expression.args["update"] 2452 key = expression.args.get("key") 2453 if update: 2454 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2455 else: 2456 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2457 expressions = self.expressions(expression, flat=True) 2458 expressions = f" OF {expressions}" if expressions else "" 2459 wait = expression.args.get("wait") 2460 2461 if wait is not None: 2462 if isinstance(wait, exp.Literal): 2463 wait = f" WAIT {self.sql(wait)}" 2464 else: 2465 wait = " NOWAIT" if wait else " SKIP LOCKED" 2466 2467 return f"{lock_type}{expressions}{wait or ''}" 2468 2469 def literal_sql(self, expression: exp.Literal) -> str: 2470 text = expression.this or "" 2471 if expression.is_string: 2472 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2473 return text 2474 2475 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2476 if self.dialect.ESCAPED_SEQUENCES: 2477 to_escaped = self.dialect.ESCAPED_SEQUENCES 2478 text = "".join( 2479 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2480 ) 2481 2482 return self._replace_line_breaks(text).replace( 2483 self.dialect.QUOTE_END, self._escaped_quote_end 2484 ) 2485 2486 def loaddata_sql(self, expression: exp.LoadData) -> str: 2487 local = " LOCAL" if expression.args.get("local") else "" 2488 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2489 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2490 this = f" INTO TABLE {self.sql(expression, 'this')}" 2491 partition = self.sql(expression, "partition") 2492 partition = f" {partition}" if partition else "" 2493 input_format = self.sql(expression, "input_format") 2494 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2495 serde = self.sql(expression, "serde") 2496 serde = f" SERDE {serde}" if serde else "" 2497 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2498 2499 def null_sql(self, *_) -> str: 2500 return "NULL" 2501 2502 def boolean_sql(self, expression: exp.Boolean) -> str: 2503 return "TRUE" if expression.this else "FALSE" 2504 2505 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2506 this = self.sql(expression, "this") 2507 this = f"{this} " if this else this 2508 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2509 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2510 2511 def withfill_sql(self, expression: exp.WithFill) -> str: 2512 from_sql = self.sql(expression, "from") 2513 from_sql = f" FROM {from_sql}" if from_sql else "" 2514 to_sql = self.sql(expression, "to") 2515 to_sql = f" TO {to_sql}" if to_sql else "" 2516 step_sql = self.sql(expression, "step") 2517 step_sql = f" STEP {step_sql}" if step_sql else "" 2518 interpolated_values = [ 2519 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2520 if isinstance(e, exp.Alias) 2521 else self.sql(e, "this") 2522 for e in expression.args.get("interpolate") or [] 2523 ] 2524 interpolate = ( 2525 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2526 ) 2527 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2528 2529 def cluster_sql(self, expression: exp.Cluster) -> str: 2530 return self.op_expressions("CLUSTER BY", expression) 2531 2532 def distribute_sql(self, expression: exp.Distribute) -> str: 2533 return self.op_expressions("DISTRIBUTE BY", expression) 2534 2535 def sort_sql(self, expression: exp.Sort) -> str: 2536 return self.op_expressions("SORT BY", expression) 2537 2538 def ordered_sql(self, expression: exp.Ordered) -> str: 2539 desc = expression.args.get("desc") 2540 asc = not desc 2541 2542 nulls_first = expression.args.get("nulls_first") 2543 nulls_last = not nulls_first 2544 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2545 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2546 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2547 2548 this = self.sql(expression, "this") 2549 2550 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2551 nulls_sort_change = "" 2552 if nulls_first and ( 2553 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2554 ): 2555 nulls_sort_change = " NULLS FIRST" 2556 elif ( 2557 nulls_last 2558 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2559 and not nulls_are_last 2560 ): 2561 nulls_sort_change = " NULLS LAST" 2562 2563 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2564 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2565 window = expression.find_ancestor(exp.Window, exp.Select) 2566 if isinstance(window, exp.Window) and window.args.get("spec"): 2567 self.unsupported( 2568 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2569 ) 2570 nulls_sort_change = "" 2571 elif self.NULL_ORDERING_SUPPORTED is False and ( 2572 (asc and nulls_sort_change == " NULLS LAST") 2573 or (desc and nulls_sort_change == " NULLS FIRST") 2574 ): 2575 # BigQuery does not allow these ordering/nulls combinations when used under 2576 # an aggregation func or under a window containing one 2577 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2578 2579 if isinstance(ancestor, exp.Window): 2580 ancestor = ancestor.this 2581 if isinstance(ancestor, exp.AggFunc): 2582 self.unsupported( 2583 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2584 ) 2585 nulls_sort_change = "" 2586 elif self.NULL_ORDERING_SUPPORTED is None: 2587 if expression.this.is_int: 2588 self.unsupported( 2589 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2590 ) 2591 elif not isinstance(expression.this, exp.Rand): 2592 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2593 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2594 nulls_sort_change = "" 2595 2596 with_fill = self.sql(expression, "with_fill") 2597 with_fill = f" {with_fill}" if with_fill else "" 2598 2599 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2600 2601 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2602 window_frame = self.sql(expression, "window_frame") 2603 window_frame = f"{window_frame} " if window_frame else "" 2604 2605 this = self.sql(expression, "this") 2606 2607 return f"{window_frame}{this}" 2608 2609 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2610 partition = self.partition_by_sql(expression) 2611 order = self.sql(expression, "order") 2612 measures = self.expressions(expression, key="measures") 2613 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2614 rows = self.sql(expression, "rows") 2615 rows = self.seg(rows) if rows else "" 2616 after = self.sql(expression, "after") 2617 after = self.seg(after) if after else "" 2618 pattern = self.sql(expression, "pattern") 2619 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2620 definition_sqls = [ 2621 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2622 for definition in expression.args.get("define", []) 2623 ] 2624 definitions = self.expressions(sqls=definition_sqls) 2625 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2626 body = "".join( 2627 ( 2628 partition, 2629 order, 2630 measures, 2631 rows, 2632 after, 2633 pattern, 2634 define, 2635 ) 2636 ) 2637 alias = self.sql(expression, "alias") 2638 alias = f" {alias}" if alias else "" 2639 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2640 2641 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2642 limit = expression.args.get("limit") 2643 2644 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2645 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2646 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2647 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2648 2649 return csv( 2650 *sqls, 2651 *[self.sql(join) for join in expression.args.get("joins") or []], 2652 self.sql(expression, "match"), 2653 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2654 self.sql(expression, "prewhere"), 2655 self.sql(expression, "where"), 2656 self.sql(expression, "connect"), 2657 self.sql(expression, "group"), 2658 self.sql(expression, "having"), 2659 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2660 self.sql(expression, "order"), 2661 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2662 *self.after_limit_modifiers(expression), 2663 self.options_modifier(expression), 2664 self.for_modifiers(expression), 2665 sep="", 2666 ) 2667 2668 def options_modifier(self, expression: exp.Expression) -> str: 2669 options = self.expressions(expression, key="options") 2670 return f" {options}" if options else "" 2671 2672 def for_modifiers(self, expression: exp.Expression) -> str: 2673 for_modifiers = self.expressions(expression, key="for") 2674 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2675 2676 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2677 self.unsupported("Unsupported query option.") 2678 return "" 2679 2680 def offset_limit_modifiers( 2681 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2682 ) -> t.List[str]: 2683 return [ 2684 self.sql(expression, "offset") if fetch else self.sql(limit), 2685 self.sql(limit) if fetch else self.sql(expression, "offset"), 2686 ] 2687 2688 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2689 locks = self.expressions(expression, key="locks", sep=" ") 2690 locks = f" {locks}" if locks else "" 2691 return [locks, self.sql(expression, "sample")] 2692 2693 def select_sql(self, expression: exp.Select) -> str: 2694 into = expression.args.get("into") 2695 if not self.SUPPORTS_SELECT_INTO and into: 2696 into.pop() 2697 2698 hint = self.sql(expression, "hint") 2699 distinct = self.sql(expression, "distinct") 2700 distinct = f" {distinct}" if distinct else "" 2701 kind = self.sql(expression, "kind") 2702 2703 limit = expression.args.get("limit") 2704 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2705 top = self.limit_sql(limit, top=True) 2706 limit.pop() 2707 else: 2708 top = "" 2709 2710 expressions = self.expressions(expression) 2711 2712 if kind: 2713 if kind in self.SELECT_KINDS: 2714 kind = f" AS {kind}" 2715 else: 2716 if kind == "STRUCT": 2717 expressions = self.expressions( 2718 sqls=[ 2719 self.sql( 2720 exp.Struct( 2721 expressions=[ 2722 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2723 if isinstance(e, exp.Alias) 2724 else e 2725 for e in expression.expressions 2726 ] 2727 ) 2728 ) 2729 ] 2730 ) 2731 kind = "" 2732 2733 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2734 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2735 2736 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2737 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2738 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2739 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2740 sql = self.query_modifiers( 2741 expression, 2742 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2743 self.sql(expression, "into", comment=False), 2744 self.sql(expression, "from", comment=False), 2745 ) 2746 2747 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2748 if expression.args.get("with"): 2749 sql = self.maybe_comment(sql, expression) 2750 expression.pop_comments() 2751 2752 sql = self.prepend_ctes(expression, sql) 2753 2754 if not self.SUPPORTS_SELECT_INTO and into: 2755 if into.args.get("temporary"): 2756 table_kind = " TEMPORARY" 2757 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2758 table_kind = " UNLOGGED" 2759 else: 2760 table_kind = "" 2761 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2762 2763 return sql 2764 2765 def schema_sql(self, expression: exp.Schema) -> str: 2766 this = self.sql(expression, "this") 2767 sql = self.schema_columns_sql(expression) 2768 return f"{this} {sql}" if this and sql else this or sql 2769 2770 def schema_columns_sql(self, expression: exp.Schema) -> str: 2771 if expression.expressions: 2772 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2773 return "" 2774 2775 def star_sql(self, expression: exp.Star) -> str: 2776 except_ = self.expressions(expression, key="except", flat=True) 2777 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2778 replace = self.expressions(expression, key="replace", flat=True) 2779 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2780 rename = self.expressions(expression, key="rename", flat=True) 2781 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2782 return f"*{except_}{replace}{rename}" 2783 2784 def parameter_sql(self, expression: exp.Parameter) -> str: 2785 this = self.sql(expression, "this") 2786 return f"{self.PARAMETER_TOKEN}{this}" 2787 2788 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2789 this = self.sql(expression, "this") 2790 kind = expression.text("kind") 2791 if kind: 2792 kind = f"{kind}." 2793 return f"@@{kind}{this}" 2794 2795 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2796 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2797 2798 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2799 alias = self.sql(expression, "alias") 2800 alias = f"{sep}{alias}" if alias else "" 2801 sample = self.sql(expression, "sample") 2802 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2803 alias = f"{sample}{alias}" 2804 2805 # Set to None so it's not generated again by self.query_modifiers() 2806 expression.set("sample", None) 2807 2808 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2809 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2810 return self.prepend_ctes(expression, sql) 2811 2812 def qualify_sql(self, expression: exp.Qualify) -> str: 2813 this = self.indent(self.sql(expression, "this")) 2814 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2815 2816 def unnest_sql(self, expression: exp.Unnest) -> str: 2817 args = self.expressions(expression, flat=True) 2818 2819 alias = expression.args.get("alias") 2820 offset = expression.args.get("offset") 2821 2822 if self.UNNEST_WITH_ORDINALITY: 2823 if alias and isinstance(offset, exp.Expression): 2824 alias.append("columns", offset) 2825 2826 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2827 columns = alias.columns 2828 alias = self.sql(columns[0]) if columns else "" 2829 else: 2830 alias = self.sql(alias) 2831 2832 alias = f" AS {alias}" if alias else alias 2833 if self.UNNEST_WITH_ORDINALITY: 2834 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2835 else: 2836 if isinstance(offset, exp.Expression): 2837 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2838 elif offset: 2839 suffix = f"{alias} WITH OFFSET" 2840 else: 2841 suffix = alias 2842 2843 return f"UNNEST({args}){suffix}" 2844 2845 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2846 return "" 2847 2848 def where_sql(self, expression: exp.Where) -> str: 2849 this = self.indent(self.sql(expression, "this")) 2850 return f"{self.seg('WHERE')}{self.sep()}{this}" 2851 2852 def window_sql(self, expression: exp.Window) -> str: 2853 this = self.sql(expression, "this") 2854 partition = self.partition_by_sql(expression) 2855 order = expression.args.get("order") 2856 order = self.order_sql(order, flat=True) if order else "" 2857 spec = self.sql(expression, "spec") 2858 alias = self.sql(expression, "alias") 2859 over = self.sql(expression, "over") or "OVER" 2860 2861 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2862 2863 first = expression.args.get("first") 2864 if first is None: 2865 first = "" 2866 else: 2867 first = "FIRST" if first else "LAST" 2868 2869 if not partition and not order and not spec and alias: 2870 return f"{this} {alias}" 2871 2872 args = self.format_args( 2873 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2874 ) 2875 return f"{this} ({args})" 2876 2877 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2878 partition = self.expressions(expression, key="partition_by", flat=True) 2879 return f"PARTITION BY {partition}" if partition else "" 2880 2881 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2882 kind = self.sql(expression, "kind") 2883 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2884 end = ( 2885 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2886 or "CURRENT ROW" 2887 ) 2888 2889 window_spec = f"{kind} BETWEEN {start} AND {end}" 2890 2891 exclude = self.sql(expression, "exclude") 2892 if exclude: 2893 if self.SUPPORTS_WINDOW_EXCLUDE: 2894 window_spec += f" EXCLUDE {exclude}" 2895 else: 2896 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2897 2898 return window_spec 2899 2900 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2901 this = self.sql(expression, "this") 2902 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2903 return f"{this} WITHIN GROUP ({expression_sql})" 2904 2905 def between_sql(self, expression: exp.Between) -> str: 2906 this = self.sql(expression, "this") 2907 low = self.sql(expression, "low") 2908 high = self.sql(expression, "high") 2909 symmetric = expression.args.get("symmetric") 2910 2911 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2912 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2913 2914 flag = ( 2915 " SYMMETRIC" 2916 if symmetric 2917 else " ASYMMETRIC" 2918 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2919 else "" # silently drop ASYMMETRIC – semantics identical 2920 ) 2921 return f"{this} BETWEEN{flag} {low} AND {high}" 2922 2923 def bracket_offset_expressions( 2924 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2925 ) -> t.List[exp.Expression]: 2926 return apply_index_offset( 2927 expression.this, 2928 expression.expressions, 2929 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2930 dialect=self.dialect, 2931 ) 2932 2933 def bracket_sql(self, expression: exp.Bracket) -> str: 2934 expressions = self.bracket_offset_expressions(expression) 2935 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2936 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2937 2938 def all_sql(self, expression: exp.All) -> str: 2939 this = self.sql(expression, "this") 2940 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2941 this = self.wrap(this) 2942 return f"ALL {this}" 2943 2944 def any_sql(self, expression: exp.Any) -> str: 2945 this = self.sql(expression, "this") 2946 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2947 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2948 this = self.wrap(this) 2949 return f"ANY{this}" 2950 return f"ANY {this}" 2951 2952 def exists_sql(self, expression: exp.Exists) -> str: 2953 return f"EXISTS{self.wrap(expression)}" 2954 2955 def case_sql(self, expression: exp.Case) -> str: 2956 this = self.sql(expression, "this") 2957 statements = [f"CASE {this}" if this else "CASE"] 2958 2959 for e in expression.args["ifs"]: 2960 statements.append(f"WHEN {self.sql(e, 'this')}") 2961 statements.append(f"THEN {self.sql(e, 'true')}") 2962 2963 default = self.sql(expression, "default") 2964 2965 if default: 2966 statements.append(f"ELSE {default}") 2967 2968 statements.append("END") 2969 2970 if self.pretty and self.too_wide(statements): 2971 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2972 2973 return " ".join(statements) 2974 2975 def constraint_sql(self, expression: exp.Constraint) -> str: 2976 this = self.sql(expression, "this") 2977 expressions = self.expressions(expression, flat=True) 2978 return f"CONSTRAINT {this} {expressions}" 2979 2980 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2981 order = expression.args.get("order") 2982 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2983 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2984 2985 def extract_sql(self, expression: exp.Extract) -> str: 2986 from sqlglot.dialects.dialect import map_date_part 2987 2988 this = ( 2989 map_date_part(expression.this, self.dialect) 2990 if self.NORMALIZE_EXTRACT_DATE_PARTS 2991 else expression.this 2992 ) 2993 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2994 expression_sql = self.sql(expression, "expression") 2995 2996 return f"EXTRACT({this_sql} FROM {expression_sql})" 2997 2998 def trim_sql(self, expression: exp.Trim) -> str: 2999 trim_type = self.sql(expression, "position") 3000 3001 if trim_type == "LEADING": 3002 func_name = "LTRIM" 3003 elif trim_type == "TRAILING": 3004 func_name = "RTRIM" 3005 else: 3006 func_name = "TRIM" 3007 3008 return self.func(func_name, expression.this, expression.expression) 3009 3010 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3011 args = expression.expressions 3012 if isinstance(expression, exp.ConcatWs): 3013 args = args[1:] # Skip the delimiter 3014 3015 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3016 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3017 3018 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3019 3020 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3021 if not e.type: 3022 from sqlglot.optimizer.annotate_types import annotate_types 3023 3024 e = annotate_types(e, dialect=self.dialect) 3025 3026 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3027 return e 3028 3029 return exp.func("coalesce", e, exp.Literal.string("")) 3030 3031 args = [_wrap_with_coalesce(e) for e in args] 3032 3033 return args 3034 3035 def concat_sql(self, expression: exp.Concat) -> str: 3036 expressions = self.convert_concat_args(expression) 3037 3038 # Some dialects don't allow a single-argument CONCAT call 3039 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3040 return self.sql(expressions[0]) 3041 3042 return self.func("CONCAT", *expressions) 3043 3044 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3045 return self.func( 3046 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3047 ) 3048 3049 def check_sql(self, expression: exp.Check) -> str: 3050 this = self.sql(expression, key="this") 3051 return f"CHECK ({this})" 3052 3053 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3054 expressions = self.expressions(expression, flat=True) 3055 expressions = f" ({expressions})" if expressions else "" 3056 reference = self.sql(expression, "reference") 3057 reference = f" {reference}" if reference else "" 3058 delete = self.sql(expression, "delete") 3059 delete = f" ON DELETE {delete}" if delete else "" 3060 update = self.sql(expression, "update") 3061 update = f" ON UPDATE {update}" if update else "" 3062 options = self.expressions(expression, key="options", flat=True, sep=" ") 3063 options = f" {options}" if options else "" 3064 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3065 3066 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3067 expressions = self.expressions(expression, flat=True) 3068 include = self.sql(expression, "include") 3069 options = self.expressions(expression, key="options", flat=True, sep=" ") 3070 options = f" {options}" if options else "" 3071 return f"PRIMARY KEY ({expressions}){include}{options}" 3072 3073 def if_sql(self, expression: exp.If) -> str: 3074 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3075 3076 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3077 if self.MATCH_AGAINST_TABLE_PREFIX: 3078 expressions = [] 3079 for expr in expression.expressions: 3080 if isinstance(expr, exp.Table): 3081 expressions.append(f"TABLE {self.sql(expr)}") 3082 else: 3083 expressions.append(expr) 3084 else: 3085 expressions = expression.expressions 3086 3087 modifier = expression.args.get("modifier") 3088 modifier = f" {modifier}" if modifier else "" 3089 return ( 3090 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3091 ) 3092 3093 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3094 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3095 3096 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3097 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3098 3099 if expression.args.get("escape"): 3100 path = self.escape_str(path) 3101 3102 if self.QUOTE_JSON_PATH: 3103 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3104 3105 return path 3106 3107 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3108 if isinstance(expression, exp.JSONPathPart): 3109 transform = self.TRANSFORMS.get(expression.__class__) 3110 if not callable(transform): 3111 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3112 return "" 3113 3114 return transform(self, expression) 3115 3116 if isinstance(expression, int): 3117 return str(expression) 3118 3119 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3120 escaped = expression.replace("'", "\\'") 3121 escaped = f"\\'{expression}\\'" 3122 else: 3123 escaped = expression.replace('"', '\\"') 3124 escaped = f'"{escaped}"' 3125 3126 return escaped 3127 3128 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3129 return f"{self.sql(expression, 'this')} FORMAT JSON" 3130 3131 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3132 # Output the Teradata column FORMAT override. 3133 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3134 this = self.sql(expression, "this") 3135 fmt = self.sql(expression, "format") 3136 return f"{this} (FORMAT {fmt})" 3137 3138 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3139 null_handling = expression.args.get("null_handling") 3140 null_handling = f" {null_handling}" if null_handling else "" 3141 3142 unique_keys = expression.args.get("unique_keys") 3143 if unique_keys is not None: 3144 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3145 else: 3146 unique_keys = "" 3147 3148 return_type = self.sql(expression, "return_type") 3149 return_type = f" RETURNING {return_type}" if return_type else "" 3150 encoding = self.sql(expression, "encoding") 3151 encoding = f" ENCODING {encoding}" if encoding else "" 3152 3153 return self.func( 3154 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3155 *expression.expressions, 3156 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3157 ) 3158 3159 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3160 return self.jsonobject_sql(expression) 3161 3162 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3163 null_handling = expression.args.get("null_handling") 3164 null_handling = f" {null_handling}" if null_handling else "" 3165 return_type = self.sql(expression, "return_type") 3166 return_type = f" RETURNING {return_type}" if return_type else "" 3167 strict = " STRICT" if expression.args.get("strict") else "" 3168 return self.func( 3169 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3170 ) 3171 3172 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3173 this = self.sql(expression, "this") 3174 order = self.sql(expression, "order") 3175 null_handling = expression.args.get("null_handling") 3176 null_handling = f" {null_handling}" if null_handling else "" 3177 return_type = self.sql(expression, "return_type") 3178 return_type = f" RETURNING {return_type}" if return_type else "" 3179 strict = " STRICT" if expression.args.get("strict") else "" 3180 return self.func( 3181 "JSON_ARRAYAGG", 3182 this, 3183 suffix=f"{order}{null_handling}{return_type}{strict})", 3184 ) 3185 3186 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3187 path = self.sql(expression, "path") 3188 path = f" PATH {path}" if path else "" 3189 nested_schema = self.sql(expression, "nested_schema") 3190 3191 if nested_schema: 3192 return f"NESTED{path} {nested_schema}" 3193 3194 this = self.sql(expression, "this") 3195 kind = self.sql(expression, "kind") 3196 kind = f" {kind}" if kind else "" 3197 return f"{this}{kind}{path}" 3198 3199 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3200 return self.func("COLUMNS", *expression.expressions) 3201 3202 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3203 this = self.sql(expression, "this") 3204 path = self.sql(expression, "path") 3205 path = f", {path}" if path else "" 3206 error_handling = expression.args.get("error_handling") 3207 error_handling = f" {error_handling}" if error_handling else "" 3208 empty_handling = expression.args.get("empty_handling") 3209 empty_handling = f" {empty_handling}" if empty_handling else "" 3210 schema = self.sql(expression, "schema") 3211 return self.func( 3212 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3213 ) 3214 3215 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3216 this = self.sql(expression, "this") 3217 kind = self.sql(expression, "kind") 3218 path = self.sql(expression, "path") 3219 path = f" {path}" if path else "" 3220 as_json = " AS JSON" if expression.args.get("as_json") else "" 3221 return f"{this} {kind}{path}{as_json}" 3222 3223 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3224 this = self.sql(expression, "this") 3225 path = self.sql(expression, "path") 3226 path = f", {path}" if path else "" 3227 expressions = self.expressions(expression) 3228 with_ = ( 3229 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3230 if expressions 3231 else "" 3232 ) 3233 return f"OPENJSON({this}{path}){with_}" 3234 3235 def in_sql(self, expression: exp.In) -> str: 3236 query = expression.args.get("query") 3237 unnest = expression.args.get("unnest") 3238 field = expression.args.get("field") 3239 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3240 3241 if query: 3242 in_sql = self.sql(query) 3243 elif unnest: 3244 in_sql = self.in_unnest_op(unnest) 3245 elif field: 3246 in_sql = self.sql(field) 3247 else: 3248 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3249 3250 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3251 3252 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3253 return f"(SELECT {self.sql(unnest)})" 3254 3255 def interval_sql(self, expression: exp.Interval) -> str: 3256 unit = self.sql(expression, "unit") 3257 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3258 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3259 unit = f" {unit}" if unit else "" 3260 3261 if self.SINGLE_STRING_INTERVAL: 3262 this = expression.this.name if expression.this else "" 3263 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3264 3265 this = self.sql(expression, "this") 3266 if this: 3267 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3268 this = f" {this}" if unwrapped else f" ({this})" 3269 3270 return f"INTERVAL{this}{unit}" 3271 3272 def return_sql(self, expression: exp.Return) -> str: 3273 return f"RETURN {self.sql(expression, 'this')}" 3274 3275 def reference_sql(self, expression: exp.Reference) -> str: 3276 this = self.sql(expression, "this") 3277 expressions = self.expressions(expression, flat=True) 3278 expressions = f"({expressions})" if expressions else "" 3279 options = self.expressions(expression, key="options", flat=True, sep=" ") 3280 options = f" {options}" if options else "" 3281 return f"REFERENCES {this}{expressions}{options}" 3282 3283 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3284 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3285 parent = expression.parent 3286 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3287 return self.func( 3288 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3289 ) 3290 3291 def paren_sql(self, expression: exp.Paren) -> str: 3292 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3293 return f"({sql}{self.seg(')', sep='')}" 3294 3295 def neg_sql(self, expression: exp.Neg) -> str: 3296 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3297 this_sql = self.sql(expression, "this") 3298 sep = " " if this_sql[0] == "-" else "" 3299 return f"-{sep}{this_sql}" 3300 3301 def not_sql(self, expression: exp.Not) -> str: 3302 return f"NOT {self.sql(expression, 'this')}" 3303 3304 def alias_sql(self, expression: exp.Alias) -> str: 3305 alias = self.sql(expression, "alias") 3306 alias = f" AS {alias}" if alias else "" 3307 return f"{self.sql(expression, 'this')}{alias}" 3308 3309 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3310 alias = expression.args["alias"] 3311 3312 parent = expression.parent 3313 pivot = parent and parent.parent 3314 3315 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3316 identifier_alias = isinstance(alias, exp.Identifier) 3317 literal_alias = isinstance(alias, exp.Literal) 3318 3319 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3320 alias.replace(exp.Literal.string(alias.output_name)) 3321 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3322 alias.replace(exp.to_identifier(alias.output_name)) 3323 3324 return self.alias_sql(expression) 3325 3326 def aliases_sql(self, expression: exp.Aliases) -> str: 3327 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3328 3329 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3330 this = self.sql(expression, "this") 3331 index = self.sql(expression, "expression") 3332 return f"{this} AT {index}" 3333 3334 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3335 this = self.sql(expression, "this") 3336 zone = self.sql(expression, "zone") 3337 return f"{this} AT TIME ZONE {zone}" 3338 3339 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3340 this = self.sql(expression, "this") 3341 zone = self.sql(expression, "zone") 3342 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3343 3344 def add_sql(self, expression: exp.Add) -> str: 3345 return self.binary(expression, "+") 3346 3347 def and_sql( 3348 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3349 ) -> str: 3350 return self.connector_sql(expression, "AND", stack) 3351 3352 def or_sql( 3353 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3354 ) -> str: 3355 return self.connector_sql(expression, "OR", stack) 3356 3357 def xor_sql( 3358 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3359 ) -> str: 3360 return self.connector_sql(expression, "XOR", stack) 3361 3362 def connector_sql( 3363 self, 3364 expression: exp.Connector, 3365 op: str, 3366 stack: t.Optional[t.List[str | exp.Expression]] = None, 3367 ) -> str: 3368 if stack is not None: 3369 if expression.expressions: 3370 stack.append(self.expressions(expression, sep=f" {op} ")) 3371 else: 3372 stack.append(expression.right) 3373 if expression.comments and self.comments: 3374 for comment in expression.comments: 3375 if comment: 3376 op += f" /*{self.sanitize_comment(comment)}*/" 3377 stack.extend((op, expression.left)) 3378 return op 3379 3380 stack = [expression] 3381 sqls: t.List[str] = [] 3382 ops = set() 3383 3384 while stack: 3385 node = stack.pop() 3386 if isinstance(node, exp.Connector): 3387 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3388 else: 3389 sql = self.sql(node) 3390 if sqls and sqls[-1] in ops: 3391 sqls[-1] += f" {sql}" 3392 else: 3393 sqls.append(sql) 3394 3395 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3396 return sep.join(sqls) 3397 3398 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3399 return self.binary(expression, "&") 3400 3401 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3402 return self.binary(expression, "<<") 3403 3404 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3405 return f"~{self.sql(expression, 'this')}" 3406 3407 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3408 return self.binary(expression, "|") 3409 3410 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3411 return self.binary(expression, ">>") 3412 3413 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3414 return self.binary(expression, "^") 3415 3416 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3417 format_sql = self.sql(expression, "format") 3418 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3419 to_sql = self.sql(expression, "to") 3420 to_sql = f" {to_sql}" if to_sql else "" 3421 action = self.sql(expression, "action") 3422 action = f" {action}" if action else "" 3423 default = self.sql(expression, "default") 3424 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3425 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3426 3427 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3428 zone = self.sql(expression, "this") 3429 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3430 3431 def collate_sql(self, expression: exp.Collate) -> str: 3432 if self.COLLATE_IS_FUNC: 3433 return self.function_fallback_sql(expression) 3434 return self.binary(expression, "COLLATE") 3435 3436 def command_sql(self, expression: exp.Command) -> str: 3437 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3438 3439 def comment_sql(self, expression: exp.Comment) -> str: 3440 this = self.sql(expression, "this") 3441 kind = expression.args["kind"] 3442 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3443 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3444 expression_sql = self.sql(expression, "expression") 3445 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3446 3447 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3448 this = self.sql(expression, "this") 3449 delete = " DELETE" if expression.args.get("delete") else "" 3450 recompress = self.sql(expression, "recompress") 3451 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3452 to_disk = self.sql(expression, "to_disk") 3453 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3454 to_volume = self.sql(expression, "to_volume") 3455 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3456 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3457 3458 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3459 where = self.sql(expression, "where") 3460 group = self.sql(expression, "group") 3461 aggregates = self.expressions(expression, key="aggregates") 3462 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3463 3464 if not (where or group or aggregates) and len(expression.expressions) == 1: 3465 return f"TTL {self.expressions(expression, flat=True)}" 3466 3467 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3468 3469 def transaction_sql(self, expression: exp.Transaction) -> str: 3470 modes = self.expressions(expression, key="modes") 3471 modes = f" {modes}" if modes else "" 3472 return f"BEGIN{modes}" 3473 3474 def commit_sql(self, expression: exp.Commit) -> str: 3475 chain = expression.args.get("chain") 3476 if chain is not None: 3477 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3478 3479 return f"COMMIT{chain or ''}" 3480 3481 def rollback_sql(self, expression: exp.Rollback) -> str: 3482 savepoint = expression.args.get("savepoint") 3483 savepoint = f" TO {savepoint}" if savepoint else "" 3484 return f"ROLLBACK{savepoint}" 3485 3486 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3487 this = self.sql(expression, "this") 3488 3489 dtype = self.sql(expression, "dtype") 3490 if dtype: 3491 collate = self.sql(expression, "collate") 3492 collate = f" COLLATE {collate}" if collate else "" 3493 using = self.sql(expression, "using") 3494 using = f" USING {using}" if using else "" 3495 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3496 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3497 3498 default = self.sql(expression, "default") 3499 if default: 3500 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3501 3502 comment = self.sql(expression, "comment") 3503 if comment: 3504 return f"ALTER COLUMN {this} COMMENT {comment}" 3505 3506 visible = expression.args.get("visible") 3507 if visible: 3508 return f"ALTER COLUMN {this} SET {visible}" 3509 3510 allow_null = expression.args.get("allow_null") 3511 drop = expression.args.get("drop") 3512 3513 if not drop and not allow_null: 3514 self.unsupported("Unsupported ALTER COLUMN syntax") 3515 3516 if allow_null is not None: 3517 keyword = "DROP" if drop else "SET" 3518 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3519 3520 return f"ALTER COLUMN {this} DROP DEFAULT" 3521 3522 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3523 this = self.sql(expression, "this") 3524 3525 visible = expression.args.get("visible") 3526 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3527 3528 return f"ALTER INDEX {this} {visible_sql}" 3529 3530 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3531 this = self.sql(expression, "this") 3532 if not isinstance(expression.this, exp.Var): 3533 this = f"KEY DISTKEY {this}" 3534 return f"ALTER DISTSTYLE {this}" 3535 3536 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3537 compound = " COMPOUND" if expression.args.get("compound") else "" 3538 this = self.sql(expression, "this") 3539 expressions = self.expressions(expression, flat=True) 3540 expressions = f"({expressions})" if expressions else "" 3541 return f"ALTER{compound} SORTKEY {this or expressions}" 3542 3543 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3544 if not self.RENAME_TABLE_WITH_DB: 3545 # Remove db from tables 3546 expression = expression.transform( 3547 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3548 ).assert_is(exp.AlterRename) 3549 this = self.sql(expression, "this") 3550 to_kw = " TO" if include_to else "" 3551 return f"RENAME{to_kw} {this}" 3552 3553 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3554 exists = " IF EXISTS" if expression.args.get("exists") else "" 3555 old_column = self.sql(expression, "this") 3556 new_column = self.sql(expression, "to") 3557 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3558 3559 def alterset_sql(self, expression: exp.AlterSet) -> str: 3560 exprs = self.expressions(expression, flat=True) 3561 if self.ALTER_SET_WRAPPED: 3562 exprs = f"({exprs})" 3563 3564 return f"SET {exprs}" 3565 3566 def alter_sql(self, expression: exp.Alter) -> str: 3567 actions = expression.args["actions"] 3568 3569 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3570 actions[0], exp.ColumnDef 3571 ): 3572 actions_sql = self.expressions(expression, key="actions", flat=True) 3573 actions_sql = f"ADD {actions_sql}" 3574 else: 3575 actions_list = [] 3576 for action in actions: 3577 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3578 action_sql = self.add_column_sql(action) 3579 else: 3580 action_sql = self.sql(action) 3581 if isinstance(action, exp.Query): 3582 action_sql = f"AS {action_sql}" 3583 3584 actions_list.append(action_sql) 3585 3586 actions_sql = self.format_args(*actions_list).lstrip("\n") 3587 3588 exists = " IF EXISTS" if expression.args.get("exists") else "" 3589 on_cluster = self.sql(expression, "cluster") 3590 on_cluster = f" {on_cluster}" if on_cluster else "" 3591 only = " ONLY" if expression.args.get("only") else "" 3592 options = self.expressions(expression, key="options") 3593 options = f", {options}" if options else "" 3594 kind = self.sql(expression, "kind") 3595 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3596 check = " WITH CHECK" if expression.args.get("check") else "" 3597 this = self.sql(expression, "this") 3598 this = f" {this}" if this else "" 3599 3600 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}" 3601 3602 def altersession_sql(self, expression: exp.AlterSession) -> str: 3603 items_sql = self.expressions(expression, flat=True) 3604 keyword = "UNSET" if expression.args.get("unset") else "SET" 3605 return f"{keyword} {items_sql}" 3606 3607 def add_column_sql(self, expression: exp.Expression) -> str: 3608 sql = self.sql(expression) 3609 if isinstance(expression, exp.Schema): 3610 column_text = " COLUMNS" 3611 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3612 column_text = " COLUMN" 3613 else: 3614 column_text = "" 3615 3616 return f"ADD{column_text} {sql}" 3617 3618 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3619 expressions = self.expressions(expression) 3620 exists = " IF EXISTS " if expression.args.get("exists") else " " 3621 return f"DROP{exists}{expressions}" 3622 3623 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3624 return f"ADD {self.expressions(expression, indent=False)}" 3625 3626 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3627 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3628 location = self.sql(expression, "location") 3629 location = f" {location}" if location else "" 3630 return f"ADD {exists}{self.sql(expression.this)}{location}" 3631 3632 def distinct_sql(self, expression: exp.Distinct) -> str: 3633 this = self.expressions(expression, flat=True) 3634 3635 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3636 case = exp.case() 3637 for arg in expression.expressions: 3638 case = case.when(arg.is_(exp.null()), exp.null()) 3639 this = self.sql(case.else_(f"({this})")) 3640 3641 this = f" {this}" if this else "" 3642 3643 on = self.sql(expression, "on") 3644 on = f" ON {on}" if on else "" 3645 return f"DISTINCT{this}{on}" 3646 3647 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3648 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3649 3650 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3651 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3652 3653 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3654 this_sql = self.sql(expression, "this") 3655 expression_sql = self.sql(expression, "expression") 3656 kind = "MAX" if expression.args.get("max") else "MIN" 3657 return f"{this_sql} HAVING {kind} {expression_sql}" 3658 3659 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3660 return self.sql( 3661 exp.Cast( 3662 this=exp.Div(this=expression.this, expression=expression.expression), 3663 to=exp.DataType(this=exp.DataType.Type.INT), 3664 ) 3665 ) 3666 3667 def dpipe_sql(self, expression: exp.DPipe) -> str: 3668 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3669 return self.func( 3670 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3671 ) 3672 return self.binary(expression, "||") 3673 3674 def div_sql(self, expression: exp.Div) -> str: 3675 l, r = expression.left, expression.right 3676 3677 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3678 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3679 3680 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3681 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3682 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3683 3684 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3685 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3686 return self.sql( 3687 exp.cast( 3688 l / r, 3689 to=exp.DataType.Type.BIGINT, 3690 ) 3691 ) 3692 3693 return self.binary(expression, "/") 3694 3695 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3696 n = exp._wrap(expression.this, exp.Binary) 3697 d = exp._wrap(expression.expression, exp.Binary) 3698 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3699 3700 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3701 return self.binary(expression, "OVERLAPS") 3702 3703 def distance_sql(self, expression: exp.Distance) -> str: 3704 return self.binary(expression, "<->") 3705 3706 def dot_sql(self, expression: exp.Dot) -> str: 3707 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3708 3709 def eq_sql(self, expression: exp.EQ) -> str: 3710 return self.binary(expression, "=") 3711 3712 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3713 return self.binary(expression, ":=") 3714 3715 def escape_sql(self, expression: exp.Escape) -> str: 3716 return self.binary(expression, "ESCAPE") 3717 3718 def glob_sql(self, expression: exp.Glob) -> str: 3719 return self.binary(expression, "GLOB") 3720 3721 def gt_sql(self, expression: exp.GT) -> str: 3722 return self.binary(expression, ">") 3723 3724 def gte_sql(self, expression: exp.GTE) -> str: 3725 return self.binary(expression, ">=") 3726 3727 def is_sql(self, expression: exp.Is) -> str: 3728 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3729 return self.sql( 3730 expression.this if expression.expression.this else exp.not_(expression.this) 3731 ) 3732 return self.binary(expression, "IS") 3733 3734 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3735 this = expression.this 3736 rhs = expression.expression 3737 3738 if isinstance(expression, exp.Like): 3739 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3740 op = "LIKE" 3741 else: 3742 exp_class = exp.ILike 3743 op = "ILIKE" 3744 3745 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3746 exprs = rhs.this.unnest() 3747 3748 if isinstance(exprs, exp.Tuple): 3749 exprs = exprs.expressions 3750 3751 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3752 3753 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3754 for expr in exprs[1:]: 3755 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3756 3757 return self.sql(like_expr) 3758 3759 return self.binary(expression, op) 3760 3761 def like_sql(self, expression: exp.Like) -> str: 3762 return self._like_sql(expression) 3763 3764 def ilike_sql(self, expression: exp.ILike) -> str: 3765 return self._like_sql(expression) 3766 3767 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3768 return self.binary(expression, "SIMILAR TO") 3769 3770 def lt_sql(self, expression: exp.LT) -> str: 3771 return self.binary(expression, "<") 3772 3773 def lte_sql(self, expression: exp.LTE) -> str: 3774 return self.binary(expression, "<=") 3775 3776 def mod_sql(self, expression: exp.Mod) -> str: 3777 return self.binary(expression, "%") 3778 3779 def mul_sql(self, expression: exp.Mul) -> str: 3780 return self.binary(expression, "*") 3781 3782 def neq_sql(self, expression: exp.NEQ) -> str: 3783 return self.binary(expression, "<>") 3784 3785 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3786 return self.binary(expression, "IS NOT DISTINCT FROM") 3787 3788 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3789 return self.binary(expression, "IS DISTINCT FROM") 3790 3791 def slice_sql(self, expression: exp.Slice) -> str: 3792 return self.binary(expression, ":") 3793 3794 def sub_sql(self, expression: exp.Sub) -> str: 3795 return self.binary(expression, "-") 3796 3797 def trycast_sql(self, expression: exp.TryCast) -> str: 3798 return self.cast_sql(expression, safe_prefix="TRY_") 3799 3800 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3801 return self.cast_sql(expression) 3802 3803 def try_sql(self, expression: exp.Try) -> str: 3804 if not self.TRY_SUPPORTED: 3805 self.unsupported("Unsupported TRY function") 3806 return self.sql(expression, "this") 3807 3808 return self.func("TRY", expression.this) 3809 3810 def log_sql(self, expression: exp.Log) -> str: 3811 this = expression.this 3812 expr = expression.expression 3813 3814 if self.dialect.LOG_BASE_FIRST is False: 3815 this, expr = expr, this 3816 elif self.dialect.LOG_BASE_FIRST is None and expr: 3817 if this.name in ("2", "10"): 3818 return self.func(f"LOG{this.name}", expr) 3819 3820 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3821 3822 return self.func("LOG", this, expr) 3823 3824 def use_sql(self, expression: exp.Use) -> str: 3825 kind = self.sql(expression, "kind") 3826 kind = f" {kind}" if kind else "" 3827 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3828 this = f" {this}" if this else "" 3829 return f"USE{kind}{this}" 3830 3831 def binary(self, expression: exp.Binary, op: str) -> str: 3832 sqls: t.List[str] = [] 3833 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3834 binary_type = type(expression) 3835 3836 while stack: 3837 node = stack.pop() 3838 3839 if type(node) is binary_type: 3840 op_func = node.args.get("operator") 3841 if op_func: 3842 op = f"OPERATOR({self.sql(op_func)})" 3843 3844 stack.append(node.right) 3845 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3846 stack.append(node.left) 3847 else: 3848 sqls.append(self.sql(node)) 3849 3850 return "".join(sqls) 3851 3852 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3853 to_clause = self.sql(expression, "to") 3854 if to_clause: 3855 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3856 3857 return self.function_fallback_sql(expression) 3858 3859 def function_fallback_sql(self, expression: exp.Func) -> str: 3860 args = [] 3861 3862 for key in expression.arg_types: 3863 arg_value = expression.args.get(key) 3864 3865 if isinstance(arg_value, list): 3866 for value in arg_value: 3867 args.append(value) 3868 elif arg_value is not None: 3869 args.append(arg_value) 3870 3871 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3872 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3873 else: 3874 name = expression.sql_name() 3875 3876 return self.func(name, *args) 3877 3878 def func( 3879 self, 3880 name: str, 3881 *args: t.Optional[exp.Expression | str], 3882 prefix: str = "(", 3883 suffix: str = ")", 3884 normalize: bool = True, 3885 ) -> str: 3886 name = self.normalize_func(name) if normalize else name 3887 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3888 3889 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3890 arg_sqls = tuple( 3891 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3892 ) 3893 if self.pretty and self.too_wide(arg_sqls): 3894 return self.indent( 3895 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3896 ) 3897 return sep.join(arg_sqls) 3898 3899 def too_wide(self, args: t.Iterable) -> bool: 3900 return sum(len(arg) for arg in args) > self.max_text_width 3901 3902 def format_time( 3903 self, 3904 expression: exp.Expression, 3905 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3906 inverse_time_trie: t.Optional[t.Dict] = None, 3907 ) -> t.Optional[str]: 3908 return format_time( 3909 self.sql(expression, "format"), 3910 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3911 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3912 ) 3913 3914 def expressions( 3915 self, 3916 expression: t.Optional[exp.Expression] = None, 3917 key: t.Optional[str] = None, 3918 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3919 flat: bool = False, 3920 indent: bool = True, 3921 skip_first: bool = False, 3922 skip_last: bool = False, 3923 sep: str = ", ", 3924 prefix: str = "", 3925 dynamic: bool = False, 3926 new_line: bool = False, 3927 ) -> str: 3928 expressions = expression.args.get(key or "expressions") if expression else sqls 3929 3930 if not expressions: 3931 return "" 3932 3933 if flat: 3934 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3935 3936 num_sqls = len(expressions) 3937 result_sqls = [] 3938 3939 for i, e in enumerate(expressions): 3940 sql = self.sql(e, comment=False) 3941 if not sql: 3942 continue 3943 3944 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3945 3946 if self.pretty: 3947 if self.leading_comma: 3948 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3949 else: 3950 result_sqls.append( 3951 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3952 ) 3953 else: 3954 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3955 3956 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3957 if new_line: 3958 result_sqls.insert(0, "") 3959 result_sqls.append("") 3960 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3961 else: 3962 result_sql = "".join(result_sqls) 3963 3964 return ( 3965 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3966 if indent 3967 else result_sql 3968 ) 3969 3970 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3971 flat = flat or isinstance(expression.parent, exp.Properties) 3972 expressions_sql = self.expressions(expression, flat=flat) 3973 if flat: 3974 return f"{op} {expressions_sql}" 3975 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3976 3977 def naked_property(self, expression: exp.Property) -> str: 3978 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3979 if not property_name: 3980 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3981 return f"{property_name} {self.sql(expression, 'this')}" 3982 3983 def tag_sql(self, expression: exp.Tag) -> str: 3984 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3985 3986 def token_sql(self, token_type: TokenType) -> str: 3987 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3988 3989 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3990 this = self.sql(expression, "this") 3991 expressions = self.no_identify(self.expressions, expression) 3992 expressions = ( 3993 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3994 ) 3995 return f"{this}{expressions}" if expressions.strip() != "" else this 3996 3997 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3998 this = self.sql(expression, "this") 3999 expressions = self.expressions(expression, flat=True) 4000 return f"{this}({expressions})" 4001 4002 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4003 return self.binary(expression, "=>") 4004 4005 def when_sql(self, expression: exp.When) -> str: 4006 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4007 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4008 condition = self.sql(expression, "condition") 4009 condition = f" AND {condition}" if condition else "" 4010 4011 then_expression = expression.args.get("then") 4012 if isinstance(then_expression, exp.Insert): 4013 this = self.sql(then_expression, "this") 4014 this = f"INSERT {this}" if this else "INSERT" 4015 then = self.sql(then_expression, "expression") 4016 then = f"{this} VALUES {then}" if then else this 4017 elif isinstance(then_expression, exp.Update): 4018 if isinstance(then_expression.args.get("expressions"), exp.Star): 4019 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4020 else: 4021 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4022 else: 4023 then = self.sql(then_expression) 4024 return f"WHEN {matched}{source}{condition} THEN {then}" 4025 4026 def whens_sql(self, expression: exp.Whens) -> str: 4027 return self.expressions(expression, sep=" ", indent=False) 4028 4029 def merge_sql(self, expression: exp.Merge) -> str: 4030 table = expression.this 4031 table_alias = "" 4032 4033 hints = table.args.get("hints") 4034 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4035 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4036 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4037 4038 this = self.sql(table) 4039 using = f"USING {self.sql(expression, 'using')}" 4040 on = f"ON {self.sql(expression, 'on')}" 4041 whens = self.sql(expression, "whens") 4042 4043 returning = self.sql(expression, "returning") 4044 if returning: 4045 whens = f"{whens}{returning}" 4046 4047 sep = self.sep() 4048 4049 return self.prepend_ctes( 4050 expression, 4051 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4052 ) 4053 4054 @unsupported_args("format") 4055 def tochar_sql(self, expression: exp.ToChar) -> str: 4056 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4057 4058 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4059 if not self.SUPPORTS_TO_NUMBER: 4060 self.unsupported("Unsupported TO_NUMBER function") 4061 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4062 4063 fmt = expression.args.get("format") 4064 if not fmt: 4065 self.unsupported("Conversion format is required for TO_NUMBER") 4066 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4067 4068 return self.func("TO_NUMBER", expression.this, fmt) 4069 4070 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4071 this = self.sql(expression, "this") 4072 kind = self.sql(expression, "kind") 4073 settings_sql = self.expressions(expression, key="settings", sep=" ") 4074 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4075 return f"{this}({kind}{args})" 4076 4077 def dictrange_sql(self, expression: exp.DictRange) -> str: 4078 this = self.sql(expression, "this") 4079 max = self.sql(expression, "max") 4080 min = self.sql(expression, "min") 4081 return f"{this}(MIN {min} MAX {max})" 4082 4083 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4084 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4085 4086 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4087 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4088 4089 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4090 def uniquekeyproperty_sql( 4091 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4092 ) -> str: 4093 return f"{prefix} ({self.expressions(expression, flat=True)})" 4094 4095 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4096 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4097 expressions = self.expressions(expression, flat=True) 4098 expressions = f" {self.wrap(expressions)}" if expressions else "" 4099 buckets = self.sql(expression, "buckets") 4100 kind = self.sql(expression, "kind") 4101 buckets = f" BUCKETS {buckets}" if buckets else "" 4102 order = self.sql(expression, "order") 4103 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4104 4105 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4106 return "" 4107 4108 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4109 expressions = self.expressions(expression, key="expressions", flat=True) 4110 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4111 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4112 buckets = self.sql(expression, "buckets") 4113 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4114 4115 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4116 this = self.sql(expression, "this") 4117 having = self.sql(expression, "having") 4118 4119 if having: 4120 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4121 4122 return self.func("ANY_VALUE", this) 4123 4124 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4125 transform = self.func("TRANSFORM", *expression.expressions) 4126 row_format_before = self.sql(expression, "row_format_before") 4127 row_format_before = f" {row_format_before}" if row_format_before else "" 4128 record_writer = self.sql(expression, "record_writer") 4129 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4130 using = f" USING {self.sql(expression, 'command_script')}" 4131 schema = self.sql(expression, "schema") 4132 schema = f" AS {schema}" if schema else "" 4133 row_format_after = self.sql(expression, "row_format_after") 4134 row_format_after = f" {row_format_after}" if row_format_after else "" 4135 record_reader = self.sql(expression, "record_reader") 4136 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4137 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4138 4139 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4140 key_block_size = self.sql(expression, "key_block_size") 4141 if key_block_size: 4142 return f"KEY_BLOCK_SIZE = {key_block_size}" 4143 4144 using = self.sql(expression, "using") 4145 if using: 4146 return f"USING {using}" 4147 4148 parser = self.sql(expression, "parser") 4149 if parser: 4150 return f"WITH PARSER {parser}" 4151 4152 comment = self.sql(expression, "comment") 4153 if comment: 4154 return f"COMMENT {comment}" 4155 4156 visible = expression.args.get("visible") 4157 if visible is not None: 4158 return "VISIBLE" if visible else "INVISIBLE" 4159 4160 engine_attr = self.sql(expression, "engine_attr") 4161 if engine_attr: 4162 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4163 4164 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4165 if secondary_engine_attr: 4166 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4167 4168 self.unsupported("Unsupported index constraint option.") 4169 return "" 4170 4171 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4172 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4173 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4174 4175 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4176 kind = self.sql(expression, "kind") 4177 kind = f"{kind} INDEX" if kind else "INDEX" 4178 this = self.sql(expression, "this") 4179 this = f" {this}" if this else "" 4180 index_type = self.sql(expression, "index_type") 4181 index_type = f" USING {index_type}" if index_type else "" 4182 expressions = self.expressions(expression, flat=True) 4183 expressions = f" ({expressions})" if expressions else "" 4184 options = self.expressions(expression, key="options", sep=" ") 4185 options = f" {options}" if options else "" 4186 return f"{kind}{this}{index_type}{expressions}{options}" 4187 4188 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4189 if self.NVL2_SUPPORTED: 4190 return self.function_fallback_sql(expression) 4191 4192 case = exp.Case().when( 4193 expression.this.is_(exp.null()).not_(copy=False), 4194 expression.args["true"], 4195 copy=False, 4196 ) 4197 else_cond = expression.args.get("false") 4198 if else_cond: 4199 case.else_(else_cond, copy=False) 4200 4201 return self.sql(case) 4202 4203 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4204 this = self.sql(expression, "this") 4205 expr = self.sql(expression, "expression") 4206 iterator = self.sql(expression, "iterator") 4207 condition = self.sql(expression, "condition") 4208 condition = f" IF {condition}" if condition else "" 4209 return f"{this} FOR {expr} IN {iterator}{condition}" 4210 4211 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4212 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4213 4214 def opclass_sql(self, expression: exp.Opclass) -> str: 4215 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4216 4217 def predict_sql(self, expression: exp.Predict) -> str: 4218 model = self.sql(expression, "this") 4219 model = f"MODEL {model}" 4220 table = self.sql(expression, "expression") 4221 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4222 parameters = self.sql(expression, "params_struct") 4223 return self.func("PREDICT", model, table, parameters or None) 4224 4225 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4226 model = self.sql(expression, "this") 4227 model = f"MODEL {model}" 4228 table = self.sql(expression, "expression") 4229 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4230 parameters = self.sql(expression, "params_struct") 4231 return self.func("GENERATE_EMBEDDING", model, table, parameters or None) 4232 4233 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4234 this_sql = self.sql(expression, "this") 4235 if isinstance(expression.this, exp.Table): 4236 this_sql = f"TABLE {this_sql}" 4237 4238 return self.func( 4239 "FEATURES_AT_TIME", 4240 this_sql, 4241 expression.args.get("time"), 4242 expression.args.get("num_rows"), 4243 expression.args.get("ignore_feature_nulls"), 4244 ) 4245 4246 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4247 this_sql = self.sql(expression, "this") 4248 if isinstance(expression.this, exp.Table): 4249 this_sql = f"TABLE {this_sql}" 4250 4251 query_table = self.sql(expression, "query_table") 4252 if isinstance(expression.args["query_table"], exp.Table): 4253 query_table = f"TABLE {query_table}" 4254 4255 return self.func( 4256 "VECTOR_SEARCH", 4257 this_sql, 4258 expression.args.get("column_to_search"), 4259 query_table, 4260 expression.args.get("query_column_to_search"), 4261 expression.args.get("top_k"), 4262 expression.args.get("distance_type"), 4263 expression.args.get("options"), 4264 ) 4265 4266 def forin_sql(self, expression: exp.ForIn) -> str: 4267 this = self.sql(expression, "this") 4268 expression_sql = self.sql(expression, "expression") 4269 return f"FOR {this} DO {expression_sql}" 4270 4271 def refresh_sql(self, expression: exp.Refresh) -> str: 4272 this = self.sql(expression, "this") 4273 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4274 return f"REFRESH {table}{this}" 4275 4276 def toarray_sql(self, expression: exp.ToArray) -> str: 4277 arg = expression.this 4278 if not arg.type: 4279 from sqlglot.optimizer.annotate_types import annotate_types 4280 4281 arg = annotate_types(arg, dialect=self.dialect) 4282 4283 if arg.is_type(exp.DataType.Type.ARRAY): 4284 return self.sql(arg) 4285 4286 cond_for_null = arg.is_(exp.null()) 4287 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4288 4289 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4290 this = expression.this 4291 time_format = self.format_time(expression) 4292 4293 if time_format: 4294 return self.sql( 4295 exp.cast( 4296 exp.StrToTime(this=this, format=expression.args["format"]), 4297 exp.DataType.Type.TIME, 4298 ) 4299 ) 4300 4301 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4302 return self.sql(this) 4303 4304 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4305 4306 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4307 this = expression.this 4308 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4309 return self.sql(this) 4310 4311 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4312 4313 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4314 this = expression.this 4315 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4316 return self.sql(this) 4317 4318 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4319 4320 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4321 this = expression.this 4322 time_format = self.format_time(expression) 4323 4324 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4325 return self.sql( 4326 exp.cast( 4327 exp.StrToTime(this=this, format=expression.args["format"]), 4328 exp.DataType.Type.DATE, 4329 ) 4330 ) 4331 4332 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4333 return self.sql(this) 4334 4335 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4336 4337 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4338 return self.sql( 4339 exp.func( 4340 "DATEDIFF", 4341 expression.this, 4342 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4343 "day", 4344 ) 4345 ) 4346 4347 def lastday_sql(self, expression: exp.LastDay) -> str: 4348 if self.LAST_DAY_SUPPORTS_DATE_PART: 4349 return self.function_fallback_sql(expression) 4350 4351 unit = expression.text("unit") 4352 if unit and unit != "MONTH": 4353 self.unsupported("Date parts are not supported in LAST_DAY.") 4354 4355 return self.func("LAST_DAY", expression.this) 4356 4357 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4358 from sqlglot.dialects.dialect import unit_to_str 4359 4360 return self.func( 4361 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4362 ) 4363 4364 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4365 if self.CAN_IMPLEMENT_ARRAY_ANY: 4366 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4367 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4368 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4369 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4370 4371 from sqlglot.dialects import Dialect 4372 4373 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4374 if self.dialect.__class__ != Dialect: 4375 self.unsupported("ARRAY_ANY is unsupported") 4376 4377 return self.function_fallback_sql(expression) 4378 4379 def struct_sql(self, expression: exp.Struct) -> str: 4380 expression.set( 4381 "expressions", 4382 [ 4383 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4384 if isinstance(e, exp.PropertyEQ) 4385 else e 4386 for e in expression.expressions 4387 ], 4388 ) 4389 4390 return self.function_fallback_sql(expression) 4391 4392 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4393 low = self.sql(expression, "this") 4394 high = self.sql(expression, "expression") 4395 4396 return f"{low} TO {high}" 4397 4398 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4399 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4400 tables = f" {self.expressions(expression)}" 4401 4402 exists = " IF EXISTS" if expression.args.get("exists") else "" 4403 4404 on_cluster = self.sql(expression, "cluster") 4405 on_cluster = f" {on_cluster}" if on_cluster else "" 4406 4407 identity = self.sql(expression, "identity") 4408 identity = f" {identity} IDENTITY" if identity else "" 4409 4410 option = self.sql(expression, "option") 4411 option = f" {option}" if option else "" 4412 4413 partition = self.sql(expression, "partition") 4414 partition = f" {partition}" if partition else "" 4415 4416 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4417 4418 # This transpiles T-SQL's CONVERT function 4419 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4420 def convert_sql(self, expression: exp.Convert) -> str: 4421 to = expression.this 4422 value = expression.expression 4423 style = expression.args.get("style") 4424 safe = expression.args.get("safe") 4425 strict = expression.args.get("strict") 4426 4427 if not to or not value: 4428 return "" 4429 4430 # Retrieve length of datatype and override to default if not specified 4431 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4432 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4433 4434 transformed: t.Optional[exp.Expression] = None 4435 cast = exp.Cast if strict else exp.TryCast 4436 4437 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4438 if isinstance(style, exp.Literal) and style.is_int: 4439 from sqlglot.dialects.tsql import TSQL 4440 4441 style_value = style.name 4442 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4443 if not converted_style: 4444 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4445 4446 fmt = exp.Literal.string(converted_style) 4447 4448 if to.this == exp.DataType.Type.DATE: 4449 transformed = exp.StrToDate(this=value, format=fmt) 4450 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4451 transformed = exp.StrToTime(this=value, format=fmt) 4452 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4453 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4454 elif to.this == exp.DataType.Type.TEXT: 4455 transformed = exp.TimeToStr(this=value, format=fmt) 4456 4457 if not transformed: 4458 transformed = cast(this=value, to=to, safe=safe) 4459 4460 return self.sql(transformed) 4461 4462 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4463 this = expression.this 4464 if isinstance(this, exp.JSONPathWildcard): 4465 this = self.json_path_part(this) 4466 return f".{this}" if this else "" 4467 4468 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4469 return f".{this}" 4470 4471 this = self.json_path_part(this) 4472 return ( 4473 f"[{this}]" 4474 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4475 else f".{this}" 4476 ) 4477 4478 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4479 this = self.json_path_part(expression.this) 4480 return f"[{this}]" if this else "" 4481 4482 def _simplify_unless_literal(self, expression: E) -> E: 4483 if not isinstance(expression, exp.Literal): 4484 from sqlglot.optimizer.simplify import simplify 4485 4486 expression = simplify(expression, dialect=self.dialect) 4487 4488 return expression 4489 4490 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4491 this = expression.this 4492 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4493 self.unsupported( 4494 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4495 ) 4496 return self.sql(this) 4497 4498 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4499 # The first modifier here will be the one closest to the AggFunc's arg 4500 mods = sorted( 4501 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4502 key=lambda x: 0 4503 if isinstance(x, exp.HavingMax) 4504 else (1 if isinstance(x, exp.Order) else 2), 4505 ) 4506 4507 if mods: 4508 mod = mods[0] 4509 this = expression.__class__(this=mod.this.copy()) 4510 this.meta["inline"] = True 4511 mod.this.replace(this) 4512 return self.sql(expression.this) 4513 4514 agg_func = expression.find(exp.AggFunc) 4515 4516 if agg_func: 4517 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4518 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4519 4520 return f"{self.sql(expression, 'this')} {text}" 4521 4522 def _replace_line_breaks(self, string: str) -> str: 4523 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4524 if self.pretty: 4525 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4526 return string 4527 4528 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4529 option = self.sql(expression, "this") 4530 4531 if expression.expressions: 4532 upper = option.upper() 4533 4534 # Snowflake FILE_FORMAT options are separated by whitespace 4535 sep = " " if upper == "FILE_FORMAT" else ", " 4536 4537 # Databricks copy/format options do not set their list of values with EQ 4538 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4539 values = self.expressions(expression, flat=True, sep=sep) 4540 return f"{option}{op}({values})" 4541 4542 value = self.sql(expression, "expression") 4543 4544 if not value: 4545 return option 4546 4547 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4548 4549 return f"{option}{op}{value}" 4550 4551 def credentials_sql(self, expression: exp.Credentials) -> str: 4552 cred_expr = expression.args.get("credentials") 4553 if isinstance(cred_expr, exp.Literal): 4554 # Redshift case: CREDENTIALS <string> 4555 credentials = self.sql(expression, "credentials") 4556 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4557 else: 4558 # Snowflake case: CREDENTIALS = (...) 4559 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4560 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4561 4562 storage = self.sql(expression, "storage") 4563 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4564 4565 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4566 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4567 4568 iam_role = self.sql(expression, "iam_role") 4569 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4570 4571 region = self.sql(expression, "region") 4572 region = f" REGION {region}" if region else "" 4573 4574 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4575 4576 def copy_sql(self, expression: exp.Copy) -> str: 4577 this = self.sql(expression, "this") 4578 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4579 4580 credentials = self.sql(expression, "credentials") 4581 credentials = self.seg(credentials) if credentials else "" 4582 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4583 files = self.expressions(expression, key="files", flat=True) 4584 4585 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4586 params = self.expressions( 4587 expression, 4588 key="params", 4589 sep=sep, 4590 new_line=True, 4591 skip_last=True, 4592 skip_first=True, 4593 indent=self.COPY_PARAMS_ARE_WRAPPED, 4594 ) 4595 4596 if params: 4597 if self.COPY_PARAMS_ARE_WRAPPED: 4598 params = f" WITH ({params})" 4599 elif not self.pretty: 4600 params = f" {params}" 4601 4602 return f"COPY{this}{kind} {files}{credentials}{params}" 4603 4604 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4605 return "" 4606 4607 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4608 on_sql = "ON" if expression.args.get("on") else "OFF" 4609 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4610 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4611 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4612 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4613 4614 if filter_col or retention_period: 4615 on_sql = self.func("ON", filter_col, retention_period) 4616 4617 return f"DATA_DELETION={on_sql}" 4618 4619 def maskingpolicycolumnconstraint_sql( 4620 self, expression: exp.MaskingPolicyColumnConstraint 4621 ) -> str: 4622 this = self.sql(expression, "this") 4623 expressions = self.expressions(expression, flat=True) 4624 expressions = f" USING ({expressions})" if expressions else "" 4625 return f"MASKING POLICY {this}{expressions}" 4626 4627 def gapfill_sql(self, expression: exp.GapFill) -> str: 4628 this = self.sql(expression, "this") 4629 this = f"TABLE {this}" 4630 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4631 4632 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4633 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4634 4635 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4636 this = self.sql(expression, "this") 4637 expr = expression.expression 4638 4639 if isinstance(expr, exp.Func): 4640 # T-SQL's CLR functions are case sensitive 4641 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4642 else: 4643 expr = self.sql(expression, "expression") 4644 4645 return self.scope_resolution(expr, this) 4646 4647 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4648 if self.PARSE_JSON_NAME is None: 4649 return self.sql(expression.this) 4650 4651 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4652 4653 def rand_sql(self, expression: exp.Rand) -> str: 4654 lower = self.sql(expression, "lower") 4655 upper = self.sql(expression, "upper") 4656 4657 if lower and upper: 4658 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4659 return self.func("RAND", expression.this) 4660 4661 def changes_sql(self, expression: exp.Changes) -> str: 4662 information = self.sql(expression, "information") 4663 information = f"INFORMATION => {information}" 4664 at_before = self.sql(expression, "at_before") 4665 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4666 end = self.sql(expression, "end") 4667 end = f"{self.seg('')}{end}" if end else "" 4668 4669 return f"CHANGES ({information}){at_before}{end}" 4670 4671 def pad_sql(self, expression: exp.Pad) -> str: 4672 prefix = "L" if expression.args.get("is_left") else "R" 4673 4674 fill_pattern = self.sql(expression, "fill_pattern") or None 4675 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4676 fill_pattern = "' '" 4677 4678 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4679 4680 def summarize_sql(self, expression: exp.Summarize) -> str: 4681 table = " TABLE" if expression.args.get("table") else "" 4682 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4683 4684 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4685 generate_series = exp.GenerateSeries(**expression.args) 4686 4687 parent = expression.parent 4688 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4689 parent = parent.parent 4690 4691 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4692 return self.sql(exp.Unnest(expressions=[generate_series])) 4693 4694 if isinstance(parent, exp.Select): 4695 self.unsupported("GenerateSeries projection unnesting is not supported.") 4696 4697 return self.sql(generate_series) 4698 4699 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4700 exprs = expression.expressions 4701 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4702 if len(exprs) == 0: 4703 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4704 else: 4705 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4706 else: 4707 rhs = self.expressions(expression) # type: ignore 4708 4709 return self.func(name, expression.this, rhs or None) 4710 4711 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4712 if self.SUPPORTS_CONVERT_TIMEZONE: 4713 return self.function_fallback_sql(expression) 4714 4715 source_tz = expression.args.get("source_tz") 4716 target_tz = expression.args.get("target_tz") 4717 timestamp = expression.args.get("timestamp") 4718 4719 if source_tz and timestamp: 4720 timestamp = exp.AtTimeZone( 4721 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4722 ) 4723 4724 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4725 4726 return self.sql(expr) 4727 4728 def json_sql(self, expression: exp.JSON) -> str: 4729 this = self.sql(expression, "this") 4730 this = f" {this}" if this else "" 4731 4732 _with = expression.args.get("with") 4733 4734 if _with is None: 4735 with_sql = "" 4736 elif not _with: 4737 with_sql = " WITHOUT" 4738 else: 4739 with_sql = " WITH" 4740 4741 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4742 4743 return f"JSON{this}{with_sql}{unique_sql}" 4744 4745 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4746 def _generate_on_options(arg: t.Any) -> str: 4747 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4748 4749 path = self.sql(expression, "path") 4750 returning = self.sql(expression, "returning") 4751 returning = f" RETURNING {returning}" if returning else "" 4752 4753 on_condition = self.sql(expression, "on_condition") 4754 on_condition = f" {on_condition}" if on_condition else "" 4755 4756 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4757 4758 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4759 else_ = "ELSE " if expression.args.get("else_") else "" 4760 condition = self.sql(expression, "expression") 4761 condition = f"WHEN {condition} THEN " if condition else else_ 4762 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4763 return f"{condition}{insert}" 4764 4765 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4766 kind = self.sql(expression, "kind") 4767 expressions = self.seg(self.expressions(expression, sep=" ")) 4768 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4769 return res 4770 4771 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4772 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4773 empty = expression.args.get("empty") 4774 empty = ( 4775 f"DEFAULT {empty} ON EMPTY" 4776 if isinstance(empty, exp.Expression) 4777 else self.sql(expression, "empty") 4778 ) 4779 4780 error = expression.args.get("error") 4781 error = ( 4782 f"DEFAULT {error} ON ERROR" 4783 if isinstance(error, exp.Expression) 4784 else self.sql(expression, "error") 4785 ) 4786 4787 if error and empty: 4788 error = ( 4789 f"{empty} {error}" 4790 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4791 else f"{error} {empty}" 4792 ) 4793 empty = "" 4794 4795 null = self.sql(expression, "null") 4796 4797 return f"{empty}{error}{null}" 4798 4799 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4800 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4801 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4802 4803 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4804 this = self.sql(expression, "this") 4805 path = self.sql(expression, "path") 4806 4807 passing = self.expressions(expression, "passing") 4808 passing = f" PASSING {passing}" if passing else "" 4809 4810 on_condition = self.sql(expression, "on_condition") 4811 on_condition = f" {on_condition}" if on_condition else "" 4812 4813 path = f"{path}{passing}{on_condition}" 4814 4815 return self.func("JSON_EXISTS", this, path) 4816 4817 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4818 array_agg = self.function_fallback_sql(expression) 4819 4820 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4821 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4822 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4823 parent = expression.parent 4824 if isinstance(parent, exp.Filter): 4825 parent_cond = parent.expression.this 4826 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4827 else: 4828 this = expression.this 4829 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4830 if this.find(exp.Column): 4831 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4832 this_sql = ( 4833 self.expressions(this) 4834 if isinstance(this, exp.Distinct) 4835 else self.sql(expression, "this") 4836 ) 4837 4838 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4839 4840 return array_agg 4841 4842 def apply_sql(self, expression: exp.Apply) -> str: 4843 this = self.sql(expression, "this") 4844 expr = self.sql(expression, "expression") 4845 4846 return f"{this} APPLY({expr})" 4847 4848 def _grant_or_revoke_sql( 4849 self, 4850 expression: exp.Grant | exp.Revoke, 4851 keyword: str, 4852 preposition: str, 4853 grant_option_prefix: str = "", 4854 grant_option_suffix: str = "", 4855 ) -> str: 4856 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4857 4858 kind = self.sql(expression, "kind") 4859 kind = f" {kind}" if kind else "" 4860 4861 securable = self.sql(expression, "securable") 4862 securable = f" {securable}" if securable else "" 4863 4864 principals = self.expressions(expression, key="principals", flat=True) 4865 4866 if not expression.args.get("grant_option"): 4867 grant_option_prefix = grant_option_suffix = "" 4868 4869 # cascade for revoke only 4870 cascade = self.sql(expression, "cascade") 4871 cascade = f" {cascade}" if cascade else "" 4872 4873 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 4874 4875 def grant_sql(self, expression: exp.Grant) -> str: 4876 return self._grant_or_revoke_sql( 4877 expression, 4878 keyword="GRANT", 4879 preposition="TO", 4880 grant_option_suffix=" WITH GRANT OPTION", 4881 ) 4882 4883 def revoke_sql(self, expression: exp.Revoke) -> str: 4884 return self._grant_or_revoke_sql( 4885 expression, 4886 keyword="REVOKE", 4887 preposition="FROM", 4888 grant_option_prefix="GRANT OPTION FOR ", 4889 ) 4890 4891 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4892 this = self.sql(expression, "this") 4893 columns = self.expressions(expression, flat=True) 4894 columns = f"({columns})" if columns else "" 4895 4896 return f"{this}{columns}" 4897 4898 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4899 this = self.sql(expression, "this") 4900 4901 kind = self.sql(expression, "kind") 4902 kind = f"{kind} " if kind else "" 4903 4904 return f"{kind}{this}" 4905 4906 def columns_sql(self, expression: exp.Columns): 4907 func = self.function_fallback_sql(expression) 4908 if expression.args.get("unpack"): 4909 func = f"*{func}" 4910 4911 return func 4912 4913 def overlay_sql(self, expression: exp.Overlay): 4914 this = self.sql(expression, "this") 4915 expr = self.sql(expression, "expression") 4916 from_sql = self.sql(expression, "from") 4917 for_sql = self.sql(expression, "for") 4918 for_sql = f" FOR {for_sql}" if for_sql else "" 4919 4920 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4921 4922 @unsupported_args("format") 4923 def todouble_sql(self, expression: exp.ToDouble) -> str: 4924 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4925 4926 def string_sql(self, expression: exp.String) -> str: 4927 this = expression.this 4928 zone = expression.args.get("zone") 4929 4930 if zone: 4931 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4932 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4933 # set for source_tz to transpile the time conversion before the STRING cast 4934 this = exp.ConvertTimezone( 4935 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4936 ) 4937 4938 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4939 4940 def median_sql(self, expression: exp.Median): 4941 if not self.SUPPORTS_MEDIAN: 4942 return self.sql( 4943 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4944 ) 4945 4946 return self.function_fallback_sql(expression) 4947 4948 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4949 filler = self.sql(expression, "this") 4950 filler = f" {filler}" if filler else "" 4951 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4952 return f"TRUNCATE{filler} {with_count}" 4953 4954 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4955 if self.SUPPORTS_UNIX_SECONDS: 4956 return self.function_fallback_sql(expression) 4957 4958 start_ts = exp.cast( 4959 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4960 ) 4961 4962 return self.sql( 4963 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4964 ) 4965 4966 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4967 dim = expression.expression 4968 4969 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4970 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4971 if not (dim.is_int and dim.name == "1"): 4972 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4973 dim = None 4974 4975 # If dimension is required but not specified, default initialize it 4976 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4977 dim = exp.Literal.number(1) 4978 4979 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4980 4981 def attach_sql(self, expression: exp.Attach) -> str: 4982 this = self.sql(expression, "this") 4983 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4984 expressions = self.expressions(expression) 4985 expressions = f" ({expressions})" if expressions else "" 4986 4987 return f"ATTACH{exists_sql} {this}{expressions}" 4988 4989 def detach_sql(self, expression: exp.Detach) -> str: 4990 this = self.sql(expression, "this") 4991 # the DATABASE keyword is required if IF EXISTS is set 4992 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4993 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4994 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4995 4996 return f"DETACH{exists_sql} {this}" 4997 4998 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4999 this = self.sql(expression, "this") 5000 value = self.sql(expression, "expression") 5001 value = f" {value}" if value else "" 5002 return f"{this}{value}" 5003 5004 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5005 return ( 5006 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5007 ) 5008 5009 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5010 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5011 encode = f"{encode} {self.sql(expression, 'this')}" 5012 5013 properties = expression.args.get("properties") 5014 if properties: 5015 encode = f"{encode} {self.properties(properties)}" 5016 5017 return encode 5018 5019 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5020 this = self.sql(expression, "this") 5021 include = f"INCLUDE {this}" 5022 5023 column_def = self.sql(expression, "column_def") 5024 if column_def: 5025 include = f"{include} {column_def}" 5026 5027 alias = self.sql(expression, "alias") 5028 if alias: 5029 include = f"{include} AS {alias}" 5030 5031 return include 5032 5033 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5034 name = f"NAME {self.sql(expression, 'this')}" 5035 return self.func("XMLELEMENT", name, *expression.expressions) 5036 5037 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5038 this = self.sql(expression, "this") 5039 expr = self.sql(expression, "expression") 5040 expr = f"({expr})" if expr else "" 5041 return f"{this}{expr}" 5042 5043 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5044 partitions = self.expressions(expression, "partition_expressions") 5045 create = self.expressions(expression, "create_expressions") 5046 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5047 5048 def partitionbyrangepropertydynamic_sql( 5049 self, expression: exp.PartitionByRangePropertyDynamic 5050 ) -> str: 5051 start = self.sql(expression, "start") 5052 end = self.sql(expression, "end") 5053 5054 every = expression.args["every"] 5055 if isinstance(every, exp.Interval) and every.this.is_string: 5056 every.this.replace(exp.Literal.number(every.name)) 5057 5058 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5059 5060 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5061 name = self.sql(expression, "this") 5062 values = self.expressions(expression, flat=True) 5063 5064 return f"NAME {name} VALUE {values}" 5065 5066 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5067 kind = self.sql(expression, "kind") 5068 sample = self.sql(expression, "sample") 5069 return f"SAMPLE {sample} {kind}" 5070 5071 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5072 kind = self.sql(expression, "kind") 5073 option = self.sql(expression, "option") 5074 option = f" {option}" if option else "" 5075 this = self.sql(expression, "this") 5076 this = f" {this}" if this else "" 5077 columns = self.expressions(expression) 5078 columns = f" {columns}" if columns else "" 5079 return f"{kind}{option} STATISTICS{this}{columns}" 5080 5081 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5082 this = self.sql(expression, "this") 5083 columns = self.expressions(expression) 5084 inner_expression = self.sql(expression, "expression") 5085 inner_expression = f" {inner_expression}" if inner_expression else "" 5086 update_options = self.sql(expression, "update_options") 5087 update_options = f" {update_options} UPDATE" if update_options else "" 5088 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5089 5090 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5091 kind = self.sql(expression, "kind") 5092 kind = f" {kind}" if kind else "" 5093 return f"DELETE{kind} STATISTICS" 5094 5095 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5096 inner_expression = self.sql(expression, "expression") 5097 return f"LIST CHAINED ROWS{inner_expression}" 5098 5099 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5100 kind = self.sql(expression, "kind") 5101 this = self.sql(expression, "this") 5102 this = f" {this}" if this else "" 5103 inner_expression = self.sql(expression, "expression") 5104 return f"VALIDATE {kind}{this}{inner_expression}" 5105 5106 def analyze_sql(self, expression: exp.Analyze) -> str: 5107 options = self.expressions(expression, key="options", sep=" ") 5108 options = f" {options}" if options else "" 5109 kind = self.sql(expression, "kind") 5110 kind = f" {kind}" if kind else "" 5111 this = self.sql(expression, "this") 5112 this = f" {this}" if this else "" 5113 mode = self.sql(expression, "mode") 5114 mode = f" {mode}" if mode else "" 5115 properties = self.sql(expression, "properties") 5116 properties = f" {properties}" if properties else "" 5117 partition = self.sql(expression, "partition") 5118 partition = f" {partition}" if partition else "" 5119 inner_expression = self.sql(expression, "expression") 5120 inner_expression = f" {inner_expression}" if inner_expression else "" 5121 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5122 5123 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5124 this = self.sql(expression, "this") 5125 namespaces = self.expressions(expression, key="namespaces") 5126 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5127 passing = self.expressions(expression, key="passing") 5128 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5129 columns = self.expressions(expression, key="columns") 5130 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5131 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5132 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5133 5134 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5135 this = self.sql(expression, "this") 5136 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5137 5138 def export_sql(self, expression: exp.Export) -> str: 5139 this = self.sql(expression, "this") 5140 connection = self.sql(expression, "connection") 5141 connection = f"WITH CONNECTION {connection} " if connection else "" 5142 options = self.sql(expression, "options") 5143 return f"EXPORT DATA {connection}{options} AS {this}" 5144 5145 def declare_sql(self, expression: exp.Declare) -> str: 5146 return f"DECLARE {self.expressions(expression, flat=True)}" 5147 5148 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5149 variable = self.sql(expression, "this") 5150 default = self.sql(expression, "default") 5151 default = f" = {default}" if default else "" 5152 5153 kind = self.sql(expression, "kind") 5154 if isinstance(expression.args.get("kind"), exp.Schema): 5155 kind = f"TABLE {kind}" 5156 5157 return f"{variable} AS {kind}{default}" 5158 5159 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5160 kind = self.sql(expression, "kind") 5161 this = self.sql(expression, "this") 5162 set = self.sql(expression, "expression") 5163 using = self.sql(expression, "using") 5164 using = f" USING {using}" if using else "" 5165 5166 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5167 5168 return f"{kind_sql} {this} SET {set}{using}" 5169 5170 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5171 params = self.expressions(expression, key="params", flat=True) 5172 return self.func(expression.name, *expression.expressions) + f"({params})" 5173 5174 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5175 return self.func(expression.name, *expression.expressions) 5176 5177 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5178 return self.anonymousaggfunc_sql(expression) 5179 5180 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5181 return self.parameterizedagg_sql(expression) 5182 5183 def show_sql(self, expression: exp.Show) -> str: 5184 self.unsupported("Unsupported SHOW statement") 5185 return "" 5186 5187 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5188 # Snowflake GET/PUT statements: 5189 # PUT <file> <internalStage> <properties> 5190 # GET <internalStage> <file> <properties> 5191 props = expression.args.get("properties") 5192 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5193 this = self.sql(expression, "this") 5194 target = self.sql(expression, "target") 5195 5196 if isinstance(expression, exp.Put): 5197 return f"PUT {this} {target}{props_sql}" 5198 else: 5199 return f"GET {target} {this}{props_sql}" 5200 5201 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5202 this = self.sql(expression, "this") 5203 expr = self.sql(expression, "expression") 5204 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5205 return f"TRANSLATE({this} USING {expr}{with_error})" 5206 5207 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5208 if self.SUPPORTS_DECODE_CASE: 5209 return self.func("DECODE", *expression.expressions) 5210 5211 expression, *expressions = expression.expressions 5212 5213 ifs = [] 5214 for search, result in zip(expressions[::2], expressions[1::2]): 5215 if isinstance(search, exp.Literal): 5216 ifs.append(exp.If(this=expression.eq(search), true=result)) 5217 elif isinstance(search, exp.Null): 5218 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5219 else: 5220 if isinstance(search, exp.Binary): 5221 search = exp.paren(search) 5222 5223 cond = exp.or_( 5224 expression.eq(search), 5225 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5226 copy=False, 5227 ) 5228 ifs.append(exp.If(this=cond, true=result)) 5229 5230 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5231 return self.sql(case) 5232 5233 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5234 this = self.sql(expression, "this") 5235 this = self.seg(this, sep="") 5236 dimensions = self.expressions( 5237 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5238 ) 5239 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5240 metrics = self.expressions( 5241 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5242 ) 5243 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5244 where = self.sql(expression, "where") 5245 where = self.seg(f"WHERE {where}") if where else "" 5246 body = self.indent(this + metrics + dimensions + where, skip_first=True) 5247 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5248 5249 def getextract_sql(self, expression: exp.GetExtract) -> str: 5250 this = expression.this 5251 expr = expression.expression 5252 5253 if not this.type or not expression.type: 5254 from sqlglot.optimizer.annotate_types import annotate_types 5255 5256 this = annotate_types(this, dialect=self.dialect) 5257 5258 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5259 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5260 5261 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5262 5263 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5264 return self.sql( 5265 exp.DateAdd( 5266 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5267 expression=expression.this, 5268 unit=exp.var("DAY"), 5269 ) 5270 ) 5271 5272 def space_sql(self: Generator, expression: exp.Space) -> str: 5273 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5274 5275 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5276 return f"BUILD {self.sql(expression, 'this')}" 5277 5278 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5279 method = self.sql(expression, "method") 5280 kind = expression.args.get("kind") 5281 if not kind: 5282 return f"REFRESH {method}" 5283 5284 every = self.sql(expression, "every") 5285 unit = self.sql(expression, "unit") 5286 every = f" EVERY {every} {unit}" if every else "" 5287 starts = self.sql(expression, "starts") 5288 starts = f" STARTS {starts}" if starts else "" 5289 5290 return f"REFRESH {method} ON {kind}{every}{starts}"
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 or 'always': Always quote. 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.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.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 142 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 143 exp.DynamicProperty: lambda *_: "DYNAMIC", 144 exp.EmptyProperty: lambda *_: "EMPTY", 145 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 146 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 147 exp.EphemeralColumnConstraint: lambda self, 148 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 149 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 150 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 151 exp.Except: lambda self, e: self.set_operations(e), 152 exp.ExternalProperty: lambda *_: "EXTERNAL", 153 exp.Floor: lambda self, e: self.ceil_floor(e), 154 exp.Get: lambda self, e: self.get_put_sql(e), 155 exp.GlobalProperty: lambda *_: "GLOBAL", 156 exp.HeapProperty: lambda *_: "HEAP", 157 exp.IcebergProperty: lambda *_: "ICEBERG", 158 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 159 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 160 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 161 exp.Intersect: lambda self, e: self.set_operations(e), 162 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 163 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 164 exp.LanguageProperty: lambda self, e: self.naked_property(e), 165 exp.LocationProperty: lambda self, e: self.naked_property(e), 166 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 167 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 168 exp.NonClusteredColumnConstraint: lambda self, 169 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 170 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 171 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 172 exp.OnCommitProperty: lambda _, 173 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 174 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 175 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 176 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 177 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 178 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 179 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 180 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 181 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 182 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 183 exp.ProjectionPolicyColumnConstraint: lambda self, 184 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 185 exp.Put: lambda self, e: self.get_put_sql(e), 186 exp.RemoteWithConnectionModelProperty: lambda self, 187 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 188 exp.ReturnsProperty: lambda self, e: ( 189 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 190 ), 191 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 192 exp.SecureProperty: lambda *_: "SECURE", 193 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 194 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 195 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 196 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 197 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 198 exp.SqlReadWriteProperty: lambda _, e: e.name, 199 exp.SqlSecurityProperty: lambda _, 200 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 201 exp.StabilityProperty: lambda _, e: e.name, 202 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 203 exp.StreamingTableProperty: lambda *_: "STREAMING", 204 exp.StrictProperty: lambda *_: "STRICT", 205 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 206 exp.TableColumn: lambda self, e: self.sql(e.this), 207 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 208 exp.TemporaryProperty: lambda *_: "TEMPORARY", 209 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 210 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 211 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 212 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 213 exp.TransientProperty: lambda *_: "TRANSIENT", 214 exp.Union: lambda self, e: self.set_operations(e), 215 exp.UnloggedProperty: lambda *_: "UNLOGGED", 216 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 217 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 218 exp.Uuid: lambda *_: "UUID()", 219 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 220 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 221 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 222 exp.UtcTimestamp: lambda self, e: self.sql( 223 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 224 ), 225 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 226 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 227 exp.VolatileProperty: lambda *_: "VOLATILE", 228 exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})", 229 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 230 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 231 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 232 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 233 exp.ForceProperty: lambda *_: "FORCE", 234 } 235 236 # Whether null ordering is supported in order by 237 # True: Full Support, None: No support, False: No support for certain cases 238 # such as window specifications, aggregate functions etc 239 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 240 241 # Whether ignore nulls is inside the agg or outside. 242 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 243 IGNORE_NULLS_IN_FUNC = False 244 245 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 246 LOCKING_READS_SUPPORTED = False 247 248 # Whether the EXCEPT and INTERSECT operations can return duplicates 249 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 250 251 # Wrap derived values in parens, usually standard but spark doesn't support it 252 WRAP_DERIVED_VALUES = True 253 254 # Whether create function uses an AS before the RETURN 255 CREATE_FUNCTION_RETURN_AS = True 256 257 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 258 MATCHED_BY_SOURCE = True 259 260 # Whether the INTERVAL expression works only with values like '1 day' 261 SINGLE_STRING_INTERVAL = False 262 263 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 264 INTERVAL_ALLOWS_PLURAL_FORM = True 265 266 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 267 LIMIT_FETCH = "ALL" 268 269 # Whether limit and fetch allows expresions or just limits 270 LIMIT_ONLY_LITERALS = False 271 272 # Whether a table is allowed to be renamed with a db 273 RENAME_TABLE_WITH_DB = True 274 275 # The separator for grouping sets and rollups 276 GROUPINGS_SEP = "," 277 278 # The string used for creating an index on a table 279 INDEX_ON = "ON" 280 281 # Whether join hints should be generated 282 JOIN_HINTS = True 283 284 # Whether table hints should be generated 285 TABLE_HINTS = True 286 287 # Whether query hints should be generated 288 QUERY_HINTS = True 289 290 # What kind of separator to use for query hints 291 QUERY_HINT_SEP = ", " 292 293 # Whether comparing against booleans (e.g. x IS TRUE) is supported 294 IS_BOOL_ALLOWED = True 295 296 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 297 DUPLICATE_KEY_UPDATE_WITH_SET = True 298 299 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 300 LIMIT_IS_TOP = False 301 302 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 303 RETURNING_END = True 304 305 # Whether to generate an unquoted value for EXTRACT's date part argument 306 EXTRACT_ALLOWS_QUOTES = True 307 308 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 309 TZ_TO_WITH_TIME_ZONE = False 310 311 # Whether the NVL2 function is supported 312 NVL2_SUPPORTED = True 313 314 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 315 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 316 317 # Whether VALUES statements can be used as derived tables. 318 # MySQL 5 and Redshift do not allow this, so when False, it will convert 319 # SELECT * VALUES into SELECT UNION 320 VALUES_AS_TABLE = True 321 322 # Whether the word COLUMN is included when adding a column with ALTER TABLE 323 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 324 325 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 326 UNNEST_WITH_ORDINALITY = True 327 328 # Whether FILTER (WHERE cond) can be used for conditional aggregation 329 AGGREGATE_FILTER_SUPPORTED = True 330 331 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 332 SEMI_ANTI_JOIN_WITH_SIDE = True 333 334 # Whether to include the type of a computed column in the CREATE DDL 335 COMPUTED_COLUMN_WITH_TYPE = True 336 337 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 338 SUPPORTS_TABLE_COPY = True 339 340 # Whether parentheses are required around the table sample's expression 341 TABLESAMPLE_REQUIRES_PARENS = True 342 343 # Whether a table sample clause's size needs to be followed by the ROWS keyword 344 TABLESAMPLE_SIZE_IS_ROWS = True 345 346 # The keyword(s) to use when generating a sample clause 347 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 348 349 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 350 TABLESAMPLE_WITH_METHOD = True 351 352 # The keyword to use when specifying the seed of a sample clause 353 TABLESAMPLE_SEED_KEYWORD = "SEED" 354 355 # Whether COLLATE is a function instead of a binary operator 356 COLLATE_IS_FUNC = False 357 358 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 359 DATA_TYPE_SPECIFIERS_ALLOWED = False 360 361 # Whether conditions require booleans WHERE x = 0 vs WHERE x 362 ENSURE_BOOLS = False 363 364 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 365 CTE_RECURSIVE_KEYWORD_REQUIRED = True 366 367 # Whether CONCAT requires >1 arguments 368 SUPPORTS_SINGLE_ARG_CONCAT = True 369 370 # Whether LAST_DAY function supports a date part argument 371 LAST_DAY_SUPPORTS_DATE_PART = True 372 373 # Whether named columns are allowed in table aliases 374 SUPPORTS_TABLE_ALIAS_COLUMNS = True 375 376 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 377 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 378 379 # What delimiter to use for separating JSON key/value pairs 380 JSON_KEY_VALUE_PAIR_SEP = ":" 381 382 # INSERT OVERWRITE TABLE x override 383 INSERT_OVERWRITE = " OVERWRITE TABLE" 384 385 # Whether the SELECT .. INTO syntax is used instead of CTAS 386 SUPPORTS_SELECT_INTO = False 387 388 # Whether UNLOGGED tables can be created 389 SUPPORTS_UNLOGGED_TABLES = False 390 391 # Whether the CREATE TABLE LIKE statement is supported 392 SUPPORTS_CREATE_TABLE_LIKE = True 393 394 # Whether the LikeProperty needs to be specified inside of the schema clause 395 LIKE_PROPERTY_INSIDE_SCHEMA = False 396 397 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 398 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 399 MULTI_ARG_DISTINCT = True 400 401 # Whether the JSON extraction operators expect a value of type JSON 402 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 403 404 # Whether bracketed keys like ["foo"] are supported in JSON paths 405 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 406 407 # Whether to escape keys using single quotes in JSON paths 408 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 409 410 # The JSONPathPart expressions supported by this dialect 411 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 412 413 # Whether any(f(x) for x in array) can be implemented by this dialect 414 CAN_IMPLEMENT_ARRAY_ANY = False 415 416 # Whether the function TO_NUMBER is supported 417 SUPPORTS_TO_NUMBER = True 418 419 # Whether EXCLUDE in window specification is supported 420 SUPPORTS_WINDOW_EXCLUDE = False 421 422 # Whether or not set op modifiers apply to the outer set op or select. 423 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 424 # True means limit 1 happens after the set op, False means it it happens on y. 425 SET_OP_MODIFIERS = True 426 427 # Whether parameters from COPY statement are wrapped in parentheses 428 COPY_PARAMS_ARE_WRAPPED = True 429 430 # Whether values of params are set with "=" token or empty space 431 COPY_PARAMS_EQ_REQUIRED = False 432 433 # Whether COPY statement has INTO keyword 434 COPY_HAS_INTO_KEYWORD = True 435 436 # Whether the conditional TRY(expression) function is supported 437 TRY_SUPPORTED = True 438 439 # Whether the UESCAPE syntax in unicode strings is supported 440 SUPPORTS_UESCAPE = True 441 442 # Function used to replace escaped unicode codes in unicode strings 443 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 444 445 # The keyword to use when generating a star projection with excluded columns 446 STAR_EXCEPT = "EXCEPT" 447 448 # The HEX function name 449 HEX_FUNC = "HEX" 450 451 # The keywords to use when prefixing & separating WITH based properties 452 WITH_PROPERTIES_PREFIX = "WITH" 453 454 # Whether to quote the generated expression of exp.JsonPath 455 QUOTE_JSON_PATH = True 456 457 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 458 PAD_FILL_PATTERN_IS_REQUIRED = False 459 460 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 461 SUPPORTS_EXPLODING_PROJECTIONS = True 462 463 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 464 ARRAY_CONCAT_IS_VAR_LEN = True 465 466 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 467 SUPPORTS_CONVERT_TIMEZONE = False 468 469 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 470 SUPPORTS_MEDIAN = True 471 472 # Whether UNIX_SECONDS(timestamp) is supported 473 SUPPORTS_UNIX_SECONDS = False 474 475 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 476 ALTER_SET_WRAPPED = False 477 478 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 479 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 480 # TODO: The normalization should be done by default once we've tested it across all dialects. 481 NORMALIZE_EXTRACT_DATE_PARTS = False 482 483 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 484 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 485 486 # The function name of the exp.ArraySize expression 487 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 488 489 # The syntax to use when altering the type of a column 490 ALTER_SET_TYPE = "SET DATA TYPE" 491 492 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 493 # None -> Doesn't support it at all 494 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 495 # True (Postgres) -> Explicitly requires it 496 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 497 498 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 499 SUPPORTS_DECODE_CASE = True 500 501 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 502 SUPPORTS_BETWEEN_FLAGS = False 503 504 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 505 SUPPORTS_LIKE_QUANTIFIERS = True 506 507 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 508 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 509 510 TYPE_MAPPING = { 511 exp.DataType.Type.DATETIME2: "TIMESTAMP", 512 exp.DataType.Type.NCHAR: "CHAR", 513 exp.DataType.Type.NVARCHAR: "VARCHAR", 514 exp.DataType.Type.MEDIUMTEXT: "TEXT", 515 exp.DataType.Type.LONGTEXT: "TEXT", 516 exp.DataType.Type.TINYTEXT: "TEXT", 517 exp.DataType.Type.BLOB: "VARBINARY", 518 exp.DataType.Type.MEDIUMBLOB: "BLOB", 519 exp.DataType.Type.LONGBLOB: "BLOB", 520 exp.DataType.Type.TINYBLOB: "BLOB", 521 exp.DataType.Type.INET: "INET", 522 exp.DataType.Type.ROWVERSION: "VARBINARY", 523 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 524 } 525 526 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 527 528 TIME_PART_SINGULARS = { 529 "MICROSECONDS": "MICROSECOND", 530 "SECONDS": "SECOND", 531 "MINUTES": "MINUTE", 532 "HOURS": "HOUR", 533 "DAYS": "DAY", 534 "WEEKS": "WEEK", 535 "MONTHS": "MONTH", 536 "QUARTERS": "QUARTER", 537 "YEARS": "YEAR", 538 } 539 540 AFTER_HAVING_MODIFIER_TRANSFORMS = { 541 "cluster": lambda self, e: self.sql(e, "cluster"), 542 "distribute": lambda self, e: self.sql(e, "distribute"), 543 "sort": lambda self, e: self.sql(e, "sort"), 544 "windows": lambda self, e: ( 545 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 546 if e.args.get("windows") 547 else "" 548 ), 549 "qualify": lambda self, e: self.sql(e, "qualify"), 550 } 551 552 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 553 554 STRUCT_DELIMITER = ("<", ">") 555 556 PARAMETER_TOKEN = "@" 557 NAMED_PLACEHOLDER_TOKEN = ":" 558 559 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 560 561 PROPERTIES_LOCATION = { 562 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 564 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 568 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 570 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 573 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 577 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 579 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 580 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 582 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 586 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 590 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 591 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 592 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 593 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 594 exp.HeapProperty: exp.Properties.Location.POST_WITH, 595 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 596 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 597 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 600 exp.JournalProperty: exp.Properties.Location.POST_NAME, 601 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 606 exp.LogProperty: exp.Properties.Location.POST_NAME, 607 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 608 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 609 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 610 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 611 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 612 exp.Order: exp.Properties.Location.POST_SCHEMA, 613 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 615 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 617 exp.Property: exp.Properties.Location.POST_WITH, 618 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 620 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 626 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 628 exp.Set: exp.Properties.Location.POST_SCHEMA, 629 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 630 exp.SetProperty: exp.Properties.Location.POST_CREATE, 631 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 633 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 634 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 636 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 637 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 640 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 641 exp.Tags: exp.Properties.Location.POST_WITH, 642 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 643 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 644 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 645 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 646 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 647 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 648 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 649 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 650 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 651 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 652 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 653 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 654 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 655 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 656 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 657 } 658 659 # Keywords that can't be used as unquoted identifier names 660 RESERVED_KEYWORDS: t.Set[str] = set() 661 662 # Expressions whose comments are separated from them for better formatting 663 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 664 exp.Command, 665 exp.Create, 666 exp.Describe, 667 exp.Delete, 668 exp.Drop, 669 exp.From, 670 exp.Insert, 671 exp.Join, 672 exp.MultitableInserts, 673 exp.Order, 674 exp.Group, 675 exp.Having, 676 exp.Select, 677 exp.SetOperation, 678 exp.Update, 679 exp.Where, 680 exp.With, 681 ) 682 683 # Expressions that should not have their comments generated in maybe_comment 684 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 685 exp.Binary, 686 exp.SetOperation, 687 ) 688 689 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 690 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 691 exp.Column, 692 exp.Literal, 693 exp.Neg, 694 exp.Paren, 695 ) 696 697 PARAMETERIZABLE_TEXT_TYPES = { 698 exp.DataType.Type.NVARCHAR, 699 exp.DataType.Type.VARCHAR, 700 exp.DataType.Type.CHAR, 701 exp.DataType.Type.NCHAR, 702 } 703 704 # Expressions that need to have all CTEs under them bubbled up to them 705 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 706 707 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 708 709 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 710 711 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 712 713 __slots__ = ( 714 "pretty", 715 "identify", 716 "normalize", 717 "pad", 718 "_indent", 719 "normalize_functions", 720 "unsupported_level", 721 "max_unsupported", 722 "leading_comma", 723 "max_text_width", 724 "comments", 725 "dialect", 726 "unsupported_messages", 727 "_escaped_quote_end", 728 "_escaped_identifier_end", 729 "_next_name", 730 "_identifier_start", 731 "_identifier_end", 732 "_quote_json_path_key_using_brackets", 733 ) 734 735 def __init__( 736 self, 737 pretty: t.Optional[bool] = None, 738 identify: str | bool = False, 739 normalize: bool = False, 740 pad: int = 2, 741 indent: int = 2, 742 normalize_functions: t.Optional[str | bool] = None, 743 unsupported_level: ErrorLevel = ErrorLevel.WARN, 744 max_unsupported: int = 3, 745 leading_comma: bool = False, 746 max_text_width: int = 80, 747 comments: bool = True, 748 dialect: DialectType = None, 749 ): 750 import sqlglot 751 from sqlglot.dialects import Dialect 752 753 self.pretty = pretty if pretty is not None else sqlglot.pretty 754 self.identify = identify 755 self.normalize = normalize 756 self.pad = pad 757 self._indent = indent 758 self.unsupported_level = unsupported_level 759 self.max_unsupported = max_unsupported 760 self.leading_comma = leading_comma 761 self.max_text_width = max_text_width 762 self.comments = comments 763 self.dialect = Dialect.get_or_raise(dialect) 764 765 # This is both a Dialect property and a Generator argument, so we prioritize the latter 766 self.normalize_functions = ( 767 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 768 ) 769 770 self.unsupported_messages: t.List[str] = [] 771 self._escaped_quote_end: str = ( 772 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 773 ) 774 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 775 776 self._next_name = name_sequence("_t") 777 778 self._identifier_start = self.dialect.IDENTIFIER_START 779 self._identifier_end = self.dialect.IDENTIFIER_END 780 781 self._quote_json_path_key_using_brackets = True 782 783 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 784 """ 785 Generates the SQL string corresponding to the given syntax tree. 786 787 Args: 788 expression: The syntax tree. 789 copy: Whether to copy the expression. The generator performs mutations so 790 it is safer to copy. 791 792 Returns: 793 The SQL string corresponding to `expression`. 794 """ 795 if copy: 796 expression = expression.copy() 797 798 expression = self.preprocess(expression) 799 800 self.unsupported_messages = [] 801 sql = self.sql(expression).strip() 802 803 if self.pretty: 804 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 805 806 if self.unsupported_level == ErrorLevel.IGNORE: 807 return sql 808 809 if self.unsupported_level == ErrorLevel.WARN: 810 for msg in self.unsupported_messages: 811 logger.warning(msg) 812 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 813 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 814 815 return sql 816 817 def preprocess(self, expression: exp.Expression) -> exp.Expression: 818 """Apply generic preprocessing transformations to a given expression.""" 819 expression = self._move_ctes_to_top_level(expression) 820 821 if self.ENSURE_BOOLS: 822 from sqlglot.transforms import ensure_bools 823 824 expression = ensure_bools(expression) 825 826 return expression 827 828 def _move_ctes_to_top_level(self, expression: E) -> E: 829 if ( 830 not expression.parent 831 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 832 and any(node.parent is not expression for node in expression.find_all(exp.With)) 833 ): 834 from sqlglot.transforms import move_ctes_to_top_level 835 836 expression = move_ctes_to_top_level(expression) 837 return expression 838 839 def unsupported(self, message: str) -> None: 840 if self.unsupported_level == ErrorLevel.IMMEDIATE: 841 raise UnsupportedError(message) 842 self.unsupported_messages.append(message) 843 844 def sep(self, sep: str = " ") -> str: 845 return f"{sep.strip()}\n" if self.pretty else sep 846 847 def seg(self, sql: str, sep: str = " ") -> str: 848 return f"{self.sep(sep)}{sql}" 849 850 def sanitize_comment(self, comment: str) -> str: 851 comment = " " + comment if comment[0].strip() else comment 852 comment = comment + " " if comment[-1].strip() else comment 853 854 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 855 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 856 comment = comment.replace("*/", "* /") 857 858 return comment 859 860 def maybe_comment( 861 self, 862 sql: str, 863 expression: t.Optional[exp.Expression] = None, 864 comments: t.Optional[t.List[str]] = None, 865 separated: bool = False, 866 ) -> str: 867 comments = ( 868 ((expression and expression.comments) if comments is None else comments) # type: ignore 869 if self.comments 870 else None 871 ) 872 873 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 874 return sql 875 876 comments_sql = " ".join( 877 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 878 ) 879 880 if not comments_sql: 881 return sql 882 883 comments_sql = self._replace_line_breaks(comments_sql) 884 885 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 886 return ( 887 f"{self.sep()}{comments_sql}{sql}" 888 if not sql or sql[0].isspace() 889 else f"{comments_sql}{self.sep()}{sql}" 890 ) 891 892 return f"{sql} {comments_sql}" 893 894 def wrap(self, expression: exp.Expression | str) -> str: 895 this_sql = ( 896 self.sql(expression) 897 if isinstance(expression, exp.UNWRAPPED_QUERIES) 898 else self.sql(expression, "this") 899 ) 900 if not this_sql: 901 return "()" 902 903 this_sql = self.indent(this_sql, level=1, pad=0) 904 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 905 906 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 907 original = self.identify 908 self.identify = False 909 result = func(*args, **kwargs) 910 self.identify = original 911 return result 912 913 def normalize_func(self, name: str) -> str: 914 if self.normalize_functions == "upper" or self.normalize_functions is True: 915 return name.upper() 916 if self.normalize_functions == "lower": 917 return name.lower() 918 return name 919 920 def indent( 921 self, 922 sql: str, 923 level: int = 0, 924 pad: t.Optional[int] = None, 925 skip_first: bool = False, 926 skip_last: bool = False, 927 ) -> str: 928 if not self.pretty or not sql: 929 return sql 930 931 pad = self.pad if pad is None else pad 932 lines = sql.split("\n") 933 934 return "\n".join( 935 ( 936 line 937 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 938 else f"{' ' * (level * self._indent + pad)}{line}" 939 ) 940 for i, line in enumerate(lines) 941 ) 942 943 def sql( 944 self, 945 expression: t.Optional[str | exp.Expression], 946 key: t.Optional[str] = None, 947 comment: bool = True, 948 ) -> str: 949 if not expression: 950 return "" 951 952 if isinstance(expression, str): 953 return expression 954 955 if key: 956 value = expression.args.get(key) 957 if value: 958 return self.sql(value) 959 return "" 960 961 transform = self.TRANSFORMS.get(expression.__class__) 962 963 if callable(transform): 964 sql = transform(self, expression) 965 elif isinstance(expression, exp.Expression): 966 exp_handler_name = f"{expression.key}_sql" 967 968 if hasattr(self, exp_handler_name): 969 sql = getattr(self, exp_handler_name)(expression) 970 elif isinstance(expression, exp.Func): 971 sql = self.function_fallback_sql(expression) 972 elif isinstance(expression, exp.Property): 973 sql = self.property_sql(expression) 974 else: 975 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 976 else: 977 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 978 979 return self.maybe_comment(sql, expression) if self.comments and comment else sql 980 981 def uncache_sql(self, expression: exp.Uncache) -> str: 982 table = self.sql(expression, "this") 983 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 984 return f"UNCACHE TABLE{exists_sql} {table}" 985 986 def cache_sql(self, expression: exp.Cache) -> str: 987 lazy = " LAZY" if expression.args.get("lazy") else "" 988 table = self.sql(expression, "this") 989 options = expression.args.get("options") 990 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 991 sql = self.sql(expression, "expression") 992 sql = f" AS{self.sep()}{sql}" if sql else "" 993 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 994 return self.prepend_ctes(expression, sql) 995 996 def characterset_sql(self, expression: exp.CharacterSet) -> str: 997 if isinstance(expression.parent, exp.Cast): 998 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 999 default = "DEFAULT " if expression.args.get("default") else "" 1000 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1001 1002 def column_parts(self, expression: exp.Column) -> str: 1003 return ".".join( 1004 self.sql(part) 1005 for part in ( 1006 expression.args.get("catalog"), 1007 expression.args.get("db"), 1008 expression.args.get("table"), 1009 expression.args.get("this"), 1010 ) 1011 if part 1012 ) 1013 1014 def column_sql(self, expression: exp.Column) -> str: 1015 join_mark = " (+)" if expression.args.get("join_mark") else "" 1016 1017 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1018 join_mark = "" 1019 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1020 1021 return f"{self.column_parts(expression)}{join_mark}" 1022 1023 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1024 this = self.sql(expression, "this") 1025 this = f" {this}" if this else "" 1026 position = self.sql(expression, "position") 1027 return f"{position}{this}" 1028 1029 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1030 column = self.sql(expression, "this") 1031 kind = self.sql(expression, "kind") 1032 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1033 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1034 kind = f"{sep}{kind}" if kind else "" 1035 constraints = f" {constraints}" if constraints else "" 1036 position = self.sql(expression, "position") 1037 position = f" {position}" if position else "" 1038 1039 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1040 kind = "" 1041 1042 return f"{exists}{column}{kind}{constraints}{position}" 1043 1044 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1045 this = self.sql(expression, "this") 1046 kind_sql = self.sql(expression, "kind").strip() 1047 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1048 1049 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1050 this = self.sql(expression, "this") 1051 if expression.args.get("not_null"): 1052 persisted = " PERSISTED NOT NULL" 1053 elif expression.args.get("persisted"): 1054 persisted = " PERSISTED" 1055 else: 1056 persisted = "" 1057 1058 return f"AS {this}{persisted}" 1059 1060 def autoincrementcolumnconstraint_sql(self, _) -> str: 1061 return self.token_sql(TokenType.AUTO_INCREMENT) 1062 1063 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1064 if isinstance(expression.this, list): 1065 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1066 else: 1067 this = self.sql(expression, "this") 1068 1069 return f"COMPRESS {this}" 1070 1071 def generatedasidentitycolumnconstraint_sql( 1072 self, expression: exp.GeneratedAsIdentityColumnConstraint 1073 ) -> str: 1074 this = "" 1075 if expression.this is not None: 1076 on_null = " ON NULL" if expression.args.get("on_null") else "" 1077 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1078 1079 start = expression.args.get("start") 1080 start = f"START WITH {start}" if start else "" 1081 increment = expression.args.get("increment") 1082 increment = f" INCREMENT BY {increment}" if increment else "" 1083 minvalue = expression.args.get("minvalue") 1084 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1085 maxvalue = expression.args.get("maxvalue") 1086 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1087 cycle = expression.args.get("cycle") 1088 cycle_sql = "" 1089 1090 if cycle is not None: 1091 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1092 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1093 1094 sequence_opts = "" 1095 if start or increment or cycle_sql: 1096 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1097 sequence_opts = f" ({sequence_opts.strip()})" 1098 1099 expr = self.sql(expression, "expression") 1100 expr = f"({expr})" if expr else "IDENTITY" 1101 1102 return f"GENERATED{this} AS {expr}{sequence_opts}" 1103 1104 def generatedasrowcolumnconstraint_sql( 1105 self, expression: exp.GeneratedAsRowColumnConstraint 1106 ) -> str: 1107 start = "START" if expression.args.get("start") else "END" 1108 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1109 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1110 1111 def periodforsystemtimeconstraint_sql( 1112 self, expression: exp.PeriodForSystemTimeConstraint 1113 ) -> str: 1114 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1115 1116 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1117 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1118 1119 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1120 desc = expression.args.get("desc") 1121 if desc is not None: 1122 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1123 options = self.expressions(expression, key="options", flat=True, sep=" ") 1124 options = f" {options}" if options else "" 1125 return f"PRIMARY KEY{options}" 1126 1127 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1128 this = self.sql(expression, "this") 1129 this = f" {this}" if this else "" 1130 index_type = expression.args.get("index_type") 1131 index_type = f" USING {index_type}" if index_type else "" 1132 on_conflict = self.sql(expression, "on_conflict") 1133 on_conflict = f" {on_conflict}" if on_conflict else "" 1134 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1135 options = self.expressions(expression, key="options", flat=True, sep=" ") 1136 options = f" {options}" if options else "" 1137 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1138 1139 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1140 return self.sql(expression, "this") 1141 1142 def create_sql(self, expression: exp.Create) -> str: 1143 kind = self.sql(expression, "kind") 1144 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1145 properties = expression.args.get("properties") 1146 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1147 1148 this = self.createable_sql(expression, properties_locs) 1149 1150 properties_sql = "" 1151 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1152 exp.Properties.Location.POST_WITH 1153 ): 1154 props_ast = exp.Properties( 1155 expressions=[ 1156 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1157 *properties_locs[exp.Properties.Location.POST_WITH], 1158 ] 1159 ) 1160 props_ast.parent = expression 1161 properties_sql = self.sql(props_ast) 1162 1163 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1164 properties_sql = self.sep() + properties_sql 1165 elif not self.pretty: 1166 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1167 properties_sql = f" {properties_sql}" 1168 1169 begin = " BEGIN" if expression.args.get("begin") else "" 1170 end = " END" if expression.args.get("end") else "" 1171 1172 expression_sql = self.sql(expression, "expression") 1173 if expression_sql: 1174 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1175 1176 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1177 postalias_props_sql = "" 1178 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1179 postalias_props_sql = self.properties( 1180 exp.Properties( 1181 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1182 ), 1183 wrapped=False, 1184 ) 1185 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1186 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1187 1188 postindex_props_sql = "" 1189 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1190 postindex_props_sql = self.properties( 1191 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1192 wrapped=False, 1193 prefix=" ", 1194 ) 1195 1196 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1197 indexes = f" {indexes}" if indexes else "" 1198 index_sql = indexes + postindex_props_sql 1199 1200 replace = " OR REPLACE" if expression.args.get("replace") else "" 1201 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1202 unique = " UNIQUE" if expression.args.get("unique") else "" 1203 1204 clustered = expression.args.get("clustered") 1205 if clustered is None: 1206 clustered_sql = "" 1207 elif clustered: 1208 clustered_sql = " CLUSTERED COLUMNSTORE" 1209 else: 1210 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1211 1212 postcreate_props_sql = "" 1213 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1214 postcreate_props_sql = self.properties( 1215 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1216 sep=" ", 1217 prefix=" ", 1218 wrapped=False, 1219 ) 1220 1221 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1222 1223 postexpression_props_sql = "" 1224 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1225 postexpression_props_sql = self.properties( 1226 exp.Properties( 1227 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1228 ), 1229 sep=" ", 1230 prefix=" ", 1231 wrapped=False, 1232 ) 1233 1234 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1235 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1236 no_schema_binding = ( 1237 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1238 ) 1239 1240 clone = self.sql(expression, "clone") 1241 clone = f" {clone}" if clone else "" 1242 1243 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1244 properties_expression = f"{expression_sql}{properties_sql}" 1245 else: 1246 properties_expression = f"{properties_sql}{expression_sql}" 1247 1248 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1249 return self.prepend_ctes(expression, expression_sql) 1250 1251 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1252 start = self.sql(expression, "start") 1253 start = f"START WITH {start}" if start else "" 1254 increment = self.sql(expression, "increment") 1255 increment = f" INCREMENT BY {increment}" if increment else "" 1256 minvalue = self.sql(expression, "minvalue") 1257 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1258 maxvalue = self.sql(expression, "maxvalue") 1259 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1260 owned = self.sql(expression, "owned") 1261 owned = f" OWNED BY {owned}" if owned else "" 1262 1263 cache = expression.args.get("cache") 1264 if cache is None: 1265 cache_str = "" 1266 elif cache is True: 1267 cache_str = " CACHE" 1268 else: 1269 cache_str = f" CACHE {cache}" 1270 1271 options = self.expressions(expression, key="options", flat=True, sep=" ") 1272 options = f" {options}" if options else "" 1273 1274 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1275 1276 def clone_sql(self, expression: exp.Clone) -> str: 1277 this = self.sql(expression, "this") 1278 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1279 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1280 return f"{shallow}{keyword} {this}" 1281 1282 def describe_sql(self, expression: exp.Describe) -> str: 1283 style = expression.args.get("style") 1284 style = f" {style}" if style else "" 1285 partition = self.sql(expression, "partition") 1286 partition = f" {partition}" if partition else "" 1287 format = self.sql(expression, "format") 1288 format = f" {format}" if format else "" 1289 1290 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1291 1292 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1293 tag = self.sql(expression, "tag") 1294 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1295 1296 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1297 with_ = self.sql(expression, "with") 1298 if with_: 1299 sql = f"{with_}{self.sep()}{sql}" 1300 return sql 1301 1302 def with_sql(self, expression: exp.With) -> str: 1303 sql = self.expressions(expression, flat=True) 1304 recursive = ( 1305 "RECURSIVE " 1306 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1307 else "" 1308 ) 1309 search = self.sql(expression, "search") 1310 search = f" {search}" if search else "" 1311 1312 return f"WITH {recursive}{sql}{search}" 1313 1314 def cte_sql(self, expression: exp.CTE) -> str: 1315 alias = expression.args.get("alias") 1316 if alias: 1317 alias.add_comments(expression.pop_comments()) 1318 1319 alias_sql = self.sql(expression, "alias") 1320 1321 materialized = expression.args.get("materialized") 1322 if materialized is False: 1323 materialized = "NOT MATERIALIZED " 1324 elif materialized: 1325 materialized = "MATERIALIZED " 1326 1327 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1328 1329 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1330 alias = self.sql(expression, "this") 1331 columns = self.expressions(expression, key="columns", flat=True) 1332 columns = f"({columns})" if columns else "" 1333 1334 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1335 columns = "" 1336 self.unsupported("Named columns are not supported in table alias.") 1337 1338 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1339 alias = self._next_name() 1340 1341 return f"{alias}{columns}" 1342 1343 def bitstring_sql(self, expression: exp.BitString) -> str: 1344 this = self.sql(expression, "this") 1345 if self.dialect.BIT_START: 1346 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1347 return f"{int(this, 2)}" 1348 1349 def hexstring_sql( 1350 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1351 ) -> str: 1352 this = self.sql(expression, "this") 1353 is_integer_type = expression.args.get("is_integer") 1354 1355 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1356 not self.dialect.HEX_START and not binary_function_repr 1357 ): 1358 # Integer representation will be returned if: 1359 # - The read dialect treats the hex value as integer literal but not the write 1360 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1361 return f"{int(this, 16)}" 1362 1363 if not is_integer_type: 1364 # Read dialect treats the hex value as BINARY/BLOB 1365 if binary_function_repr: 1366 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1367 return self.func(binary_function_repr, exp.Literal.string(this)) 1368 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1369 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1370 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1371 1372 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1373 1374 def bytestring_sql(self, expression: exp.ByteString) -> str: 1375 this = self.sql(expression, "this") 1376 if self.dialect.BYTE_START: 1377 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1378 return this 1379 1380 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1381 this = self.sql(expression, "this") 1382 escape = expression.args.get("escape") 1383 1384 if self.dialect.UNICODE_START: 1385 escape_substitute = r"\\\1" 1386 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1387 else: 1388 escape_substitute = r"\\u\1" 1389 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1390 1391 if escape: 1392 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1393 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1394 else: 1395 escape_pattern = ESCAPED_UNICODE_RE 1396 escape_sql = "" 1397 1398 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1399 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1400 1401 return f"{left_quote}{this}{right_quote}{escape_sql}" 1402 1403 def rawstring_sql(self, expression: exp.RawString) -> str: 1404 string = expression.this 1405 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1406 string = string.replace("\\", "\\\\") 1407 1408 string = self.escape_str(string, escape_backslash=False) 1409 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1410 1411 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1412 this = self.sql(expression, "this") 1413 specifier = self.sql(expression, "expression") 1414 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1415 return f"{this}{specifier}" 1416 1417 def datatype_sql(self, expression: exp.DataType) -> str: 1418 nested = "" 1419 values = "" 1420 interior = self.expressions(expression, flat=True) 1421 1422 type_value = expression.this 1423 if type_value in self.UNSUPPORTED_TYPES: 1424 self.unsupported( 1425 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1426 ) 1427 1428 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1429 type_sql = self.sql(expression, "kind") 1430 else: 1431 type_sql = ( 1432 self.TYPE_MAPPING.get(type_value, type_value.value) 1433 if isinstance(type_value, exp.DataType.Type) 1434 else type_value 1435 ) 1436 1437 if interior: 1438 if expression.args.get("nested"): 1439 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1440 if expression.args.get("values") is not None: 1441 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1442 values = self.expressions(expression, key="values", flat=True) 1443 values = f"{delimiters[0]}{values}{delimiters[1]}" 1444 elif type_value == exp.DataType.Type.INTERVAL: 1445 nested = f" {interior}" 1446 else: 1447 nested = f"({interior})" 1448 1449 type_sql = f"{type_sql}{nested}{values}" 1450 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1451 exp.DataType.Type.TIMETZ, 1452 exp.DataType.Type.TIMESTAMPTZ, 1453 ): 1454 type_sql = f"{type_sql} WITH TIME ZONE" 1455 1456 return type_sql 1457 1458 def directory_sql(self, expression: exp.Directory) -> str: 1459 local = "LOCAL " if expression.args.get("local") else "" 1460 row_format = self.sql(expression, "row_format") 1461 row_format = f" {row_format}" if row_format else "" 1462 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1463 1464 def delete_sql(self, expression: exp.Delete) -> str: 1465 this = self.sql(expression, "this") 1466 this = f" FROM {this}" if this else "" 1467 using = self.sql(expression, "using") 1468 using = f" USING {using}" if using else "" 1469 cluster = self.sql(expression, "cluster") 1470 cluster = f" {cluster}" if cluster else "" 1471 where = self.sql(expression, "where") 1472 returning = self.sql(expression, "returning") 1473 limit = self.sql(expression, "limit") 1474 tables = self.expressions(expression, key="tables") 1475 tables = f" {tables}" if tables else "" 1476 if self.RETURNING_END: 1477 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1478 else: 1479 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1480 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1481 1482 def drop_sql(self, expression: exp.Drop) -> str: 1483 this = self.sql(expression, "this") 1484 expressions = self.expressions(expression, flat=True) 1485 expressions = f" ({expressions})" if expressions else "" 1486 kind = expression.args["kind"] 1487 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1488 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1489 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1490 on_cluster = self.sql(expression, "cluster") 1491 on_cluster = f" {on_cluster}" if on_cluster else "" 1492 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1493 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1494 cascade = " CASCADE" if expression.args.get("cascade") else "" 1495 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1496 purge = " PURGE" if expression.args.get("purge") else "" 1497 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1498 1499 def set_operation(self, expression: exp.SetOperation) -> str: 1500 op_type = type(expression) 1501 op_name = op_type.key.upper() 1502 1503 distinct = expression.args.get("distinct") 1504 if ( 1505 distinct is False 1506 and op_type in (exp.Except, exp.Intersect) 1507 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1508 ): 1509 self.unsupported(f"{op_name} ALL is not supported") 1510 1511 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1512 1513 if distinct is None: 1514 distinct = default_distinct 1515 if distinct is None: 1516 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1517 1518 if distinct is default_distinct: 1519 distinct_or_all = "" 1520 else: 1521 distinct_or_all = " DISTINCT" if distinct else " ALL" 1522 1523 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1524 side_kind = f"{side_kind} " if side_kind else "" 1525 1526 by_name = " BY NAME" if expression.args.get("by_name") else "" 1527 on = self.expressions(expression, key="on", flat=True) 1528 on = f" ON ({on})" if on else "" 1529 1530 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1531 1532 def set_operations(self, expression: exp.SetOperation) -> str: 1533 if not self.SET_OP_MODIFIERS: 1534 limit = expression.args.get("limit") 1535 order = expression.args.get("order") 1536 1537 if limit or order: 1538 select = self._move_ctes_to_top_level( 1539 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1540 ) 1541 1542 if limit: 1543 select = select.limit(limit.pop(), copy=False) 1544 if order: 1545 select = select.order_by(order.pop(), copy=False) 1546 return self.sql(select) 1547 1548 sqls: t.List[str] = [] 1549 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1550 1551 while stack: 1552 node = stack.pop() 1553 1554 if isinstance(node, exp.SetOperation): 1555 stack.append(node.expression) 1556 stack.append( 1557 self.maybe_comment( 1558 self.set_operation(node), comments=node.comments, separated=True 1559 ) 1560 ) 1561 stack.append(node.this) 1562 else: 1563 sqls.append(self.sql(node)) 1564 1565 this = self.sep().join(sqls) 1566 this = self.query_modifiers(expression, this) 1567 return self.prepend_ctes(expression, this) 1568 1569 def fetch_sql(self, expression: exp.Fetch) -> str: 1570 direction = expression.args.get("direction") 1571 direction = f" {direction}" if direction else "" 1572 count = self.sql(expression, "count") 1573 count = f" {count}" if count else "" 1574 limit_options = self.sql(expression, "limit_options") 1575 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1576 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1577 1578 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1579 percent = " PERCENT" if expression.args.get("percent") else "" 1580 rows = " ROWS" if expression.args.get("rows") else "" 1581 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1582 if not with_ties and rows: 1583 with_ties = " ONLY" 1584 return f"{percent}{rows}{with_ties}" 1585 1586 def filter_sql(self, expression: exp.Filter) -> str: 1587 if self.AGGREGATE_FILTER_SUPPORTED: 1588 this = self.sql(expression, "this") 1589 where = self.sql(expression, "expression").strip() 1590 return f"{this} FILTER({where})" 1591 1592 agg = expression.this 1593 agg_arg = agg.this 1594 cond = expression.expression.this 1595 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1596 return self.sql(agg) 1597 1598 def hint_sql(self, expression: exp.Hint) -> str: 1599 if not self.QUERY_HINTS: 1600 self.unsupported("Hints are not supported") 1601 return "" 1602 1603 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1604 1605 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1606 using = self.sql(expression, "using") 1607 using = f" USING {using}" if using else "" 1608 columns = self.expressions(expression, key="columns", flat=True) 1609 columns = f"({columns})" if columns else "" 1610 partition_by = self.expressions(expression, key="partition_by", flat=True) 1611 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1612 where = self.sql(expression, "where") 1613 include = self.expressions(expression, key="include", flat=True) 1614 if include: 1615 include = f" INCLUDE ({include})" 1616 with_storage = self.expressions(expression, key="with_storage", flat=True) 1617 with_storage = f" WITH ({with_storage})" if with_storage else "" 1618 tablespace = self.sql(expression, "tablespace") 1619 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1620 on = self.sql(expression, "on") 1621 on = f" ON {on}" if on else "" 1622 1623 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1624 1625 def index_sql(self, expression: exp.Index) -> str: 1626 unique = "UNIQUE " if expression.args.get("unique") else "" 1627 primary = "PRIMARY " if expression.args.get("primary") else "" 1628 amp = "AMP " if expression.args.get("amp") else "" 1629 name = self.sql(expression, "this") 1630 name = f"{name} " if name else "" 1631 table = self.sql(expression, "table") 1632 table = f"{self.INDEX_ON} {table}" if table else "" 1633 1634 index = "INDEX " if not table else "" 1635 1636 params = self.sql(expression, "params") 1637 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1638 1639 def identifier_sql(self, expression: exp.Identifier) -> str: 1640 text = expression.name 1641 lower = text.lower() 1642 text = lower if self.normalize and not expression.quoted else text 1643 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1644 if ( 1645 expression.quoted 1646 or self.dialect.can_identify(text, self.identify) 1647 or lower in self.RESERVED_KEYWORDS 1648 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1649 ): 1650 text = f"{self._identifier_start}{text}{self._identifier_end}" 1651 return text 1652 1653 def hex_sql(self, expression: exp.Hex) -> str: 1654 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1655 if self.dialect.HEX_LOWERCASE: 1656 text = self.func("LOWER", text) 1657 1658 return text 1659 1660 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1661 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1662 if not self.dialect.HEX_LOWERCASE: 1663 text = self.func("LOWER", text) 1664 return text 1665 1666 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1667 input_format = self.sql(expression, "input_format") 1668 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1669 output_format = self.sql(expression, "output_format") 1670 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1671 return self.sep().join((input_format, output_format)) 1672 1673 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1674 string = self.sql(exp.Literal.string(expression.name)) 1675 return f"{prefix}{string}" 1676 1677 def partition_sql(self, expression: exp.Partition) -> str: 1678 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1679 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1680 1681 def properties_sql(self, expression: exp.Properties) -> str: 1682 root_properties = [] 1683 with_properties = [] 1684 1685 for p in expression.expressions: 1686 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1687 if p_loc == exp.Properties.Location.POST_WITH: 1688 with_properties.append(p) 1689 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1690 root_properties.append(p) 1691 1692 root_props_ast = exp.Properties(expressions=root_properties) 1693 root_props_ast.parent = expression.parent 1694 1695 with_props_ast = exp.Properties(expressions=with_properties) 1696 with_props_ast.parent = expression.parent 1697 1698 root_props = self.root_properties(root_props_ast) 1699 with_props = self.with_properties(with_props_ast) 1700 1701 if root_props and with_props and not self.pretty: 1702 with_props = " " + with_props 1703 1704 return root_props + with_props 1705 1706 def root_properties(self, properties: exp.Properties) -> str: 1707 if properties.expressions: 1708 return self.expressions(properties, indent=False, sep=" ") 1709 return "" 1710 1711 def properties( 1712 self, 1713 properties: exp.Properties, 1714 prefix: str = "", 1715 sep: str = ", ", 1716 suffix: str = "", 1717 wrapped: bool = True, 1718 ) -> str: 1719 if properties.expressions: 1720 expressions = self.expressions(properties, sep=sep, indent=False) 1721 if expressions: 1722 expressions = self.wrap(expressions) if wrapped else expressions 1723 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1724 return "" 1725 1726 def with_properties(self, properties: exp.Properties) -> str: 1727 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1728 1729 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1730 properties_locs = defaultdict(list) 1731 for p in properties.expressions: 1732 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1733 if p_loc != exp.Properties.Location.UNSUPPORTED: 1734 properties_locs[p_loc].append(p) 1735 else: 1736 self.unsupported(f"Unsupported property {p.key}") 1737 1738 return properties_locs 1739 1740 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1741 if isinstance(expression.this, exp.Dot): 1742 return self.sql(expression, "this") 1743 return f"'{expression.name}'" if string_key else expression.name 1744 1745 def property_sql(self, expression: exp.Property) -> str: 1746 property_cls = expression.__class__ 1747 if property_cls == exp.Property: 1748 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1749 1750 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1751 if not property_name: 1752 self.unsupported(f"Unsupported property {expression.key}") 1753 1754 return f"{property_name}={self.sql(expression, 'this')}" 1755 1756 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1757 if self.SUPPORTS_CREATE_TABLE_LIKE: 1758 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1759 options = f" {options}" if options else "" 1760 1761 like = f"LIKE {self.sql(expression, 'this')}{options}" 1762 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1763 like = f"({like})" 1764 1765 return like 1766 1767 if expression.expressions: 1768 self.unsupported("Transpilation of LIKE property options is unsupported") 1769 1770 select = exp.select("*").from_(expression.this).limit(0) 1771 return f"AS {self.sql(select)}" 1772 1773 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1774 no = "NO " if expression.args.get("no") else "" 1775 protection = " PROTECTION" if expression.args.get("protection") else "" 1776 return f"{no}FALLBACK{protection}" 1777 1778 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1779 no = "NO " if expression.args.get("no") else "" 1780 local = expression.args.get("local") 1781 local = f"{local} " if local else "" 1782 dual = "DUAL " if expression.args.get("dual") else "" 1783 before = "BEFORE " if expression.args.get("before") else "" 1784 after = "AFTER " if expression.args.get("after") else "" 1785 return f"{no}{local}{dual}{before}{after}JOURNAL" 1786 1787 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1788 freespace = self.sql(expression, "this") 1789 percent = " PERCENT" if expression.args.get("percent") else "" 1790 return f"FREESPACE={freespace}{percent}" 1791 1792 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1793 if expression.args.get("default"): 1794 property = "DEFAULT" 1795 elif expression.args.get("on"): 1796 property = "ON" 1797 else: 1798 property = "OFF" 1799 return f"CHECKSUM={property}" 1800 1801 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1802 if expression.args.get("no"): 1803 return "NO MERGEBLOCKRATIO" 1804 if expression.args.get("default"): 1805 return "DEFAULT MERGEBLOCKRATIO" 1806 1807 percent = " PERCENT" if expression.args.get("percent") else "" 1808 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1809 1810 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1811 default = expression.args.get("default") 1812 minimum = expression.args.get("minimum") 1813 maximum = expression.args.get("maximum") 1814 if default or minimum or maximum: 1815 if default: 1816 prop = "DEFAULT" 1817 elif minimum: 1818 prop = "MINIMUM" 1819 else: 1820 prop = "MAXIMUM" 1821 return f"{prop} DATABLOCKSIZE" 1822 units = expression.args.get("units") 1823 units = f" {units}" if units else "" 1824 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1825 1826 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1827 autotemp = expression.args.get("autotemp") 1828 always = expression.args.get("always") 1829 default = expression.args.get("default") 1830 manual = expression.args.get("manual") 1831 never = expression.args.get("never") 1832 1833 if autotemp is not None: 1834 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1835 elif always: 1836 prop = "ALWAYS" 1837 elif default: 1838 prop = "DEFAULT" 1839 elif manual: 1840 prop = "MANUAL" 1841 elif never: 1842 prop = "NEVER" 1843 return f"BLOCKCOMPRESSION={prop}" 1844 1845 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1846 no = expression.args.get("no") 1847 no = " NO" if no else "" 1848 concurrent = expression.args.get("concurrent") 1849 concurrent = " CONCURRENT" if concurrent else "" 1850 target = self.sql(expression, "target") 1851 target = f" {target}" if target else "" 1852 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1853 1854 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1855 if isinstance(expression.this, list): 1856 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1857 if expression.this: 1858 modulus = self.sql(expression, "this") 1859 remainder = self.sql(expression, "expression") 1860 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1861 1862 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1863 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1864 return f"FROM ({from_expressions}) TO ({to_expressions})" 1865 1866 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1867 this = self.sql(expression, "this") 1868 1869 for_values_or_default = expression.expression 1870 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1871 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1872 else: 1873 for_values_or_default = " DEFAULT" 1874 1875 return f"PARTITION OF {this}{for_values_or_default}" 1876 1877 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1878 kind = expression.args.get("kind") 1879 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1880 for_or_in = expression.args.get("for_or_in") 1881 for_or_in = f" {for_or_in}" if for_or_in else "" 1882 lock_type = expression.args.get("lock_type") 1883 override = " OVERRIDE" if expression.args.get("override") else "" 1884 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1885 1886 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1887 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1888 statistics = expression.args.get("statistics") 1889 statistics_sql = "" 1890 if statistics is not None: 1891 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1892 return f"{data_sql}{statistics_sql}" 1893 1894 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1895 this = self.sql(expression, "this") 1896 this = f"HISTORY_TABLE={this}" if this else "" 1897 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1898 data_consistency = ( 1899 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1900 ) 1901 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1902 retention_period = ( 1903 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1904 ) 1905 1906 if this: 1907 on_sql = self.func("ON", this, data_consistency, retention_period) 1908 else: 1909 on_sql = "ON" if expression.args.get("on") else "OFF" 1910 1911 sql = f"SYSTEM_VERSIONING={on_sql}" 1912 1913 return f"WITH({sql})" if expression.args.get("with") else sql 1914 1915 def insert_sql(self, expression: exp.Insert) -> str: 1916 hint = self.sql(expression, "hint") 1917 overwrite = expression.args.get("overwrite") 1918 1919 if isinstance(expression.this, exp.Directory): 1920 this = " OVERWRITE" if overwrite else " INTO" 1921 else: 1922 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1923 1924 stored = self.sql(expression, "stored") 1925 stored = f" {stored}" if stored else "" 1926 alternative = expression.args.get("alternative") 1927 alternative = f" OR {alternative}" if alternative else "" 1928 ignore = " IGNORE" if expression.args.get("ignore") else "" 1929 is_function = expression.args.get("is_function") 1930 if is_function: 1931 this = f"{this} FUNCTION" 1932 this = f"{this} {self.sql(expression, 'this')}" 1933 1934 exists = " IF EXISTS" if expression.args.get("exists") else "" 1935 where = self.sql(expression, "where") 1936 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1937 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1938 on_conflict = self.sql(expression, "conflict") 1939 on_conflict = f" {on_conflict}" if on_conflict else "" 1940 by_name = " BY NAME" if expression.args.get("by_name") else "" 1941 returning = self.sql(expression, "returning") 1942 1943 if self.RETURNING_END: 1944 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1945 else: 1946 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1947 1948 partition_by = self.sql(expression, "partition") 1949 partition_by = f" {partition_by}" if partition_by else "" 1950 settings = self.sql(expression, "settings") 1951 settings = f" {settings}" if settings else "" 1952 1953 source = self.sql(expression, "source") 1954 source = f"TABLE {source}" if source else "" 1955 1956 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1957 return self.prepend_ctes(expression, sql) 1958 1959 def introducer_sql(self, expression: exp.Introducer) -> str: 1960 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1961 1962 def kill_sql(self, expression: exp.Kill) -> str: 1963 kind = self.sql(expression, "kind") 1964 kind = f" {kind}" if kind else "" 1965 this = self.sql(expression, "this") 1966 this = f" {this}" if this else "" 1967 return f"KILL{kind}{this}" 1968 1969 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1970 return expression.name 1971 1972 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1973 return expression.name 1974 1975 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1976 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1977 1978 constraint = self.sql(expression, "constraint") 1979 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1980 1981 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1982 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1983 action = self.sql(expression, "action") 1984 1985 expressions = self.expressions(expression, flat=True) 1986 if expressions: 1987 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1988 expressions = f" {set_keyword}{expressions}" 1989 1990 where = self.sql(expression, "where") 1991 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1992 1993 def returning_sql(self, expression: exp.Returning) -> str: 1994 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1995 1996 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1997 fields = self.sql(expression, "fields") 1998 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1999 escaped = self.sql(expression, "escaped") 2000 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2001 items = self.sql(expression, "collection_items") 2002 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2003 keys = self.sql(expression, "map_keys") 2004 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2005 lines = self.sql(expression, "lines") 2006 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2007 null = self.sql(expression, "null") 2008 null = f" NULL DEFINED AS {null}" if null else "" 2009 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2010 2011 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2012 return f"WITH ({self.expressions(expression, flat=True)})" 2013 2014 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2015 this = f"{self.sql(expression, 'this')} INDEX" 2016 target = self.sql(expression, "target") 2017 target = f" FOR {target}" if target else "" 2018 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2019 2020 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2021 this = self.sql(expression, "this") 2022 kind = self.sql(expression, "kind") 2023 expr = self.sql(expression, "expression") 2024 return f"{this} ({kind} => {expr})" 2025 2026 def table_parts(self, expression: exp.Table) -> str: 2027 return ".".join( 2028 self.sql(part) 2029 for part in ( 2030 expression.args.get("catalog"), 2031 expression.args.get("db"), 2032 expression.args.get("this"), 2033 ) 2034 if part is not None 2035 ) 2036 2037 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2038 table = self.table_parts(expression) 2039 only = "ONLY " if expression.args.get("only") else "" 2040 partition = self.sql(expression, "partition") 2041 partition = f" {partition}" if partition else "" 2042 version = self.sql(expression, "version") 2043 version = f" {version}" if version else "" 2044 alias = self.sql(expression, "alias") 2045 alias = f"{sep}{alias}" if alias else "" 2046 2047 sample = self.sql(expression, "sample") 2048 if self.dialect.ALIAS_POST_TABLESAMPLE: 2049 sample_pre_alias = sample 2050 sample_post_alias = "" 2051 else: 2052 sample_pre_alias = "" 2053 sample_post_alias = sample 2054 2055 hints = self.expressions(expression, key="hints", sep=" ") 2056 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2057 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2058 joins = self.indent( 2059 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2060 ) 2061 laterals = self.expressions(expression, key="laterals", sep="") 2062 2063 file_format = self.sql(expression, "format") 2064 if file_format: 2065 pattern = self.sql(expression, "pattern") 2066 pattern = f", PATTERN => {pattern}" if pattern else "" 2067 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2068 2069 ordinality = expression.args.get("ordinality") or "" 2070 if ordinality: 2071 ordinality = f" WITH ORDINALITY{alias}" 2072 alias = "" 2073 2074 when = self.sql(expression, "when") 2075 if when: 2076 table = f"{table} {when}" 2077 2078 changes = self.sql(expression, "changes") 2079 changes = f" {changes}" if changes else "" 2080 2081 rows_from = self.expressions(expression, key="rows_from") 2082 if rows_from: 2083 table = f"ROWS FROM {self.wrap(rows_from)}" 2084 2085 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2086 2087 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2088 table = self.func("TABLE", expression.this) 2089 alias = self.sql(expression, "alias") 2090 alias = f" AS {alias}" if alias else "" 2091 sample = self.sql(expression, "sample") 2092 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2093 joins = self.indent( 2094 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2095 ) 2096 return f"{table}{alias}{pivots}{sample}{joins}" 2097 2098 def tablesample_sql( 2099 self, 2100 expression: exp.TableSample, 2101 tablesample_keyword: t.Optional[str] = None, 2102 ) -> str: 2103 method = self.sql(expression, "method") 2104 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2105 numerator = self.sql(expression, "bucket_numerator") 2106 denominator = self.sql(expression, "bucket_denominator") 2107 field = self.sql(expression, "bucket_field") 2108 field = f" ON {field}" if field else "" 2109 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2110 seed = self.sql(expression, "seed") 2111 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2112 2113 size = self.sql(expression, "size") 2114 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2115 size = f"{size} ROWS" 2116 2117 percent = self.sql(expression, "percent") 2118 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2119 percent = f"{percent} PERCENT" 2120 2121 expr = f"{bucket}{percent}{size}" 2122 if self.TABLESAMPLE_REQUIRES_PARENS: 2123 expr = f"({expr})" 2124 2125 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2126 2127 def pivot_sql(self, expression: exp.Pivot) -> str: 2128 expressions = self.expressions(expression, flat=True) 2129 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2130 2131 group = self.sql(expression, "group") 2132 2133 if expression.this: 2134 this = self.sql(expression, "this") 2135 if not expressions: 2136 return f"UNPIVOT {this}" 2137 2138 on = f"{self.seg('ON')} {expressions}" 2139 into = self.sql(expression, "into") 2140 into = f"{self.seg('INTO')} {into}" if into else "" 2141 using = self.expressions(expression, key="using", flat=True) 2142 using = f"{self.seg('USING')} {using}" if using else "" 2143 return f"{direction} {this}{on}{into}{using}{group}" 2144 2145 alias = self.sql(expression, "alias") 2146 alias = f" AS {alias}" if alias else "" 2147 2148 fields = self.expressions( 2149 expression, 2150 "fields", 2151 sep=" ", 2152 dynamic=True, 2153 new_line=True, 2154 skip_first=True, 2155 skip_last=True, 2156 ) 2157 2158 include_nulls = expression.args.get("include_nulls") 2159 if include_nulls is not None: 2160 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2161 else: 2162 nulls = "" 2163 2164 default_on_null = self.sql(expression, "default_on_null") 2165 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2166 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2167 2168 def version_sql(self, expression: exp.Version) -> str: 2169 this = f"FOR {expression.name}" 2170 kind = expression.text("kind") 2171 expr = self.sql(expression, "expression") 2172 return f"{this} {kind} {expr}" 2173 2174 def tuple_sql(self, expression: exp.Tuple) -> str: 2175 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2176 2177 def update_sql(self, expression: exp.Update) -> str: 2178 this = self.sql(expression, "this") 2179 set_sql = self.expressions(expression, flat=True) 2180 from_sql = self.sql(expression, "from") 2181 where_sql = self.sql(expression, "where") 2182 returning = self.sql(expression, "returning") 2183 order = self.sql(expression, "order") 2184 limit = self.sql(expression, "limit") 2185 if self.RETURNING_END: 2186 expression_sql = f"{from_sql}{where_sql}{returning}" 2187 else: 2188 expression_sql = f"{returning}{from_sql}{where_sql}" 2189 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2190 return self.prepend_ctes(expression, sql) 2191 2192 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2193 values_as_table = values_as_table and self.VALUES_AS_TABLE 2194 2195 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2196 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2197 args = self.expressions(expression) 2198 alias = self.sql(expression, "alias") 2199 values = f"VALUES{self.seg('')}{args}" 2200 values = ( 2201 f"({values})" 2202 if self.WRAP_DERIVED_VALUES 2203 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2204 else values 2205 ) 2206 return f"{values} AS {alias}" if alias else values 2207 2208 # Converts `VALUES...` expression into a series of select unions. 2209 alias_node = expression.args.get("alias") 2210 column_names = alias_node and alias_node.columns 2211 2212 selects: t.List[exp.Query] = [] 2213 2214 for i, tup in enumerate(expression.expressions): 2215 row = tup.expressions 2216 2217 if i == 0 and column_names: 2218 row = [ 2219 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2220 ] 2221 2222 selects.append(exp.Select(expressions=row)) 2223 2224 if self.pretty: 2225 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2226 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2227 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2228 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2229 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2230 2231 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2232 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2233 return f"({unions}){alias}" 2234 2235 def var_sql(self, expression: exp.Var) -> str: 2236 return self.sql(expression, "this") 2237 2238 @unsupported_args("expressions") 2239 def into_sql(self, expression: exp.Into) -> str: 2240 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2241 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2242 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2243 2244 def from_sql(self, expression: exp.From) -> str: 2245 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2246 2247 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2248 grouping_sets = self.expressions(expression, indent=False) 2249 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2250 2251 def rollup_sql(self, expression: exp.Rollup) -> str: 2252 expressions = self.expressions(expression, indent=False) 2253 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2254 2255 def cube_sql(self, expression: exp.Cube) -> str: 2256 expressions = self.expressions(expression, indent=False) 2257 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2258 2259 def group_sql(self, expression: exp.Group) -> str: 2260 group_by_all = expression.args.get("all") 2261 if group_by_all is True: 2262 modifier = " ALL" 2263 elif group_by_all is False: 2264 modifier = " DISTINCT" 2265 else: 2266 modifier = "" 2267 2268 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2269 2270 grouping_sets = self.expressions(expression, key="grouping_sets") 2271 cube = self.expressions(expression, key="cube") 2272 rollup = self.expressions(expression, key="rollup") 2273 2274 groupings = csv( 2275 self.seg(grouping_sets) if grouping_sets else "", 2276 self.seg(cube) if cube else "", 2277 self.seg(rollup) if rollup else "", 2278 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2279 sep=self.GROUPINGS_SEP, 2280 ) 2281 2282 if ( 2283 expression.expressions 2284 and groupings 2285 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2286 ): 2287 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2288 2289 return f"{group_by}{groupings}" 2290 2291 def having_sql(self, expression: exp.Having) -> str: 2292 this = self.indent(self.sql(expression, "this")) 2293 return f"{self.seg('HAVING')}{self.sep()}{this}" 2294 2295 def connect_sql(self, expression: exp.Connect) -> str: 2296 start = self.sql(expression, "start") 2297 start = self.seg(f"START WITH {start}") if start else "" 2298 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2299 connect = self.sql(expression, "connect") 2300 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2301 return start + connect 2302 2303 def prior_sql(self, expression: exp.Prior) -> str: 2304 return f"PRIOR {self.sql(expression, 'this')}" 2305 2306 def join_sql(self, expression: exp.Join) -> str: 2307 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2308 side = None 2309 else: 2310 side = expression.side 2311 2312 op_sql = " ".join( 2313 op 2314 for op in ( 2315 expression.method, 2316 "GLOBAL" if expression.args.get("global") else None, 2317 side, 2318 expression.kind, 2319 expression.hint if self.JOIN_HINTS else None, 2320 ) 2321 if op 2322 ) 2323 match_cond = self.sql(expression, "match_condition") 2324 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2325 on_sql = self.sql(expression, "on") 2326 using = expression.args.get("using") 2327 2328 if not on_sql and using: 2329 on_sql = csv(*(self.sql(column) for column in using)) 2330 2331 this = expression.this 2332 this_sql = self.sql(this) 2333 2334 exprs = self.expressions(expression) 2335 if exprs: 2336 this_sql = f"{this_sql},{self.seg(exprs)}" 2337 2338 if on_sql: 2339 on_sql = self.indent(on_sql, skip_first=True) 2340 space = self.seg(" " * self.pad) if self.pretty else " " 2341 if using: 2342 on_sql = f"{space}USING ({on_sql})" 2343 else: 2344 on_sql = f"{space}ON {on_sql}" 2345 elif not op_sql: 2346 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2347 return f" {this_sql}" 2348 2349 return f", {this_sql}" 2350 2351 if op_sql != "STRAIGHT_JOIN": 2352 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2353 2354 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2355 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2356 2357 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2358 args = self.expressions(expression, flat=True) 2359 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2360 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2361 2362 def lateral_op(self, expression: exp.Lateral) -> str: 2363 cross_apply = expression.args.get("cross_apply") 2364 2365 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2366 if cross_apply is True: 2367 op = "INNER JOIN " 2368 elif cross_apply is False: 2369 op = "LEFT JOIN " 2370 else: 2371 op = "" 2372 2373 return f"{op}LATERAL" 2374 2375 def lateral_sql(self, expression: exp.Lateral) -> str: 2376 this = self.sql(expression, "this") 2377 2378 if expression.args.get("view"): 2379 alias = expression.args["alias"] 2380 columns = self.expressions(alias, key="columns", flat=True) 2381 table = f" {alias.name}" if alias.name else "" 2382 columns = f" AS {columns}" if columns else "" 2383 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2384 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2385 2386 alias = self.sql(expression, "alias") 2387 alias = f" AS {alias}" if alias else "" 2388 2389 ordinality = expression.args.get("ordinality") or "" 2390 if ordinality: 2391 ordinality = f" WITH ORDINALITY{alias}" 2392 alias = "" 2393 2394 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2395 2396 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2397 this = self.sql(expression, "this") 2398 2399 args = [ 2400 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2401 for e in (expression.args.get(k) for k in ("offset", "expression")) 2402 if e 2403 ] 2404 2405 args_sql = ", ".join(self.sql(e) for e in args) 2406 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2407 expressions = self.expressions(expression, flat=True) 2408 limit_options = self.sql(expression, "limit_options") 2409 expressions = f" BY {expressions}" if expressions else "" 2410 2411 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2412 2413 def offset_sql(self, expression: exp.Offset) -> str: 2414 this = self.sql(expression, "this") 2415 value = expression.expression 2416 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2417 expressions = self.expressions(expression, flat=True) 2418 expressions = f" BY {expressions}" if expressions else "" 2419 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2420 2421 def setitem_sql(self, expression: exp.SetItem) -> str: 2422 kind = self.sql(expression, "kind") 2423 kind = f"{kind} " if kind else "" 2424 this = self.sql(expression, "this") 2425 expressions = self.expressions(expression) 2426 collate = self.sql(expression, "collate") 2427 collate = f" COLLATE {collate}" if collate else "" 2428 global_ = "GLOBAL " if expression.args.get("global") else "" 2429 return f"{global_}{kind}{this}{expressions}{collate}" 2430 2431 def set_sql(self, expression: exp.Set) -> str: 2432 expressions = f" {self.expressions(expression, flat=True)}" 2433 tag = " TAG" if expression.args.get("tag") else "" 2434 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2435 2436 def queryband_sql(self, expression: exp.QueryBand) -> str: 2437 this = self.sql(expression, "this") 2438 update = " UPDATE" if expression.args.get("update") else "" 2439 scope = self.sql(expression, "scope") 2440 scope = f" FOR {scope}" if scope else "" 2441 2442 return f"QUERY_BAND = {this}{update}{scope}" 2443 2444 def pragma_sql(self, expression: exp.Pragma) -> str: 2445 return f"PRAGMA {self.sql(expression, 'this')}" 2446 2447 def lock_sql(self, expression: exp.Lock) -> str: 2448 if not self.LOCKING_READS_SUPPORTED: 2449 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2450 return "" 2451 2452 update = expression.args["update"] 2453 key = expression.args.get("key") 2454 if update: 2455 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2456 else: 2457 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2458 expressions = self.expressions(expression, flat=True) 2459 expressions = f" OF {expressions}" if expressions else "" 2460 wait = expression.args.get("wait") 2461 2462 if wait is not None: 2463 if isinstance(wait, exp.Literal): 2464 wait = f" WAIT {self.sql(wait)}" 2465 else: 2466 wait = " NOWAIT" if wait else " SKIP LOCKED" 2467 2468 return f"{lock_type}{expressions}{wait or ''}" 2469 2470 def literal_sql(self, expression: exp.Literal) -> str: 2471 text = expression.this or "" 2472 if expression.is_string: 2473 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2474 return text 2475 2476 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2477 if self.dialect.ESCAPED_SEQUENCES: 2478 to_escaped = self.dialect.ESCAPED_SEQUENCES 2479 text = "".join( 2480 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2481 ) 2482 2483 return self._replace_line_breaks(text).replace( 2484 self.dialect.QUOTE_END, self._escaped_quote_end 2485 ) 2486 2487 def loaddata_sql(self, expression: exp.LoadData) -> str: 2488 local = " LOCAL" if expression.args.get("local") else "" 2489 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2490 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2491 this = f" INTO TABLE {self.sql(expression, 'this')}" 2492 partition = self.sql(expression, "partition") 2493 partition = f" {partition}" if partition else "" 2494 input_format = self.sql(expression, "input_format") 2495 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2496 serde = self.sql(expression, "serde") 2497 serde = f" SERDE {serde}" if serde else "" 2498 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2499 2500 def null_sql(self, *_) -> str: 2501 return "NULL" 2502 2503 def boolean_sql(self, expression: exp.Boolean) -> str: 2504 return "TRUE" if expression.this else "FALSE" 2505 2506 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2507 this = self.sql(expression, "this") 2508 this = f"{this} " if this else this 2509 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2510 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2511 2512 def withfill_sql(self, expression: exp.WithFill) -> str: 2513 from_sql = self.sql(expression, "from") 2514 from_sql = f" FROM {from_sql}" if from_sql else "" 2515 to_sql = self.sql(expression, "to") 2516 to_sql = f" TO {to_sql}" if to_sql else "" 2517 step_sql = self.sql(expression, "step") 2518 step_sql = f" STEP {step_sql}" if step_sql else "" 2519 interpolated_values = [ 2520 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2521 if isinstance(e, exp.Alias) 2522 else self.sql(e, "this") 2523 for e in expression.args.get("interpolate") or [] 2524 ] 2525 interpolate = ( 2526 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2527 ) 2528 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2529 2530 def cluster_sql(self, expression: exp.Cluster) -> str: 2531 return self.op_expressions("CLUSTER BY", expression) 2532 2533 def distribute_sql(self, expression: exp.Distribute) -> str: 2534 return self.op_expressions("DISTRIBUTE BY", expression) 2535 2536 def sort_sql(self, expression: exp.Sort) -> str: 2537 return self.op_expressions("SORT BY", expression) 2538 2539 def ordered_sql(self, expression: exp.Ordered) -> str: 2540 desc = expression.args.get("desc") 2541 asc = not desc 2542 2543 nulls_first = expression.args.get("nulls_first") 2544 nulls_last = not nulls_first 2545 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2546 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2547 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2548 2549 this = self.sql(expression, "this") 2550 2551 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2552 nulls_sort_change = "" 2553 if nulls_first and ( 2554 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2555 ): 2556 nulls_sort_change = " NULLS FIRST" 2557 elif ( 2558 nulls_last 2559 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2560 and not nulls_are_last 2561 ): 2562 nulls_sort_change = " NULLS LAST" 2563 2564 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2565 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2566 window = expression.find_ancestor(exp.Window, exp.Select) 2567 if isinstance(window, exp.Window) and window.args.get("spec"): 2568 self.unsupported( 2569 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2570 ) 2571 nulls_sort_change = "" 2572 elif self.NULL_ORDERING_SUPPORTED is False and ( 2573 (asc and nulls_sort_change == " NULLS LAST") 2574 or (desc and nulls_sort_change == " NULLS FIRST") 2575 ): 2576 # BigQuery does not allow these ordering/nulls combinations when used under 2577 # an aggregation func or under a window containing one 2578 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2579 2580 if isinstance(ancestor, exp.Window): 2581 ancestor = ancestor.this 2582 if isinstance(ancestor, exp.AggFunc): 2583 self.unsupported( 2584 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2585 ) 2586 nulls_sort_change = "" 2587 elif self.NULL_ORDERING_SUPPORTED is None: 2588 if expression.this.is_int: 2589 self.unsupported( 2590 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2591 ) 2592 elif not isinstance(expression.this, exp.Rand): 2593 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2594 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2595 nulls_sort_change = "" 2596 2597 with_fill = self.sql(expression, "with_fill") 2598 with_fill = f" {with_fill}" if with_fill else "" 2599 2600 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2601 2602 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2603 window_frame = self.sql(expression, "window_frame") 2604 window_frame = f"{window_frame} " if window_frame else "" 2605 2606 this = self.sql(expression, "this") 2607 2608 return f"{window_frame}{this}" 2609 2610 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2611 partition = self.partition_by_sql(expression) 2612 order = self.sql(expression, "order") 2613 measures = self.expressions(expression, key="measures") 2614 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2615 rows = self.sql(expression, "rows") 2616 rows = self.seg(rows) if rows else "" 2617 after = self.sql(expression, "after") 2618 after = self.seg(after) if after else "" 2619 pattern = self.sql(expression, "pattern") 2620 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2621 definition_sqls = [ 2622 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2623 for definition in expression.args.get("define", []) 2624 ] 2625 definitions = self.expressions(sqls=definition_sqls) 2626 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2627 body = "".join( 2628 ( 2629 partition, 2630 order, 2631 measures, 2632 rows, 2633 after, 2634 pattern, 2635 define, 2636 ) 2637 ) 2638 alias = self.sql(expression, "alias") 2639 alias = f" {alias}" if alias else "" 2640 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2641 2642 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2643 limit = expression.args.get("limit") 2644 2645 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2646 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2647 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2648 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2649 2650 return csv( 2651 *sqls, 2652 *[self.sql(join) for join in expression.args.get("joins") or []], 2653 self.sql(expression, "match"), 2654 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2655 self.sql(expression, "prewhere"), 2656 self.sql(expression, "where"), 2657 self.sql(expression, "connect"), 2658 self.sql(expression, "group"), 2659 self.sql(expression, "having"), 2660 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2661 self.sql(expression, "order"), 2662 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2663 *self.after_limit_modifiers(expression), 2664 self.options_modifier(expression), 2665 self.for_modifiers(expression), 2666 sep="", 2667 ) 2668 2669 def options_modifier(self, expression: exp.Expression) -> str: 2670 options = self.expressions(expression, key="options") 2671 return f" {options}" if options else "" 2672 2673 def for_modifiers(self, expression: exp.Expression) -> str: 2674 for_modifiers = self.expressions(expression, key="for") 2675 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2676 2677 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2678 self.unsupported("Unsupported query option.") 2679 return "" 2680 2681 def offset_limit_modifiers( 2682 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2683 ) -> t.List[str]: 2684 return [ 2685 self.sql(expression, "offset") if fetch else self.sql(limit), 2686 self.sql(limit) if fetch else self.sql(expression, "offset"), 2687 ] 2688 2689 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2690 locks = self.expressions(expression, key="locks", sep=" ") 2691 locks = f" {locks}" if locks else "" 2692 return [locks, self.sql(expression, "sample")] 2693 2694 def select_sql(self, expression: exp.Select) -> str: 2695 into = expression.args.get("into") 2696 if not self.SUPPORTS_SELECT_INTO and into: 2697 into.pop() 2698 2699 hint = self.sql(expression, "hint") 2700 distinct = self.sql(expression, "distinct") 2701 distinct = f" {distinct}" if distinct else "" 2702 kind = self.sql(expression, "kind") 2703 2704 limit = expression.args.get("limit") 2705 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2706 top = self.limit_sql(limit, top=True) 2707 limit.pop() 2708 else: 2709 top = "" 2710 2711 expressions = self.expressions(expression) 2712 2713 if kind: 2714 if kind in self.SELECT_KINDS: 2715 kind = f" AS {kind}" 2716 else: 2717 if kind == "STRUCT": 2718 expressions = self.expressions( 2719 sqls=[ 2720 self.sql( 2721 exp.Struct( 2722 expressions=[ 2723 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2724 if isinstance(e, exp.Alias) 2725 else e 2726 for e in expression.expressions 2727 ] 2728 ) 2729 ) 2730 ] 2731 ) 2732 kind = "" 2733 2734 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2735 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2736 2737 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2738 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2739 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2740 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2741 sql = self.query_modifiers( 2742 expression, 2743 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2744 self.sql(expression, "into", comment=False), 2745 self.sql(expression, "from", comment=False), 2746 ) 2747 2748 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2749 if expression.args.get("with"): 2750 sql = self.maybe_comment(sql, expression) 2751 expression.pop_comments() 2752 2753 sql = self.prepend_ctes(expression, sql) 2754 2755 if not self.SUPPORTS_SELECT_INTO and into: 2756 if into.args.get("temporary"): 2757 table_kind = " TEMPORARY" 2758 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2759 table_kind = " UNLOGGED" 2760 else: 2761 table_kind = "" 2762 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2763 2764 return sql 2765 2766 def schema_sql(self, expression: exp.Schema) -> str: 2767 this = self.sql(expression, "this") 2768 sql = self.schema_columns_sql(expression) 2769 return f"{this} {sql}" if this and sql else this or sql 2770 2771 def schema_columns_sql(self, expression: exp.Schema) -> str: 2772 if expression.expressions: 2773 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2774 return "" 2775 2776 def star_sql(self, expression: exp.Star) -> str: 2777 except_ = self.expressions(expression, key="except", flat=True) 2778 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2779 replace = self.expressions(expression, key="replace", flat=True) 2780 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2781 rename = self.expressions(expression, key="rename", flat=True) 2782 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2783 return f"*{except_}{replace}{rename}" 2784 2785 def parameter_sql(self, expression: exp.Parameter) -> str: 2786 this = self.sql(expression, "this") 2787 return f"{self.PARAMETER_TOKEN}{this}" 2788 2789 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2790 this = self.sql(expression, "this") 2791 kind = expression.text("kind") 2792 if kind: 2793 kind = f"{kind}." 2794 return f"@@{kind}{this}" 2795 2796 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2797 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2798 2799 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2800 alias = self.sql(expression, "alias") 2801 alias = f"{sep}{alias}" if alias else "" 2802 sample = self.sql(expression, "sample") 2803 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2804 alias = f"{sample}{alias}" 2805 2806 # Set to None so it's not generated again by self.query_modifiers() 2807 expression.set("sample", None) 2808 2809 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2810 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2811 return self.prepend_ctes(expression, sql) 2812 2813 def qualify_sql(self, expression: exp.Qualify) -> str: 2814 this = self.indent(self.sql(expression, "this")) 2815 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2816 2817 def unnest_sql(self, expression: exp.Unnest) -> str: 2818 args = self.expressions(expression, flat=True) 2819 2820 alias = expression.args.get("alias") 2821 offset = expression.args.get("offset") 2822 2823 if self.UNNEST_WITH_ORDINALITY: 2824 if alias and isinstance(offset, exp.Expression): 2825 alias.append("columns", offset) 2826 2827 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2828 columns = alias.columns 2829 alias = self.sql(columns[0]) if columns else "" 2830 else: 2831 alias = self.sql(alias) 2832 2833 alias = f" AS {alias}" if alias else alias 2834 if self.UNNEST_WITH_ORDINALITY: 2835 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2836 else: 2837 if isinstance(offset, exp.Expression): 2838 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2839 elif offset: 2840 suffix = f"{alias} WITH OFFSET" 2841 else: 2842 suffix = alias 2843 2844 return f"UNNEST({args}){suffix}" 2845 2846 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2847 return "" 2848 2849 def where_sql(self, expression: exp.Where) -> str: 2850 this = self.indent(self.sql(expression, "this")) 2851 return f"{self.seg('WHERE')}{self.sep()}{this}" 2852 2853 def window_sql(self, expression: exp.Window) -> str: 2854 this = self.sql(expression, "this") 2855 partition = self.partition_by_sql(expression) 2856 order = expression.args.get("order") 2857 order = self.order_sql(order, flat=True) if order else "" 2858 spec = self.sql(expression, "spec") 2859 alias = self.sql(expression, "alias") 2860 over = self.sql(expression, "over") or "OVER" 2861 2862 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2863 2864 first = expression.args.get("first") 2865 if first is None: 2866 first = "" 2867 else: 2868 first = "FIRST" if first else "LAST" 2869 2870 if not partition and not order and not spec and alias: 2871 return f"{this} {alias}" 2872 2873 args = self.format_args( 2874 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2875 ) 2876 return f"{this} ({args})" 2877 2878 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2879 partition = self.expressions(expression, key="partition_by", flat=True) 2880 return f"PARTITION BY {partition}" if partition else "" 2881 2882 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2883 kind = self.sql(expression, "kind") 2884 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2885 end = ( 2886 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2887 or "CURRENT ROW" 2888 ) 2889 2890 window_spec = f"{kind} BETWEEN {start} AND {end}" 2891 2892 exclude = self.sql(expression, "exclude") 2893 if exclude: 2894 if self.SUPPORTS_WINDOW_EXCLUDE: 2895 window_spec += f" EXCLUDE {exclude}" 2896 else: 2897 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2898 2899 return window_spec 2900 2901 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2902 this = self.sql(expression, "this") 2903 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2904 return f"{this} WITHIN GROUP ({expression_sql})" 2905 2906 def between_sql(self, expression: exp.Between) -> str: 2907 this = self.sql(expression, "this") 2908 low = self.sql(expression, "low") 2909 high = self.sql(expression, "high") 2910 symmetric = expression.args.get("symmetric") 2911 2912 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2913 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2914 2915 flag = ( 2916 " SYMMETRIC" 2917 if symmetric 2918 else " ASYMMETRIC" 2919 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2920 else "" # silently drop ASYMMETRIC – semantics identical 2921 ) 2922 return f"{this} BETWEEN{flag} {low} AND {high}" 2923 2924 def bracket_offset_expressions( 2925 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2926 ) -> t.List[exp.Expression]: 2927 return apply_index_offset( 2928 expression.this, 2929 expression.expressions, 2930 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2931 dialect=self.dialect, 2932 ) 2933 2934 def bracket_sql(self, expression: exp.Bracket) -> str: 2935 expressions = self.bracket_offset_expressions(expression) 2936 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2937 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2938 2939 def all_sql(self, expression: exp.All) -> str: 2940 this = self.sql(expression, "this") 2941 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2942 this = self.wrap(this) 2943 return f"ALL {this}" 2944 2945 def any_sql(self, expression: exp.Any) -> str: 2946 this = self.sql(expression, "this") 2947 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2948 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2949 this = self.wrap(this) 2950 return f"ANY{this}" 2951 return f"ANY {this}" 2952 2953 def exists_sql(self, expression: exp.Exists) -> str: 2954 return f"EXISTS{self.wrap(expression)}" 2955 2956 def case_sql(self, expression: exp.Case) -> str: 2957 this = self.sql(expression, "this") 2958 statements = [f"CASE {this}" if this else "CASE"] 2959 2960 for e in expression.args["ifs"]: 2961 statements.append(f"WHEN {self.sql(e, 'this')}") 2962 statements.append(f"THEN {self.sql(e, 'true')}") 2963 2964 default = self.sql(expression, "default") 2965 2966 if default: 2967 statements.append(f"ELSE {default}") 2968 2969 statements.append("END") 2970 2971 if self.pretty and self.too_wide(statements): 2972 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2973 2974 return " ".join(statements) 2975 2976 def constraint_sql(self, expression: exp.Constraint) -> str: 2977 this = self.sql(expression, "this") 2978 expressions = self.expressions(expression, flat=True) 2979 return f"CONSTRAINT {this} {expressions}" 2980 2981 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2982 order = expression.args.get("order") 2983 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2984 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2985 2986 def extract_sql(self, expression: exp.Extract) -> str: 2987 from sqlglot.dialects.dialect import map_date_part 2988 2989 this = ( 2990 map_date_part(expression.this, self.dialect) 2991 if self.NORMALIZE_EXTRACT_DATE_PARTS 2992 else expression.this 2993 ) 2994 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2995 expression_sql = self.sql(expression, "expression") 2996 2997 return f"EXTRACT({this_sql} FROM {expression_sql})" 2998 2999 def trim_sql(self, expression: exp.Trim) -> str: 3000 trim_type = self.sql(expression, "position") 3001 3002 if trim_type == "LEADING": 3003 func_name = "LTRIM" 3004 elif trim_type == "TRAILING": 3005 func_name = "RTRIM" 3006 else: 3007 func_name = "TRIM" 3008 3009 return self.func(func_name, expression.this, expression.expression) 3010 3011 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3012 args = expression.expressions 3013 if isinstance(expression, exp.ConcatWs): 3014 args = args[1:] # Skip the delimiter 3015 3016 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3017 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3018 3019 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3020 3021 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3022 if not e.type: 3023 from sqlglot.optimizer.annotate_types import annotate_types 3024 3025 e = annotate_types(e, dialect=self.dialect) 3026 3027 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3028 return e 3029 3030 return exp.func("coalesce", e, exp.Literal.string("")) 3031 3032 args = [_wrap_with_coalesce(e) for e in args] 3033 3034 return args 3035 3036 def concat_sql(self, expression: exp.Concat) -> str: 3037 expressions = self.convert_concat_args(expression) 3038 3039 # Some dialects don't allow a single-argument CONCAT call 3040 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3041 return self.sql(expressions[0]) 3042 3043 return self.func("CONCAT", *expressions) 3044 3045 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3046 return self.func( 3047 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3048 ) 3049 3050 def check_sql(self, expression: exp.Check) -> str: 3051 this = self.sql(expression, key="this") 3052 return f"CHECK ({this})" 3053 3054 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3055 expressions = self.expressions(expression, flat=True) 3056 expressions = f" ({expressions})" if expressions else "" 3057 reference = self.sql(expression, "reference") 3058 reference = f" {reference}" if reference else "" 3059 delete = self.sql(expression, "delete") 3060 delete = f" ON DELETE {delete}" if delete else "" 3061 update = self.sql(expression, "update") 3062 update = f" ON UPDATE {update}" if update else "" 3063 options = self.expressions(expression, key="options", flat=True, sep=" ") 3064 options = f" {options}" if options else "" 3065 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3066 3067 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3068 expressions = self.expressions(expression, flat=True) 3069 include = self.sql(expression, "include") 3070 options = self.expressions(expression, key="options", flat=True, sep=" ") 3071 options = f" {options}" if options else "" 3072 return f"PRIMARY KEY ({expressions}){include}{options}" 3073 3074 def if_sql(self, expression: exp.If) -> str: 3075 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3076 3077 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3078 if self.MATCH_AGAINST_TABLE_PREFIX: 3079 expressions = [] 3080 for expr in expression.expressions: 3081 if isinstance(expr, exp.Table): 3082 expressions.append(f"TABLE {self.sql(expr)}") 3083 else: 3084 expressions.append(expr) 3085 else: 3086 expressions = expression.expressions 3087 3088 modifier = expression.args.get("modifier") 3089 modifier = f" {modifier}" if modifier else "" 3090 return ( 3091 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3092 ) 3093 3094 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3095 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3096 3097 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3098 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3099 3100 if expression.args.get("escape"): 3101 path = self.escape_str(path) 3102 3103 if self.QUOTE_JSON_PATH: 3104 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3105 3106 return path 3107 3108 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3109 if isinstance(expression, exp.JSONPathPart): 3110 transform = self.TRANSFORMS.get(expression.__class__) 3111 if not callable(transform): 3112 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3113 return "" 3114 3115 return transform(self, expression) 3116 3117 if isinstance(expression, int): 3118 return str(expression) 3119 3120 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3121 escaped = expression.replace("'", "\\'") 3122 escaped = f"\\'{expression}\\'" 3123 else: 3124 escaped = expression.replace('"', '\\"') 3125 escaped = f'"{escaped}"' 3126 3127 return escaped 3128 3129 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3130 return f"{self.sql(expression, 'this')} FORMAT JSON" 3131 3132 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3133 # Output the Teradata column FORMAT override. 3134 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3135 this = self.sql(expression, "this") 3136 fmt = self.sql(expression, "format") 3137 return f"{this} (FORMAT {fmt})" 3138 3139 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3140 null_handling = expression.args.get("null_handling") 3141 null_handling = f" {null_handling}" if null_handling else "" 3142 3143 unique_keys = expression.args.get("unique_keys") 3144 if unique_keys is not None: 3145 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3146 else: 3147 unique_keys = "" 3148 3149 return_type = self.sql(expression, "return_type") 3150 return_type = f" RETURNING {return_type}" if return_type else "" 3151 encoding = self.sql(expression, "encoding") 3152 encoding = f" ENCODING {encoding}" if encoding else "" 3153 3154 return self.func( 3155 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3156 *expression.expressions, 3157 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3158 ) 3159 3160 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3161 return self.jsonobject_sql(expression) 3162 3163 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3164 null_handling = expression.args.get("null_handling") 3165 null_handling = f" {null_handling}" if null_handling else "" 3166 return_type = self.sql(expression, "return_type") 3167 return_type = f" RETURNING {return_type}" if return_type else "" 3168 strict = " STRICT" if expression.args.get("strict") else "" 3169 return self.func( 3170 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3171 ) 3172 3173 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3174 this = self.sql(expression, "this") 3175 order = self.sql(expression, "order") 3176 null_handling = expression.args.get("null_handling") 3177 null_handling = f" {null_handling}" if null_handling else "" 3178 return_type = self.sql(expression, "return_type") 3179 return_type = f" RETURNING {return_type}" if return_type else "" 3180 strict = " STRICT" if expression.args.get("strict") else "" 3181 return self.func( 3182 "JSON_ARRAYAGG", 3183 this, 3184 suffix=f"{order}{null_handling}{return_type}{strict})", 3185 ) 3186 3187 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3188 path = self.sql(expression, "path") 3189 path = f" PATH {path}" if path else "" 3190 nested_schema = self.sql(expression, "nested_schema") 3191 3192 if nested_schema: 3193 return f"NESTED{path} {nested_schema}" 3194 3195 this = self.sql(expression, "this") 3196 kind = self.sql(expression, "kind") 3197 kind = f" {kind}" if kind else "" 3198 return f"{this}{kind}{path}" 3199 3200 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3201 return self.func("COLUMNS", *expression.expressions) 3202 3203 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3204 this = self.sql(expression, "this") 3205 path = self.sql(expression, "path") 3206 path = f", {path}" if path else "" 3207 error_handling = expression.args.get("error_handling") 3208 error_handling = f" {error_handling}" if error_handling else "" 3209 empty_handling = expression.args.get("empty_handling") 3210 empty_handling = f" {empty_handling}" if empty_handling else "" 3211 schema = self.sql(expression, "schema") 3212 return self.func( 3213 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3214 ) 3215 3216 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3217 this = self.sql(expression, "this") 3218 kind = self.sql(expression, "kind") 3219 path = self.sql(expression, "path") 3220 path = f" {path}" if path else "" 3221 as_json = " AS JSON" if expression.args.get("as_json") else "" 3222 return f"{this} {kind}{path}{as_json}" 3223 3224 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3225 this = self.sql(expression, "this") 3226 path = self.sql(expression, "path") 3227 path = f", {path}" if path else "" 3228 expressions = self.expressions(expression) 3229 with_ = ( 3230 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3231 if expressions 3232 else "" 3233 ) 3234 return f"OPENJSON({this}{path}){with_}" 3235 3236 def in_sql(self, expression: exp.In) -> str: 3237 query = expression.args.get("query") 3238 unnest = expression.args.get("unnest") 3239 field = expression.args.get("field") 3240 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3241 3242 if query: 3243 in_sql = self.sql(query) 3244 elif unnest: 3245 in_sql = self.in_unnest_op(unnest) 3246 elif field: 3247 in_sql = self.sql(field) 3248 else: 3249 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3250 3251 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3252 3253 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3254 return f"(SELECT {self.sql(unnest)})" 3255 3256 def interval_sql(self, expression: exp.Interval) -> str: 3257 unit = self.sql(expression, "unit") 3258 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3259 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3260 unit = f" {unit}" if unit else "" 3261 3262 if self.SINGLE_STRING_INTERVAL: 3263 this = expression.this.name if expression.this else "" 3264 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3265 3266 this = self.sql(expression, "this") 3267 if this: 3268 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3269 this = f" {this}" if unwrapped else f" ({this})" 3270 3271 return f"INTERVAL{this}{unit}" 3272 3273 def return_sql(self, expression: exp.Return) -> str: 3274 return f"RETURN {self.sql(expression, 'this')}" 3275 3276 def reference_sql(self, expression: exp.Reference) -> str: 3277 this = self.sql(expression, "this") 3278 expressions = self.expressions(expression, flat=True) 3279 expressions = f"({expressions})" if expressions else "" 3280 options = self.expressions(expression, key="options", flat=True, sep=" ") 3281 options = f" {options}" if options else "" 3282 return f"REFERENCES {this}{expressions}{options}" 3283 3284 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3285 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3286 parent = expression.parent 3287 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3288 return self.func( 3289 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3290 ) 3291 3292 def paren_sql(self, expression: exp.Paren) -> str: 3293 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3294 return f"({sql}{self.seg(')', sep='')}" 3295 3296 def neg_sql(self, expression: exp.Neg) -> str: 3297 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3298 this_sql = self.sql(expression, "this") 3299 sep = " " if this_sql[0] == "-" else "" 3300 return f"-{sep}{this_sql}" 3301 3302 def not_sql(self, expression: exp.Not) -> str: 3303 return f"NOT {self.sql(expression, 'this')}" 3304 3305 def alias_sql(self, expression: exp.Alias) -> str: 3306 alias = self.sql(expression, "alias") 3307 alias = f" AS {alias}" if alias else "" 3308 return f"{self.sql(expression, 'this')}{alias}" 3309 3310 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3311 alias = expression.args["alias"] 3312 3313 parent = expression.parent 3314 pivot = parent and parent.parent 3315 3316 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3317 identifier_alias = isinstance(alias, exp.Identifier) 3318 literal_alias = isinstance(alias, exp.Literal) 3319 3320 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3321 alias.replace(exp.Literal.string(alias.output_name)) 3322 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3323 alias.replace(exp.to_identifier(alias.output_name)) 3324 3325 return self.alias_sql(expression) 3326 3327 def aliases_sql(self, expression: exp.Aliases) -> str: 3328 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3329 3330 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3331 this = self.sql(expression, "this") 3332 index = self.sql(expression, "expression") 3333 return f"{this} AT {index}" 3334 3335 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3336 this = self.sql(expression, "this") 3337 zone = self.sql(expression, "zone") 3338 return f"{this} AT TIME ZONE {zone}" 3339 3340 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3341 this = self.sql(expression, "this") 3342 zone = self.sql(expression, "zone") 3343 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3344 3345 def add_sql(self, expression: exp.Add) -> str: 3346 return self.binary(expression, "+") 3347 3348 def and_sql( 3349 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3350 ) -> str: 3351 return self.connector_sql(expression, "AND", stack) 3352 3353 def or_sql( 3354 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3355 ) -> str: 3356 return self.connector_sql(expression, "OR", stack) 3357 3358 def xor_sql( 3359 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3360 ) -> str: 3361 return self.connector_sql(expression, "XOR", stack) 3362 3363 def connector_sql( 3364 self, 3365 expression: exp.Connector, 3366 op: str, 3367 stack: t.Optional[t.List[str | exp.Expression]] = None, 3368 ) -> str: 3369 if stack is not None: 3370 if expression.expressions: 3371 stack.append(self.expressions(expression, sep=f" {op} ")) 3372 else: 3373 stack.append(expression.right) 3374 if expression.comments and self.comments: 3375 for comment in expression.comments: 3376 if comment: 3377 op += f" /*{self.sanitize_comment(comment)}*/" 3378 stack.extend((op, expression.left)) 3379 return op 3380 3381 stack = [expression] 3382 sqls: t.List[str] = [] 3383 ops = set() 3384 3385 while stack: 3386 node = stack.pop() 3387 if isinstance(node, exp.Connector): 3388 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3389 else: 3390 sql = self.sql(node) 3391 if sqls and sqls[-1] in ops: 3392 sqls[-1] += f" {sql}" 3393 else: 3394 sqls.append(sql) 3395 3396 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3397 return sep.join(sqls) 3398 3399 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3400 return self.binary(expression, "&") 3401 3402 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3403 return self.binary(expression, "<<") 3404 3405 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3406 return f"~{self.sql(expression, 'this')}" 3407 3408 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3409 return self.binary(expression, "|") 3410 3411 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3412 return self.binary(expression, ">>") 3413 3414 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3415 return self.binary(expression, "^") 3416 3417 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3418 format_sql = self.sql(expression, "format") 3419 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3420 to_sql = self.sql(expression, "to") 3421 to_sql = f" {to_sql}" if to_sql else "" 3422 action = self.sql(expression, "action") 3423 action = f" {action}" if action else "" 3424 default = self.sql(expression, "default") 3425 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3426 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3427 3428 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3429 zone = self.sql(expression, "this") 3430 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3431 3432 def collate_sql(self, expression: exp.Collate) -> str: 3433 if self.COLLATE_IS_FUNC: 3434 return self.function_fallback_sql(expression) 3435 return self.binary(expression, "COLLATE") 3436 3437 def command_sql(self, expression: exp.Command) -> str: 3438 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3439 3440 def comment_sql(self, expression: exp.Comment) -> str: 3441 this = self.sql(expression, "this") 3442 kind = expression.args["kind"] 3443 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3444 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3445 expression_sql = self.sql(expression, "expression") 3446 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3447 3448 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3449 this = self.sql(expression, "this") 3450 delete = " DELETE" if expression.args.get("delete") else "" 3451 recompress = self.sql(expression, "recompress") 3452 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3453 to_disk = self.sql(expression, "to_disk") 3454 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3455 to_volume = self.sql(expression, "to_volume") 3456 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3457 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3458 3459 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3460 where = self.sql(expression, "where") 3461 group = self.sql(expression, "group") 3462 aggregates = self.expressions(expression, key="aggregates") 3463 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3464 3465 if not (where or group or aggregates) and len(expression.expressions) == 1: 3466 return f"TTL {self.expressions(expression, flat=True)}" 3467 3468 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3469 3470 def transaction_sql(self, expression: exp.Transaction) -> str: 3471 modes = self.expressions(expression, key="modes") 3472 modes = f" {modes}" if modes else "" 3473 return f"BEGIN{modes}" 3474 3475 def commit_sql(self, expression: exp.Commit) -> str: 3476 chain = expression.args.get("chain") 3477 if chain is not None: 3478 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3479 3480 return f"COMMIT{chain or ''}" 3481 3482 def rollback_sql(self, expression: exp.Rollback) -> str: 3483 savepoint = expression.args.get("savepoint") 3484 savepoint = f" TO {savepoint}" if savepoint else "" 3485 return f"ROLLBACK{savepoint}" 3486 3487 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3488 this = self.sql(expression, "this") 3489 3490 dtype = self.sql(expression, "dtype") 3491 if dtype: 3492 collate = self.sql(expression, "collate") 3493 collate = f" COLLATE {collate}" if collate else "" 3494 using = self.sql(expression, "using") 3495 using = f" USING {using}" if using else "" 3496 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3497 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3498 3499 default = self.sql(expression, "default") 3500 if default: 3501 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3502 3503 comment = self.sql(expression, "comment") 3504 if comment: 3505 return f"ALTER COLUMN {this} COMMENT {comment}" 3506 3507 visible = expression.args.get("visible") 3508 if visible: 3509 return f"ALTER COLUMN {this} SET {visible}" 3510 3511 allow_null = expression.args.get("allow_null") 3512 drop = expression.args.get("drop") 3513 3514 if not drop and not allow_null: 3515 self.unsupported("Unsupported ALTER COLUMN syntax") 3516 3517 if allow_null is not None: 3518 keyword = "DROP" if drop else "SET" 3519 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3520 3521 return f"ALTER COLUMN {this} DROP DEFAULT" 3522 3523 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3524 this = self.sql(expression, "this") 3525 3526 visible = expression.args.get("visible") 3527 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3528 3529 return f"ALTER INDEX {this} {visible_sql}" 3530 3531 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3532 this = self.sql(expression, "this") 3533 if not isinstance(expression.this, exp.Var): 3534 this = f"KEY DISTKEY {this}" 3535 return f"ALTER DISTSTYLE {this}" 3536 3537 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3538 compound = " COMPOUND" if expression.args.get("compound") else "" 3539 this = self.sql(expression, "this") 3540 expressions = self.expressions(expression, flat=True) 3541 expressions = f"({expressions})" if expressions else "" 3542 return f"ALTER{compound} SORTKEY {this or expressions}" 3543 3544 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3545 if not self.RENAME_TABLE_WITH_DB: 3546 # Remove db from tables 3547 expression = expression.transform( 3548 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3549 ).assert_is(exp.AlterRename) 3550 this = self.sql(expression, "this") 3551 to_kw = " TO" if include_to else "" 3552 return f"RENAME{to_kw} {this}" 3553 3554 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3555 exists = " IF EXISTS" if expression.args.get("exists") else "" 3556 old_column = self.sql(expression, "this") 3557 new_column = self.sql(expression, "to") 3558 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3559 3560 def alterset_sql(self, expression: exp.AlterSet) -> str: 3561 exprs = self.expressions(expression, flat=True) 3562 if self.ALTER_SET_WRAPPED: 3563 exprs = f"({exprs})" 3564 3565 return f"SET {exprs}" 3566 3567 def alter_sql(self, expression: exp.Alter) -> str: 3568 actions = expression.args["actions"] 3569 3570 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3571 actions[0], exp.ColumnDef 3572 ): 3573 actions_sql = self.expressions(expression, key="actions", flat=True) 3574 actions_sql = f"ADD {actions_sql}" 3575 else: 3576 actions_list = [] 3577 for action in actions: 3578 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3579 action_sql = self.add_column_sql(action) 3580 else: 3581 action_sql = self.sql(action) 3582 if isinstance(action, exp.Query): 3583 action_sql = f"AS {action_sql}" 3584 3585 actions_list.append(action_sql) 3586 3587 actions_sql = self.format_args(*actions_list).lstrip("\n") 3588 3589 exists = " IF EXISTS" if expression.args.get("exists") else "" 3590 on_cluster = self.sql(expression, "cluster") 3591 on_cluster = f" {on_cluster}" if on_cluster else "" 3592 only = " ONLY" if expression.args.get("only") else "" 3593 options = self.expressions(expression, key="options") 3594 options = f", {options}" if options else "" 3595 kind = self.sql(expression, "kind") 3596 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3597 check = " WITH CHECK" if expression.args.get("check") else "" 3598 this = self.sql(expression, "this") 3599 this = f" {this}" if this else "" 3600 3601 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}" 3602 3603 def altersession_sql(self, expression: exp.AlterSession) -> str: 3604 items_sql = self.expressions(expression, flat=True) 3605 keyword = "UNSET" if expression.args.get("unset") else "SET" 3606 return f"{keyword} {items_sql}" 3607 3608 def add_column_sql(self, expression: exp.Expression) -> str: 3609 sql = self.sql(expression) 3610 if isinstance(expression, exp.Schema): 3611 column_text = " COLUMNS" 3612 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3613 column_text = " COLUMN" 3614 else: 3615 column_text = "" 3616 3617 return f"ADD{column_text} {sql}" 3618 3619 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3620 expressions = self.expressions(expression) 3621 exists = " IF EXISTS " if expression.args.get("exists") else " " 3622 return f"DROP{exists}{expressions}" 3623 3624 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3625 return f"ADD {self.expressions(expression, indent=False)}" 3626 3627 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3628 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3629 location = self.sql(expression, "location") 3630 location = f" {location}" if location else "" 3631 return f"ADD {exists}{self.sql(expression.this)}{location}" 3632 3633 def distinct_sql(self, expression: exp.Distinct) -> str: 3634 this = self.expressions(expression, flat=True) 3635 3636 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3637 case = exp.case() 3638 for arg in expression.expressions: 3639 case = case.when(arg.is_(exp.null()), exp.null()) 3640 this = self.sql(case.else_(f"({this})")) 3641 3642 this = f" {this}" if this else "" 3643 3644 on = self.sql(expression, "on") 3645 on = f" ON {on}" if on else "" 3646 return f"DISTINCT{this}{on}" 3647 3648 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3649 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3650 3651 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3652 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3653 3654 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3655 this_sql = self.sql(expression, "this") 3656 expression_sql = self.sql(expression, "expression") 3657 kind = "MAX" if expression.args.get("max") else "MIN" 3658 return f"{this_sql} HAVING {kind} {expression_sql}" 3659 3660 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3661 return self.sql( 3662 exp.Cast( 3663 this=exp.Div(this=expression.this, expression=expression.expression), 3664 to=exp.DataType(this=exp.DataType.Type.INT), 3665 ) 3666 ) 3667 3668 def dpipe_sql(self, expression: exp.DPipe) -> str: 3669 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3670 return self.func( 3671 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3672 ) 3673 return self.binary(expression, "||") 3674 3675 def div_sql(self, expression: exp.Div) -> str: 3676 l, r = expression.left, expression.right 3677 3678 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3679 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3680 3681 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3682 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3683 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3684 3685 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3686 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3687 return self.sql( 3688 exp.cast( 3689 l / r, 3690 to=exp.DataType.Type.BIGINT, 3691 ) 3692 ) 3693 3694 return self.binary(expression, "/") 3695 3696 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3697 n = exp._wrap(expression.this, exp.Binary) 3698 d = exp._wrap(expression.expression, exp.Binary) 3699 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3700 3701 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3702 return self.binary(expression, "OVERLAPS") 3703 3704 def distance_sql(self, expression: exp.Distance) -> str: 3705 return self.binary(expression, "<->") 3706 3707 def dot_sql(self, expression: exp.Dot) -> str: 3708 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3709 3710 def eq_sql(self, expression: exp.EQ) -> str: 3711 return self.binary(expression, "=") 3712 3713 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3714 return self.binary(expression, ":=") 3715 3716 def escape_sql(self, expression: exp.Escape) -> str: 3717 return self.binary(expression, "ESCAPE") 3718 3719 def glob_sql(self, expression: exp.Glob) -> str: 3720 return self.binary(expression, "GLOB") 3721 3722 def gt_sql(self, expression: exp.GT) -> str: 3723 return self.binary(expression, ">") 3724 3725 def gte_sql(self, expression: exp.GTE) -> str: 3726 return self.binary(expression, ">=") 3727 3728 def is_sql(self, expression: exp.Is) -> str: 3729 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3730 return self.sql( 3731 expression.this if expression.expression.this else exp.not_(expression.this) 3732 ) 3733 return self.binary(expression, "IS") 3734 3735 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3736 this = expression.this 3737 rhs = expression.expression 3738 3739 if isinstance(expression, exp.Like): 3740 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3741 op = "LIKE" 3742 else: 3743 exp_class = exp.ILike 3744 op = "ILIKE" 3745 3746 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3747 exprs = rhs.this.unnest() 3748 3749 if isinstance(exprs, exp.Tuple): 3750 exprs = exprs.expressions 3751 3752 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3753 3754 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3755 for expr in exprs[1:]: 3756 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3757 3758 return self.sql(like_expr) 3759 3760 return self.binary(expression, op) 3761 3762 def like_sql(self, expression: exp.Like) -> str: 3763 return self._like_sql(expression) 3764 3765 def ilike_sql(self, expression: exp.ILike) -> str: 3766 return self._like_sql(expression) 3767 3768 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3769 return self.binary(expression, "SIMILAR TO") 3770 3771 def lt_sql(self, expression: exp.LT) -> str: 3772 return self.binary(expression, "<") 3773 3774 def lte_sql(self, expression: exp.LTE) -> str: 3775 return self.binary(expression, "<=") 3776 3777 def mod_sql(self, expression: exp.Mod) -> str: 3778 return self.binary(expression, "%") 3779 3780 def mul_sql(self, expression: exp.Mul) -> str: 3781 return self.binary(expression, "*") 3782 3783 def neq_sql(self, expression: exp.NEQ) -> str: 3784 return self.binary(expression, "<>") 3785 3786 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3787 return self.binary(expression, "IS NOT DISTINCT FROM") 3788 3789 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3790 return self.binary(expression, "IS DISTINCT FROM") 3791 3792 def slice_sql(self, expression: exp.Slice) -> str: 3793 return self.binary(expression, ":") 3794 3795 def sub_sql(self, expression: exp.Sub) -> str: 3796 return self.binary(expression, "-") 3797 3798 def trycast_sql(self, expression: exp.TryCast) -> str: 3799 return self.cast_sql(expression, safe_prefix="TRY_") 3800 3801 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3802 return self.cast_sql(expression) 3803 3804 def try_sql(self, expression: exp.Try) -> str: 3805 if not self.TRY_SUPPORTED: 3806 self.unsupported("Unsupported TRY function") 3807 return self.sql(expression, "this") 3808 3809 return self.func("TRY", expression.this) 3810 3811 def log_sql(self, expression: exp.Log) -> str: 3812 this = expression.this 3813 expr = expression.expression 3814 3815 if self.dialect.LOG_BASE_FIRST is False: 3816 this, expr = expr, this 3817 elif self.dialect.LOG_BASE_FIRST is None and expr: 3818 if this.name in ("2", "10"): 3819 return self.func(f"LOG{this.name}", expr) 3820 3821 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3822 3823 return self.func("LOG", this, expr) 3824 3825 def use_sql(self, expression: exp.Use) -> str: 3826 kind = self.sql(expression, "kind") 3827 kind = f" {kind}" if kind else "" 3828 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3829 this = f" {this}" if this else "" 3830 return f"USE{kind}{this}" 3831 3832 def binary(self, expression: exp.Binary, op: str) -> str: 3833 sqls: t.List[str] = [] 3834 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3835 binary_type = type(expression) 3836 3837 while stack: 3838 node = stack.pop() 3839 3840 if type(node) is binary_type: 3841 op_func = node.args.get("operator") 3842 if op_func: 3843 op = f"OPERATOR({self.sql(op_func)})" 3844 3845 stack.append(node.right) 3846 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3847 stack.append(node.left) 3848 else: 3849 sqls.append(self.sql(node)) 3850 3851 return "".join(sqls) 3852 3853 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3854 to_clause = self.sql(expression, "to") 3855 if to_clause: 3856 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3857 3858 return self.function_fallback_sql(expression) 3859 3860 def function_fallback_sql(self, expression: exp.Func) -> str: 3861 args = [] 3862 3863 for key in expression.arg_types: 3864 arg_value = expression.args.get(key) 3865 3866 if isinstance(arg_value, list): 3867 for value in arg_value: 3868 args.append(value) 3869 elif arg_value is not None: 3870 args.append(arg_value) 3871 3872 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3873 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3874 else: 3875 name = expression.sql_name() 3876 3877 return self.func(name, *args) 3878 3879 def func( 3880 self, 3881 name: str, 3882 *args: t.Optional[exp.Expression | str], 3883 prefix: str = "(", 3884 suffix: str = ")", 3885 normalize: bool = True, 3886 ) -> str: 3887 name = self.normalize_func(name) if normalize else name 3888 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3889 3890 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3891 arg_sqls = tuple( 3892 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3893 ) 3894 if self.pretty and self.too_wide(arg_sqls): 3895 return self.indent( 3896 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3897 ) 3898 return sep.join(arg_sqls) 3899 3900 def too_wide(self, args: t.Iterable) -> bool: 3901 return sum(len(arg) for arg in args) > self.max_text_width 3902 3903 def format_time( 3904 self, 3905 expression: exp.Expression, 3906 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3907 inverse_time_trie: t.Optional[t.Dict] = None, 3908 ) -> t.Optional[str]: 3909 return format_time( 3910 self.sql(expression, "format"), 3911 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3912 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3913 ) 3914 3915 def expressions( 3916 self, 3917 expression: t.Optional[exp.Expression] = None, 3918 key: t.Optional[str] = None, 3919 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3920 flat: bool = False, 3921 indent: bool = True, 3922 skip_first: bool = False, 3923 skip_last: bool = False, 3924 sep: str = ", ", 3925 prefix: str = "", 3926 dynamic: bool = False, 3927 new_line: bool = False, 3928 ) -> str: 3929 expressions = expression.args.get(key or "expressions") if expression else sqls 3930 3931 if not expressions: 3932 return "" 3933 3934 if flat: 3935 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3936 3937 num_sqls = len(expressions) 3938 result_sqls = [] 3939 3940 for i, e in enumerate(expressions): 3941 sql = self.sql(e, comment=False) 3942 if not sql: 3943 continue 3944 3945 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3946 3947 if self.pretty: 3948 if self.leading_comma: 3949 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3950 else: 3951 result_sqls.append( 3952 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3953 ) 3954 else: 3955 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3956 3957 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3958 if new_line: 3959 result_sqls.insert(0, "") 3960 result_sqls.append("") 3961 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3962 else: 3963 result_sql = "".join(result_sqls) 3964 3965 return ( 3966 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3967 if indent 3968 else result_sql 3969 ) 3970 3971 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3972 flat = flat or isinstance(expression.parent, exp.Properties) 3973 expressions_sql = self.expressions(expression, flat=flat) 3974 if flat: 3975 return f"{op} {expressions_sql}" 3976 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3977 3978 def naked_property(self, expression: exp.Property) -> str: 3979 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3980 if not property_name: 3981 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3982 return f"{property_name} {self.sql(expression, 'this')}" 3983 3984 def tag_sql(self, expression: exp.Tag) -> str: 3985 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3986 3987 def token_sql(self, token_type: TokenType) -> str: 3988 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3989 3990 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3991 this = self.sql(expression, "this") 3992 expressions = self.no_identify(self.expressions, expression) 3993 expressions = ( 3994 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3995 ) 3996 return f"{this}{expressions}" if expressions.strip() != "" else this 3997 3998 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3999 this = self.sql(expression, "this") 4000 expressions = self.expressions(expression, flat=True) 4001 return f"{this}({expressions})" 4002 4003 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4004 return self.binary(expression, "=>") 4005 4006 def when_sql(self, expression: exp.When) -> str: 4007 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4008 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4009 condition = self.sql(expression, "condition") 4010 condition = f" AND {condition}" if condition else "" 4011 4012 then_expression = expression.args.get("then") 4013 if isinstance(then_expression, exp.Insert): 4014 this = self.sql(then_expression, "this") 4015 this = f"INSERT {this}" if this else "INSERT" 4016 then = self.sql(then_expression, "expression") 4017 then = f"{this} VALUES {then}" if then else this 4018 elif isinstance(then_expression, exp.Update): 4019 if isinstance(then_expression.args.get("expressions"), exp.Star): 4020 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4021 else: 4022 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4023 else: 4024 then = self.sql(then_expression) 4025 return f"WHEN {matched}{source}{condition} THEN {then}" 4026 4027 def whens_sql(self, expression: exp.Whens) -> str: 4028 return self.expressions(expression, sep=" ", indent=False) 4029 4030 def merge_sql(self, expression: exp.Merge) -> str: 4031 table = expression.this 4032 table_alias = "" 4033 4034 hints = table.args.get("hints") 4035 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4036 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4037 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4038 4039 this = self.sql(table) 4040 using = f"USING {self.sql(expression, 'using')}" 4041 on = f"ON {self.sql(expression, 'on')}" 4042 whens = self.sql(expression, "whens") 4043 4044 returning = self.sql(expression, "returning") 4045 if returning: 4046 whens = f"{whens}{returning}" 4047 4048 sep = self.sep() 4049 4050 return self.prepend_ctes( 4051 expression, 4052 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4053 ) 4054 4055 @unsupported_args("format") 4056 def tochar_sql(self, expression: exp.ToChar) -> str: 4057 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4058 4059 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4060 if not self.SUPPORTS_TO_NUMBER: 4061 self.unsupported("Unsupported TO_NUMBER function") 4062 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4063 4064 fmt = expression.args.get("format") 4065 if not fmt: 4066 self.unsupported("Conversion format is required for TO_NUMBER") 4067 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4068 4069 return self.func("TO_NUMBER", expression.this, fmt) 4070 4071 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4072 this = self.sql(expression, "this") 4073 kind = self.sql(expression, "kind") 4074 settings_sql = self.expressions(expression, key="settings", sep=" ") 4075 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4076 return f"{this}({kind}{args})" 4077 4078 def dictrange_sql(self, expression: exp.DictRange) -> str: 4079 this = self.sql(expression, "this") 4080 max = self.sql(expression, "max") 4081 min = self.sql(expression, "min") 4082 return f"{this}(MIN {min} MAX {max})" 4083 4084 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4085 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4086 4087 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4088 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4089 4090 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4091 def uniquekeyproperty_sql( 4092 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4093 ) -> str: 4094 return f"{prefix} ({self.expressions(expression, flat=True)})" 4095 4096 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4097 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4098 expressions = self.expressions(expression, flat=True) 4099 expressions = f" {self.wrap(expressions)}" if expressions else "" 4100 buckets = self.sql(expression, "buckets") 4101 kind = self.sql(expression, "kind") 4102 buckets = f" BUCKETS {buckets}" if buckets else "" 4103 order = self.sql(expression, "order") 4104 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4105 4106 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4107 return "" 4108 4109 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4110 expressions = self.expressions(expression, key="expressions", flat=True) 4111 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4112 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4113 buckets = self.sql(expression, "buckets") 4114 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4115 4116 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4117 this = self.sql(expression, "this") 4118 having = self.sql(expression, "having") 4119 4120 if having: 4121 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4122 4123 return self.func("ANY_VALUE", this) 4124 4125 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4126 transform = self.func("TRANSFORM", *expression.expressions) 4127 row_format_before = self.sql(expression, "row_format_before") 4128 row_format_before = f" {row_format_before}" if row_format_before else "" 4129 record_writer = self.sql(expression, "record_writer") 4130 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4131 using = f" USING {self.sql(expression, 'command_script')}" 4132 schema = self.sql(expression, "schema") 4133 schema = f" AS {schema}" if schema else "" 4134 row_format_after = self.sql(expression, "row_format_after") 4135 row_format_after = f" {row_format_after}" if row_format_after else "" 4136 record_reader = self.sql(expression, "record_reader") 4137 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4138 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4139 4140 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4141 key_block_size = self.sql(expression, "key_block_size") 4142 if key_block_size: 4143 return f"KEY_BLOCK_SIZE = {key_block_size}" 4144 4145 using = self.sql(expression, "using") 4146 if using: 4147 return f"USING {using}" 4148 4149 parser = self.sql(expression, "parser") 4150 if parser: 4151 return f"WITH PARSER {parser}" 4152 4153 comment = self.sql(expression, "comment") 4154 if comment: 4155 return f"COMMENT {comment}" 4156 4157 visible = expression.args.get("visible") 4158 if visible is not None: 4159 return "VISIBLE" if visible else "INVISIBLE" 4160 4161 engine_attr = self.sql(expression, "engine_attr") 4162 if engine_attr: 4163 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4164 4165 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4166 if secondary_engine_attr: 4167 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4168 4169 self.unsupported("Unsupported index constraint option.") 4170 return "" 4171 4172 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4173 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4174 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4175 4176 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4177 kind = self.sql(expression, "kind") 4178 kind = f"{kind} INDEX" if kind else "INDEX" 4179 this = self.sql(expression, "this") 4180 this = f" {this}" if this else "" 4181 index_type = self.sql(expression, "index_type") 4182 index_type = f" USING {index_type}" if index_type else "" 4183 expressions = self.expressions(expression, flat=True) 4184 expressions = f" ({expressions})" if expressions else "" 4185 options = self.expressions(expression, key="options", sep=" ") 4186 options = f" {options}" if options else "" 4187 return f"{kind}{this}{index_type}{expressions}{options}" 4188 4189 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4190 if self.NVL2_SUPPORTED: 4191 return self.function_fallback_sql(expression) 4192 4193 case = exp.Case().when( 4194 expression.this.is_(exp.null()).not_(copy=False), 4195 expression.args["true"], 4196 copy=False, 4197 ) 4198 else_cond = expression.args.get("false") 4199 if else_cond: 4200 case.else_(else_cond, copy=False) 4201 4202 return self.sql(case) 4203 4204 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4205 this = self.sql(expression, "this") 4206 expr = self.sql(expression, "expression") 4207 iterator = self.sql(expression, "iterator") 4208 condition = self.sql(expression, "condition") 4209 condition = f" IF {condition}" if condition else "" 4210 return f"{this} FOR {expr} IN {iterator}{condition}" 4211 4212 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4213 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4214 4215 def opclass_sql(self, expression: exp.Opclass) -> str: 4216 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4217 4218 def predict_sql(self, expression: exp.Predict) -> str: 4219 model = self.sql(expression, "this") 4220 model = f"MODEL {model}" 4221 table = self.sql(expression, "expression") 4222 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4223 parameters = self.sql(expression, "params_struct") 4224 return self.func("PREDICT", model, table, parameters or None) 4225 4226 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4227 model = self.sql(expression, "this") 4228 model = f"MODEL {model}" 4229 table = self.sql(expression, "expression") 4230 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4231 parameters = self.sql(expression, "params_struct") 4232 return self.func("GENERATE_EMBEDDING", model, table, parameters or None) 4233 4234 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4235 this_sql = self.sql(expression, "this") 4236 if isinstance(expression.this, exp.Table): 4237 this_sql = f"TABLE {this_sql}" 4238 4239 return self.func( 4240 "FEATURES_AT_TIME", 4241 this_sql, 4242 expression.args.get("time"), 4243 expression.args.get("num_rows"), 4244 expression.args.get("ignore_feature_nulls"), 4245 ) 4246 4247 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4248 this_sql = self.sql(expression, "this") 4249 if isinstance(expression.this, exp.Table): 4250 this_sql = f"TABLE {this_sql}" 4251 4252 query_table = self.sql(expression, "query_table") 4253 if isinstance(expression.args["query_table"], exp.Table): 4254 query_table = f"TABLE {query_table}" 4255 4256 return self.func( 4257 "VECTOR_SEARCH", 4258 this_sql, 4259 expression.args.get("column_to_search"), 4260 query_table, 4261 expression.args.get("query_column_to_search"), 4262 expression.args.get("top_k"), 4263 expression.args.get("distance_type"), 4264 expression.args.get("options"), 4265 ) 4266 4267 def forin_sql(self, expression: exp.ForIn) -> str: 4268 this = self.sql(expression, "this") 4269 expression_sql = self.sql(expression, "expression") 4270 return f"FOR {this} DO {expression_sql}" 4271 4272 def refresh_sql(self, expression: exp.Refresh) -> str: 4273 this = self.sql(expression, "this") 4274 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4275 return f"REFRESH {table}{this}" 4276 4277 def toarray_sql(self, expression: exp.ToArray) -> str: 4278 arg = expression.this 4279 if not arg.type: 4280 from sqlglot.optimizer.annotate_types import annotate_types 4281 4282 arg = annotate_types(arg, dialect=self.dialect) 4283 4284 if arg.is_type(exp.DataType.Type.ARRAY): 4285 return self.sql(arg) 4286 4287 cond_for_null = arg.is_(exp.null()) 4288 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4289 4290 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4291 this = expression.this 4292 time_format = self.format_time(expression) 4293 4294 if time_format: 4295 return self.sql( 4296 exp.cast( 4297 exp.StrToTime(this=this, format=expression.args["format"]), 4298 exp.DataType.Type.TIME, 4299 ) 4300 ) 4301 4302 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4303 return self.sql(this) 4304 4305 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4306 4307 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4308 this = expression.this 4309 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4310 return self.sql(this) 4311 4312 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4313 4314 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4315 this = expression.this 4316 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4317 return self.sql(this) 4318 4319 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4320 4321 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4322 this = expression.this 4323 time_format = self.format_time(expression) 4324 4325 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4326 return self.sql( 4327 exp.cast( 4328 exp.StrToTime(this=this, format=expression.args["format"]), 4329 exp.DataType.Type.DATE, 4330 ) 4331 ) 4332 4333 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4334 return self.sql(this) 4335 4336 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4337 4338 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4339 return self.sql( 4340 exp.func( 4341 "DATEDIFF", 4342 expression.this, 4343 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4344 "day", 4345 ) 4346 ) 4347 4348 def lastday_sql(self, expression: exp.LastDay) -> str: 4349 if self.LAST_DAY_SUPPORTS_DATE_PART: 4350 return self.function_fallback_sql(expression) 4351 4352 unit = expression.text("unit") 4353 if unit and unit != "MONTH": 4354 self.unsupported("Date parts are not supported in LAST_DAY.") 4355 4356 return self.func("LAST_DAY", expression.this) 4357 4358 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4359 from sqlglot.dialects.dialect import unit_to_str 4360 4361 return self.func( 4362 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4363 ) 4364 4365 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4366 if self.CAN_IMPLEMENT_ARRAY_ANY: 4367 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4368 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4369 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4370 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4371 4372 from sqlglot.dialects import Dialect 4373 4374 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4375 if self.dialect.__class__ != Dialect: 4376 self.unsupported("ARRAY_ANY is unsupported") 4377 4378 return self.function_fallback_sql(expression) 4379 4380 def struct_sql(self, expression: exp.Struct) -> str: 4381 expression.set( 4382 "expressions", 4383 [ 4384 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4385 if isinstance(e, exp.PropertyEQ) 4386 else e 4387 for e in expression.expressions 4388 ], 4389 ) 4390 4391 return self.function_fallback_sql(expression) 4392 4393 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4394 low = self.sql(expression, "this") 4395 high = self.sql(expression, "expression") 4396 4397 return f"{low} TO {high}" 4398 4399 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4400 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4401 tables = f" {self.expressions(expression)}" 4402 4403 exists = " IF EXISTS" if expression.args.get("exists") else "" 4404 4405 on_cluster = self.sql(expression, "cluster") 4406 on_cluster = f" {on_cluster}" if on_cluster else "" 4407 4408 identity = self.sql(expression, "identity") 4409 identity = f" {identity} IDENTITY" if identity else "" 4410 4411 option = self.sql(expression, "option") 4412 option = f" {option}" if option else "" 4413 4414 partition = self.sql(expression, "partition") 4415 partition = f" {partition}" if partition else "" 4416 4417 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4418 4419 # This transpiles T-SQL's CONVERT function 4420 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4421 def convert_sql(self, expression: exp.Convert) -> str: 4422 to = expression.this 4423 value = expression.expression 4424 style = expression.args.get("style") 4425 safe = expression.args.get("safe") 4426 strict = expression.args.get("strict") 4427 4428 if not to or not value: 4429 return "" 4430 4431 # Retrieve length of datatype and override to default if not specified 4432 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4433 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4434 4435 transformed: t.Optional[exp.Expression] = None 4436 cast = exp.Cast if strict else exp.TryCast 4437 4438 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4439 if isinstance(style, exp.Literal) and style.is_int: 4440 from sqlglot.dialects.tsql import TSQL 4441 4442 style_value = style.name 4443 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4444 if not converted_style: 4445 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4446 4447 fmt = exp.Literal.string(converted_style) 4448 4449 if to.this == exp.DataType.Type.DATE: 4450 transformed = exp.StrToDate(this=value, format=fmt) 4451 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4452 transformed = exp.StrToTime(this=value, format=fmt) 4453 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4454 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4455 elif to.this == exp.DataType.Type.TEXT: 4456 transformed = exp.TimeToStr(this=value, format=fmt) 4457 4458 if not transformed: 4459 transformed = cast(this=value, to=to, safe=safe) 4460 4461 return self.sql(transformed) 4462 4463 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4464 this = expression.this 4465 if isinstance(this, exp.JSONPathWildcard): 4466 this = self.json_path_part(this) 4467 return f".{this}" if this else "" 4468 4469 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4470 return f".{this}" 4471 4472 this = self.json_path_part(this) 4473 return ( 4474 f"[{this}]" 4475 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4476 else f".{this}" 4477 ) 4478 4479 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4480 this = self.json_path_part(expression.this) 4481 return f"[{this}]" if this else "" 4482 4483 def _simplify_unless_literal(self, expression: E) -> E: 4484 if not isinstance(expression, exp.Literal): 4485 from sqlglot.optimizer.simplify import simplify 4486 4487 expression = simplify(expression, dialect=self.dialect) 4488 4489 return expression 4490 4491 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4492 this = expression.this 4493 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4494 self.unsupported( 4495 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4496 ) 4497 return self.sql(this) 4498 4499 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4500 # The first modifier here will be the one closest to the AggFunc's arg 4501 mods = sorted( 4502 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4503 key=lambda x: 0 4504 if isinstance(x, exp.HavingMax) 4505 else (1 if isinstance(x, exp.Order) else 2), 4506 ) 4507 4508 if mods: 4509 mod = mods[0] 4510 this = expression.__class__(this=mod.this.copy()) 4511 this.meta["inline"] = True 4512 mod.this.replace(this) 4513 return self.sql(expression.this) 4514 4515 agg_func = expression.find(exp.AggFunc) 4516 4517 if agg_func: 4518 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4519 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4520 4521 return f"{self.sql(expression, 'this')} {text}" 4522 4523 def _replace_line_breaks(self, string: str) -> str: 4524 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4525 if self.pretty: 4526 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4527 return string 4528 4529 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4530 option = self.sql(expression, "this") 4531 4532 if expression.expressions: 4533 upper = option.upper() 4534 4535 # Snowflake FILE_FORMAT options are separated by whitespace 4536 sep = " " if upper == "FILE_FORMAT" else ", " 4537 4538 # Databricks copy/format options do not set their list of values with EQ 4539 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4540 values = self.expressions(expression, flat=True, sep=sep) 4541 return f"{option}{op}({values})" 4542 4543 value = self.sql(expression, "expression") 4544 4545 if not value: 4546 return option 4547 4548 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4549 4550 return f"{option}{op}{value}" 4551 4552 def credentials_sql(self, expression: exp.Credentials) -> str: 4553 cred_expr = expression.args.get("credentials") 4554 if isinstance(cred_expr, exp.Literal): 4555 # Redshift case: CREDENTIALS <string> 4556 credentials = self.sql(expression, "credentials") 4557 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4558 else: 4559 # Snowflake case: CREDENTIALS = (...) 4560 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4561 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4562 4563 storage = self.sql(expression, "storage") 4564 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4565 4566 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4567 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4568 4569 iam_role = self.sql(expression, "iam_role") 4570 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4571 4572 region = self.sql(expression, "region") 4573 region = f" REGION {region}" if region else "" 4574 4575 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4576 4577 def copy_sql(self, expression: exp.Copy) -> str: 4578 this = self.sql(expression, "this") 4579 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4580 4581 credentials = self.sql(expression, "credentials") 4582 credentials = self.seg(credentials) if credentials else "" 4583 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4584 files = self.expressions(expression, key="files", flat=True) 4585 4586 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4587 params = self.expressions( 4588 expression, 4589 key="params", 4590 sep=sep, 4591 new_line=True, 4592 skip_last=True, 4593 skip_first=True, 4594 indent=self.COPY_PARAMS_ARE_WRAPPED, 4595 ) 4596 4597 if params: 4598 if self.COPY_PARAMS_ARE_WRAPPED: 4599 params = f" WITH ({params})" 4600 elif not self.pretty: 4601 params = f" {params}" 4602 4603 return f"COPY{this}{kind} {files}{credentials}{params}" 4604 4605 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4606 return "" 4607 4608 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4609 on_sql = "ON" if expression.args.get("on") else "OFF" 4610 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4611 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4612 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4613 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4614 4615 if filter_col or retention_period: 4616 on_sql = self.func("ON", filter_col, retention_period) 4617 4618 return f"DATA_DELETION={on_sql}" 4619 4620 def maskingpolicycolumnconstraint_sql( 4621 self, expression: exp.MaskingPolicyColumnConstraint 4622 ) -> str: 4623 this = self.sql(expression, "this") 4624 expressions = self.expressions(expression, flat=True) 4625 expressions = f" USING ({expressions})" if expressions else "" 4626 return f"MASKING POLICY {this}{expressions}" 4627 4628 def gapfill_sql(self, expression: exp.GapFill) -> str: 4629 this = self.sql(expression, "this") 4630 this = f"TABLE {this}" 4631 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4632 4633 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4634 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4635 4636 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4637 this = self.sql(expression, "this") 4638 expr = expression.expression 4639 4640 if isinstance(expr, exp.Func): 4641 # T-SQL's CLR functions are case sensitive 4642 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4643 else: 4644 expr = self.sql(expression, "expression") 4645 4646 return self.scope_resolution(expr, this) 4647 4648 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4649 if self.PARSE_JSON_NAME is None: 4650 return self.sql(expression.this) 4651 4652 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4653 4654 def rand_sql(self, expression: exp.Rand) -> str: 4655 lower = self.sql(expression, "lower") 4656 upper = self.sql(expression, "upper") 4657 4658 if lower and upper: 4659 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4660 return self.func("RAND", expression.this) 4661 4662 def changes_sql(self, expression: exp.Changes) -> str: 4663 information = self.sql(expression, "information") 4664 information = f"INFORMATION => {information}" 4665 at_before = self.sql(expression, "at_before") 4666 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4667 end = self.sql(expression, "end") 4668 end = f"{self.seg('')}{end}" if end else "" 4669 4670 return f"CHANGES ({information}){at_before}{end}" 4671 4672 def pad_sql(self, expression: exp.Pad) -> str: 4673 prefix = "L" if expression.args.get("is_left") else "R" 4674 4675 fill_pattern = self.sql(expression, "fill_pattern") or None 4676 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4677 fill_pattern = "' '" 4678 4679 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4680 4681 def summarize_sql(self, expression: exp.Summarize) -> str: 4682 table = " TABLE" if expression.args.get("table") else "" 4683 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4684 4685 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4686 generate_series = exp.GenerateSeries(**expression.args) 4687 4688 parent = expression.parent 4689 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4690 parent = parent.parent 4691 4692 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4693 return self.sql(exp.Unnest(expressions=[generate_series])) 4694 4695 if isinstance(parent, exp.Select): 4696 self.unsupported("GenerateSeries projection unnesting is not supported.") 4697 4698 return self.sql(generate_series) 4699 4700 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4701 exprs = expression.expressions 4702 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4703 if len(exprs) == 0: 4704 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4705 else: 4706 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4707 else: 4708 rhs = self.expressions(expression) # type: ignore 4709 4710 return self.func(name, expression.this, rhs or None) 4711 4712 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4713 if self.SUPPORTS_CONVERT_TIMEZONE: 4714 return self.function_fallback_sql(expression) 4715 4716 source_tz = expression.args.get("source_tz") 4717 target_tz = expression.args.get("target_tz") 4718 timestamp = expression.args.get("timestamp") 4719 4720 if source_tz and timestamp: 4721 timestamp = exp.AtTimeZone( 4722 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4723 ) 4724 4725 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4726 4727 return self.sql(expr) 4728 4729 def json_sql(self, expression: exp.JSON) -> str: 4730 this = self.sql(expression, "this") 4731 this = f" {this}" if this else "" 4732 4733 _with = expression.args.get("with") 4734 4735 if _with is None: 4736 with_sql = "" 4737 elif not _with: 4738 with_sql = " WITHOUT" 4739 else: 4740 with_sql = " WITH" 4741 4742 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4743 4744 return f"JSON{this}{with_sql}{unique_sql}" 4745 4746 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4747 def _generate_on_options(arg: t.Any) -> str: 4748 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4749 4750 path = self.sql(expression, "path") 4751 returning = self.sql(expression, "returning") 4752 returning = f" RETURNING {returning}" if returning else "" 4753 4754 on_condition = self.sql(expression, "on_condition") 4755 on_condition = f" {on_condition}" if on_condition else "" 4756 4757 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4758 4759 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4760 else_ = "ELSE " if expression.args.get("else_") else "" 4761 condition = self.sql(expression, "expression") 4762 condition = f"WHEN {condition} THEN " if condition else else_ 4763 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4764 return f"{condition}{insert}" 4765 4766 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4767 kind = self.sql(expression, "kind") 4768 expressions = self.seg(self.expressions(expression, sep=" ")) 4769 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4770 return res 4771 4772 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4773 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4774 empty = expression.args.get("empty") 4775 empty = ( 4776 f"DEFAULT {empty} ON EMPTY" 4777 if isinstance(empty, exp.Expression) 4778 else self.sql(expression, "empty") 4779 ) 4780 4781 error = expression.args.get("error") 4782 error = ( 4783 f"DEFAULT {error} ON ERROR" 4784 if isinstance(error, exp.Expression) 4785 else self.sql(expression, "error") 4786 ) 4787 4788 if error and empty: 4789 error = ( 4790 f"{empty} {error}" 4791 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4792 else f"{error} {empty}" 4793 ) 4794 empty = "" 4795 4796 null = self.sql(expression, "null") 4797 4798 return f"{empty}{error}{null}" 4799 4800 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4801 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4802 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4803 4804 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4805 this = self.sql(expression, "this") 4806 path = self.sql(expression, "path") 4807 4808 passing = self.expressions(expression, "passing") 4809 passing = f" PASSING {passing}" if passing else "" 4810 4811 on_condition = self.sql(expression, "on_condition") 4812 on_condition = f" {on_condition}" if on_condition else "" 4813 4814 path = f"{path}{passing}{on_condition}" 4815 4816 return self.func("JSON_EXISTS", this, path) 4817 4818 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4819 array_agg = self.function_fallback_sql(expression) 4820 4821 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4822 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4823 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4824 parent = expression.parent 4825 if isinstance(parent, exp.Filter): 4826 parent_cond = parent.expression.this 4827 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4828 else: 4829 this = expression.this 4830 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4831 if this.find(exp.Column): 4832 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4833 this_sql = ( 4834 self.expressions(this) 4835 if isinstance(this, exp.Distinct) 4836 else self.sql(expression, "this") 4837 ) 4838 4839 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4840 4841 return array_agg 4842 4843 def apply_sql(self, expression: exp.Apply) -> str: 4844 this = self.sql(expression, "this") 4845 expr = self.sql(expression, "expression") 4846 4847 return f"{this} APPLY({expr})" 4848 4849 def _grant_or_revoke_sql( 4850 self, 4851 expression: exp.Grant | exp.Revoke, 4852 keyword: str, 4853 preposition: str, 4854 grant_option_prefix: str = "", 4855 grant_option_suffix: str = "", 4856 ) -> str: 4857 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4858 4859 kind = self.sql(expression, "kind") 4860 kind = f" {kind}" if kind else "" 4861 4862 securable = self.sql(expression, "securable") 4863 securable = f" {securable}" if securable else "" 4864 4865 principals = self.expressions(expression, key="principals", flat=True) 4866 4867 if not expression.args.get("grant_option"): 4868 grant_option_prefix = grant_option_suffix = "" 4869 4870 # cascade for revoke only 4871 cascade = self.sql(expression, "cascade") 4872 cascade = f" {cascade}" if cascade else "" 4873 4874 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 4875 4876 def grant_sql(self, expression: exp.Grant) -> str: 4877 return self._grant_or_revoke_sql( 4878 expression, 4879 keyword="GRANT", 4880 preposition="TO", 4881 grant_option_suffix=" WITH GRANT OPTION", 4882 ) 4883 4884 def revoke_sql(self, expression: exp.Revoke) -> str: 4885 return self._grant_or_revoke_sql( 4886 expression, 4887 keyword="REVOKE", 4888 preposition="FROM", 4889 grant_option_prefix="GRANT OPTION FOR ", 4890 ) 4891 4892 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4893 this = self.sql(expression, "this") 4894 columns = self.expressions(expression, flat=True) 4895 columns = f"({columns})" if columns else "" 4896 4897 return f"{this}{columns}" 4898 4899 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4900 this = self.sql(expression, "this") 4901 4902 kind = self.sql(expression, "kind") 4903 kind = f"{kind} " if kind else "" 4904 4905 return f"{kind}{this}" 4906 4907 def columns_sql(self, expression: exp.Columns): 4908 func = self.function_fallback_sql(expression) 4909 if expression.args.get("unpack"): 4910 func = f"*{func}" 4911 4912 return func 4913 4914 def overlay_sql(self, expression: exp.Overlay): 4915 this = self.sql(expression, "this") 4916 expr = self.sql(expression, "expression") 4917 from_sql = self.sql(expression, "from") 4918 for_sql = self.sql(expression, "for") 4919 for_sql = f" FOR {for_sql}" if for_sql else "" 4920 4921 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4922 4923 @unsupported_args("format") 4924 def todouble_sql(self, expression: exp.ToDouble) -> str: 4925 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4926 4927 def string_sql(self, expression: exp.String) -> str: 4928 this = expression.this 4929 zone = expression.args.get("zone") 4930 4931 if zone: 4932 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4933 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4934 # set for source_tz to transpile the time conversion before the STRING cast 4935 this = exp.ConvertTimezone( 4936 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4937 ) 4938 4939 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4940 4941 def median_sql(self, expression: exp.Median): 4942 if not self.SUPPORTS_MEDIAN: 4943 return self.sql( 4944 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4945 ) 4946 4947 return self.function_fallback_sql(expression) 4948 4949 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4950 filler = self.sql(expression, "this") 4951 filler = f" {filler}" if filler else "" 4952 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4953 return f"TRUNCATE{filler} {with_count}" 4954 4955 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4956 if self.SUPPORTS_UNIX_SECONDS: 4957 return self.function_fallback_sql(expression) 4958 4959 start_ts = exp.cast( 4960 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4961 ) 4962 4963 return self.sql( 4964 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4965 ) 4966 4967 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4968 dim = expression.expression 4969 4970 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4971 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4972 if not (dim.is_int and dim.name == "1"): 4973 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4974 dim = None 4975 4976 # If dimension is required but not specified, default initialize it 4977 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4978 dim = exp.Literal.number(1) 4979 4980 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4981 4982 def attach_sql(self, expression: exp.Attach) -> str: 4983 this = self.sql(expression, "this") 4984 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4985 expressions = self.expressions(expression) 4986 expressions = f" ({expressions})" if expressions else "" 4987 4988 return f"ATTACH{exists_sql} {this}{expressions}" 4989 4990 def detach_sql(self, expression: exp.Detach) -> str: 4991 this = self.sql(expression, "this") 4992 # the DATABASE keyword is required if IF EXISTS is set 4993 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4994 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4995 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4996 4997 return f"DETACH{exists_sql} {this}" 4998 4999 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5000 this = self.sql(expression, "this") 5001 value = self.sql(expression, "expression") 5002 value = f" {value}" if value else "" 5003 return f"{this}{value}" 5004 5005 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5006 return ( 5007 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5008 ) 5009 5010 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5011 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5012 encode = f"{encode} {self.sql(expression, 'this')}" 5013 5014 properties = expression.args.get("properties") 5015 if properties: 5016 encode = f"{encode} {self.properties(properties)}" 5017 5018 return encode 5019 5020 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5021 this = self.sql(expression, "this") 5022 include = f"INCLUDE {this}" 5023 5024 column_def = self.sql(expression, "column_def") 5025 if column_def: 5026 include = f"{include} {column_def}" 5027 5028 alias = self.sql(expression, "alias") 5029 if alias: 5030 include = f"{include} AS {alias}" 5031 5032 return include 5033 5034 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5035 name = f"NAME {self.sql(expression, 'this')}" 5036 return self.func("XMLELEMENT", name, *expression.expressions) 5037 5038 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5039 this = self.sql(expression, "this") 5040 expr = self.sql(expression, "expression") 5041 expr = f"({expr})" if expr else "" 5042 return f"{this}{expr}" 5043 5044 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5045 partitions = self.expressions(expression, "partition_expressions") 5046 create = self.expressions(expression, "create_expressions") 5047 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5048 5049 def partitionbyrangepropertydynamic_sql( 5050 self, expression: exp.PartitionByRangePropertyDynamic 5051 ) -> str: 5052 start = self.sql(expression, "start") 5053 end = self.sql(expression, "end") 5054 5055 every = expression.args["every"] 5056 if isinstance(every, exp.Interval) and every.this.is_string: 5057 every.this.replace(exp.Literal.number(every.name)) 5058 5059 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5060 5061 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5062 name = self.sql(expression, "this") 5063 values = self.expressions(expression, flat=True) 5064 5065 return f"NAME {name} VALUE {values}" 5066 5067 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5068 kind = self.sql(expression, "kind") 5069 sample = self.sql(expression, "sample") 5070 return f"SAMPLE {sample} {kind}" 5071 5072 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5073 kind = self.sql(expression, "kind") 5074 option = self.sql(expression, "option") 5075 option = f" {option}" if option else "" 5076 this = self.sql(expression, "this") 5077 this = f" {this}" if this else "" 5078 columns = self.expressions(expression) 5079 columns = f" {columns}" if columns else "" 5080 return f"{kind}{option} STATISTICS{this}{columns}" 5081 5082 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5083 this = self.sql(expression, "this") 5084 columns = self.expressions(expression) 5085 inner_expression = self.sql(expression, "expression") 5086 inner_expression = f" {inner_expression}" if inner_expression else "" 5087 update_options = self.sql(expression, "update_options") 5088 update_options = f" {update_options} UPDATE" if update_options else "" 5089 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5090 5091 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5092 kind = self.sql(expression, "kind") 5093 kind = f" {kind}" if kind else "" 5094 return f"DELETE{kind} STATISTICS" 5095 5096 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5097 inner_expression = self.sql(expression, "expression") 5098 return f"LIST CHAINED ROWS{inner_expression}" 5099 5100 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5101 kind = self.sql(expression, "kind") 5102 this = self.sql(expression, "this") 5103 this = f" {this}" if this else "" 5104 inner_expression = self.sql(expression, "expression") 5105 return f"VALIDATE {kind}{this}{inner_expression}" 5106 5107 def analyze_sql(self, expression: exp.Analyze) -> str: 5108 options = self.expressions(expression, key="options", sep=" ") 5109 options = f" {options}" if options else "" 5110 kind = self.sql(expression, "kind") 5111 kind = f" {kind}" if kind else "" 5112 this = self.sql(expression, "this") 5113 this = f" {this}" if this else "" 5114 mode = self.sql(expression, "mode") 5115 mode = f" {mode}" if mode else "" 5116 properties = self.sql(expression, "properties") 5117 properties = f" {properties}" if properties else "" 5118 partition = self.sql(expression, "partition") 5119 partition = f" {partition}" if partition else "" 5120 inner_expression = self.sql(expression, "expression") 5121 inner_expression = f" {inner_expression}" if inner_expression else "" 5122 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5123 5124 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5125 this = self.sql(expression, "this") 5126 namespaces = self.expressions(expression, key="namespaces") 5127 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5128 passing = self.expressions(expression, key="passing") 5129 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5130 columns = self.expressions(expression, key="columns") 5131 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5132 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5133 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5134 5135 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5136 this = self.sql(expression, "this") 5137 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5138 5139 def export_sql(self, expression: exp.Export) -> str: 5140 this = self.sql(expression, "this") 5141 connection = self.sql(expression, "connection") 5142 connection = f"WITH CONNECTION {connection} " if connection else "" 5143 options = self.sql(expression, "options") 5144 return f"EXPORT DATA {connection}{options} AS {this}" 5145 5146 def declare_sql(self, expression: exp.Declare) -> str: 5147 return f"DECLARE {self.expressions(expression, flat=True)}" 5148 5149 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5150 variable = self.sql(expression, "this") 5151 default = self.sql(expression, "default") 5152 default = f" = {default}" if default else "" 5153 5154 kind = self.sql(expression, "kind") 5155 if isinstance(expression.args.get("kind"), exp.Schema): 5156 kind = f"TABLE {kind}" 5157 5158 return f"{variable} AS {kind}{default}" 5159 5160 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5161 kind = self.sql(expression, "kind") 5162 this = self.sql(expression, "this") 5163 set = self.sql(expression, "expression") 5164 using = self.sql(expression, "using") 5165 using = f" USING {using}" if using else "" 5166 5167 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5168 5169 return f"{kind_sql} {this} SET {set}{using}" 5170 5171 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5172 params = self.expressions(expression, key="params", flat=True) 5173 return self.func(expression.name, *expression.expressions) + f"({params})" 5174 5175 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5176 return self.func(expression.name, *expression.expressions) 5177 5178 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5179 return self.anonymousaggfunc_sql(expression) 5180 5181 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5182 return self.parameterizedagg_sql(expression) 5183 5184 def show_sql(self, expression: exp.Show) -> str: 5185 self.unsupported("Unsupported SHOW statement") 5186 return "" 5187 5188 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5189 # Snowflake GET/PUT statements: 5190 # PUT <file> <internalStage> <properties> 5191 # GET <internalStage> <file> <properties> 5192 props = expression.args.get("properties") 5193 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5194 this = self.sql(expression, "this") 5195 target = self.sql(expression, "target") 5196 5197 if isinstance(expression, exp.Put): 5198 return f"PUT {this} {target}{props_sql}" 5199 else: 5200 return f"GET {target} {this}{props_sql}" 5201 5202 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5203 this = self.sql(expression, "this") 5204 expr = self.sql(expression, "expression") 5205 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5206 return f"TRANSLATE({this} USING {expr}{with_error})" 5207 5208 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5209 if self.SUPPORTS_DECODE_CASE: 5210 return self.func("DECODE", *expression.expressions) 5211 5212 expression, *expressions = expression.expressions 5213 5214 ifs = [] 5215 for search, result in zip(expressions[::2], expressions[1::2]): 5216 if isinstance(search, exp.Literal): 5217 ifs.append(exp.If(this=expression.eq(search), true=result)) 5218 elif isinstance(search, exp.Null): 5219 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5220 else: 5221 if isinstance(search, exp.Binary): 5222 search = exp.paren(search) 5223 5224 cond = exp.or_( 5225 expression.eq(search), 5226 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5227 copy=False, 5228 ) 5229 ifs.append(exp.If(this=cond, true=result)) 5230 5231 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5232 return self.sql(case) 5233 5234 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5235 this = self.sql(expression, "this") 5236 this = self.seg(this, sep="") 5237 dimensions = self.expressions( 5238 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5239 ) 5240 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5241 metrics = self.expressions( 5242 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5243 ) 5244 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5245 where = self.sql(expression, "where") 5246 where = self.seg(f"WHERE {where}") if where else "" 5247 body = self.indent(this + metrics + dimensions + where, skip_first=True) 5248 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5249 5250 def getextract_sql(self, expression: exp.GetExtract) -> str: 5251 this = expression.this 5252 expr = expression.expression 5253 5254 if not this.type or not expression.type: 5255 from sqlglot.optimizer.annotate_types import annotate_types 5256 5257 this = annotate_types(this, dialect=self.dialect) 5258 5259 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5260 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5261 5262 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5263 5264 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5265 return self.sql( 5266 exp.DateAdd( 5267 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5268 expression=expression.this, 5269 unit=exp.var("DAY"), 5270 ) 5271 ) 5272 5273 def space_sql(self: Generator, expression: exp.Space) -> str: 5274 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5275 5276 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5277 return f"BUILD {self.sql(expression, 'this')}" 5278 5279 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5280 method = self.sql(expression, "method") 5281 kind = expression.args.get("kind") 5282 if not kind: 5283 return f"REFRESH {method}" 5284 5285 every = self.sql(expression, "every") 5286 unit = self.sql(expression, "unit") 5287 every = f" EVERY {every} {unit}" if every else "" 5288 starts = self.sql(expression, "starts") 5289 starts = f" STARTS {starts}" if starts else "" 5290 5291 return f"REFRESH {method} ON {kind}{every}{starts}"
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 or 'always': Always quote. '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)
735 def __init__( 736 self, 737 pretty: t.Optional[bool] = None, 738 identify: str | bool = False, 739 normalize: bool = False, 740 pad: int = 2, 741 indent: int = 2, 742 normalize_functions: t.Optional[str | bool] = None, 743 unsupported_level: ErrorLevel = ErrorLevel.WARN, 744 max_unsupported: int = 3, 745 leading_comma: bool = False, 746 max_text_width: int = 80, 747 comments: bool = True, 748 dialect: DialectType = None, 749 ): 750 import sqlglot 751 from sqlglot.dialects import Dialect 752 753 self.pretty = pretty if pretty is not None else sqlglot.pretty 754 self.identify = identify 755 self.normalize = normalize 756 self.pad = pad 757 self._indent = indent 758 self.unsupported_level = unsupported_level 759 self.max_unsupported = max_unsupported 760 self.leading_comma = leading_comma 761 self.max_text_width = max_text_width 762 self.comments = comments 763 self.dialect = Dialect.get_or_raise(dialect) 764 765 # This is both a Dialect property and a Generator argument, so we prioritize the latter 766 self.normalize_functions = ( 767 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 768 ) 769 770 self.unsupported_messages: t.List[str] = [] 771 self._escaped_quote_end: str = ( 772 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 773 ) 774 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 775 776 self._next_name = name_sequence("_t") 777 778 self._identifier_start = self.dialect.IDENTIFIER_START 779 self._identifier_end = self.dialect.IDENTIFIER_END 780 781 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.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.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.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.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.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.Uuid'>: <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.WeekStart'>: <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.JSONPathKey'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <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'>}
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.VARCHAR: 'VARCHAR'>, <Type.NCHAR: 'NCHAR'>}
783 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 784 """ 785 Generates the SQL string corresponding to the given syntax tree. 786 787 Args: 788 expression: The syntax tree. 789 copy: Whether to copy the expression. The generator performs mutations so 790 it is safer to copy. 791 792 Returns: 793 The SQL string corresponding to `expression`. 794 """ 795 if copy: 796 expression = expression.copy() 797 798 expression = self.preprocess(expression) 799 800 self.unsupported_messages = [] 801 sql = self.sql(expression).strip() 802 803 if self.pretty: 804 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 805 806 if self.unsupported_level == ErrorLevel.IGNORE: 807 return sql 808 809 if self.unsupported_level == ErrorLevel.WARN: 810 for msg in self.unsupported_messages: 811 logger.warning(msg) 812 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 813 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 814 815 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:
817 def preprocess(self, expression: exp.Expression) -> exp.Expression: 818 """Apply generic preprocessing transformations to a given expression.""" 819 expression = self._move_ctes_to_top_level(expression) 820 821 if self.ENSURE_BOOLS: 822 from sqlglot.transforms import ensure_bools 823 824 expression = ensure_bools(expression) 825 826 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
850 def sanitize_comment(self, comment: str) -> str: 851 comment = " " + comment if comment[0].strip() else comment 852 comment = comment + " " if comment[-1].strip() else comment 853 854 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 855 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 856 comment = comment.replace("*/", "* /") 857 858 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
860 def maybe_comment( 861 self, 862 sql: str, 863 expression: t.Optional[exp.Expression] = None, 864 comments: t.Optional[t.List[str]] = None, 865 separated: bool = False, 866 ) -> str: 867 comments = ( 868 ((expression and expression.comments) if comments is None else comments) # type: ignore 869 if self.comments 870 else None 871 ) 872 873 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 874 return sql 875 876 comments_sql = " ".join( 877 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 878 ) 879 880 if not comments_sql: 881 return sql 882 883 comments_sql = self._replace_line_breaks(comments_sql) 884 885 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 886 return ( 887 f"{self.sep()}{comments_sql}{sql}" 888 if not sql or sql[0].isspace() 889 else f"{comments_sql}{self.sep()}{sql}" 890 ) 891 892 return f"{sql} {comments_sql}"
894 def wrap(self, expression: exp.Expression | str) -> str: 895 this_sql = ( 896 self.sql(expression) 897 if isinstance(expression, exp.UNWRAPPED_QUERIES) 898 else self.sql(expression, "this") 899 ) 900 if not this_sql: 901 return "()" 902 903 this_sql = self.indent(this_sql, level=1, pad=0) 904 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:
920 def indent( 921 self, 922 sql: str, 923 level: int = 0, 924 pad: t.Optional[int] = None, 925 skip_first: bool = False, 926 skip_last: bool = False, 927 ) -> str: 928 if not self.pretty or not sql: 929 return sql 930 931 pad = self.pad if pad is None else pad 932 lines = sql.split("\n") 933 934 return "\n".join( 935 ( 936 line 937 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 938 else f"{' ' * (level * self._indent + pad)}{line}" 939 ) 940 for i, line in enumerate(lines) 941 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
943 def sql( 944 self, 945 expression: t.Optional[str | exp.Expression], 946 key: t.Optional[str] = None, 947 comment: bool = True, 948 ) -> str: 949 if not expression: 950 return "" 951 952 if isinstance(expression, str): 953 return expression 954 955 if key: 956 value = expression.args.get(key) 957 if value: 958 return self.sql(value) 959 return "" 960 961 transform = self.TRANSFORMS.get(expression.__class__) 962 963 if callable(transform): 964 sql = transform(self, expression) 965 elif isinstance(expression, exp.Expression): 966 exp_handler_name = f"{expression.key}_sql" 967 968 if hasattr(self, exp_handler_name): 969 sql = getattr(self, exp_handler_name)(expression) 970 elif isinstance(expression, exp.Func): 971 sql = self.function_fallback_sql(expression) 972 elif isinstance(expression, exp.Property): 973 sql = self.property_sql(expression) 974 else: 975 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 976 else: 977 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 978 979 return self.maybe_comment(sql, expression) if self.comments and comment else sql
986 def cache_sql(self, expression: exp.Cache) -> str: 987 lazy = " LAZY" if expression.args.get("lazy") else "" 988 table = self.sql(expression, "this") 989 options = expression.args.get("options") 990 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 991 sql = self.sql(expression, "expression") 992 sql = f" AS{self.sep()}{sql}" if sql else "" 993 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 994 return self.prepend_ctes(expression, sql)
996 def characterset_sql(self, expression: exp.CharacterSet) -> str: 997 if isinstance(expression.parent, exp.Cast): 998 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 999 default = "DEFAULT " if expression.args.get("default") else "" 1000 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1014 def column_sql(self, expression: exp.Column) -> str: 1015 join_mark = " (+)" if expression.args.get("join_mark") else "" 1016 1017 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1018 join_mark = "" 1019 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1020 1021 return f"{self.column_parts(expression)}{join_mark}"
1029 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1030 column = self.sql(expression, "this") 1031 kind = self.sql(expression, "kind") 1032 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1033 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1034 kind = f"{sep}{kind}" if kind else "" 1035 constraints = f" {constraints}" if constraints else "" 1036 position = self.sql(expression, "position") 1037 position = f" {position}" if position else "" 1038 1039 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1040 kind = "" 1041 1042 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1049 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1050 this = self.sql(expression, "this") 1051 if expression.args.get("not_null"): 1052 persisted = " PERSISTED NOT NULL" 1053 elif expression.args.get("persisted"): 1054 persisted = " PERSISTED" 1055 else: 1056 persisted = "" 1057 1058 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1071 def generatedasidentitycolumnconstraint_sql( 1072 self, expression: exp.GeneratedAsIdentityColumnConstraint 1073 ) -> str: 1074 this = "" 1075 if expression.this is not None: 1076 on_null = " ON NULL" if expression.args.get("on_null") else "" 1077 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1078 1079 start = expression.args.get("start") 1080 start = f"START WITH {start}" if start else "" 1081 increment = expression.args.get("increment") 1082 increment = f" INCREMENT BY {increment}" if increment else "" 1083 minvalue = expression.args.get("minvalue") 1084 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1085 maxvalue = expression.args.get("maxvalue") 1086 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1087 cycle = expression.args.get("cycle") 1088 cycle_sql = "" 1089 1090 if cycle is not None: 1091 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1092 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1093 1094 sequence_opts = "" 1095 if start or increment or cycle_sql: 1096 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1097 sequence_opts = f" ({sequence_opts.strip()})" 1098 1099 expr = self.sql(expression, "expression") 1100 expr = f"({expr})" if expr else "IDENTITY" 1101 1102 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1104 def generatedasrowcolumnconstraint_sql( 1105 self, expression: exp.GeneratedAsRowColumnConstraint 1106 ) -> str: 1107 start = "START" if expression.args.get("start") else "END" 1108 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1109 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:
1119 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1120 desc = expression.args.get("desc") 1121 if desc is not None: 1122 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1123 options = self.expressions(expression, key="options", flat=True, sep=" ") 1124 options = f" {options}" if options else "" 1125 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1127 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1128 this = self.sql(expression, "this") 1129 this = f" {this}" if this else "" 1130 index_type = expression.args.get("index_type") 1131 index_type = f" USING {index_type}" if index_type else "" 1132 on_conflict = self.sql(expression, "on_conflict") 1133 on_conflict = f" {on_conflict}" if on_conflict else "" 1134 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1135 options = self.expressions(expression, key="options", flat=True, sep=" ") 1136 options = f" {options}" if options else "" 1137 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1142 def create_sql(self, expression: exp.Create) -> str: 1143 kind = self.sql(expression, "kind") 1144 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1145 properties = expression.args.get("properties") 1146 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1147 1148 this = self.createable_sql(expression, properties_locs) 1149 1150 properties_sql = "" 1151 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1152 exp.Properties.Location.POST_WITH 1153 ): 1154 props_ast = exp.Properties( 1155 expressions=[ 1156 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1157 *properties_locs[exp.Properties.Location.POST_WITH], 1158 ] 1159 ) 1160 props_ast.parent = expression 1161 properties_sql = self.sql(props_ast) 1162 1163 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1164 properties_sql = self.sep() + properties_sql 1165 elif not self.pretty: 1166 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1167 properties_sql = f" {properties_sql}" 1168 1169 begin = " BEGIN" if expression.args.get("begin") else "" 1170 end = " END" if expression.args.get("end") else "" 1171 1172 expression_sql = self.sql(expression, "expression") 1173 if expression_sql: 1174 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1175 1176 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1177 postalias_props_sql = "" 1178 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1179 postalias_props_sql = self.properties( 1180 exp.Properties( 1181 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1182 ), 1183 wrapped=False, 1184 ) 1185 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1186 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1187 1188 postindex_props_sql = "" 1189 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1190 postindex_props_sql = self.properties( 1191 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1192 wrapped=False, 1193 prefix=" ", 1194 ) 1195 1196 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1197 indexes = f" {indexes}" if indexes else "" 1198 index_sql = indexes + postindex_props_sql 1199 1200 replace = " OR REPLACE" if expression.args.get("replace") else "" 1201 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1202 unique = " UNIQUE" if expression.args.get("unique") else "" 1203 1204 clustered = expression.args.get("clustered") 1205 if clustered is None: 1206 clustered_sql = "" 1207 elif clustered: 1208 clustered_sql = " CLUSTERED COLUMNSTORE" 1209 else: 1210 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1211 1212 postcreate_props_sql = "" 1213 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1214 postcreate_props_sql = self.properties( 1215 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1216 sep=" ", 1217 prefix=" ", 1218 wrapped=False, 1219 ) 1220 1221 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1222 1223 postexpression_props_sql = "" 1224 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1225 postexpression_props_sql = self.properties( 1226 exp.Properties( 1227 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1228 ), 1229 sep=" ", 1230 prefix=" ", 1231 wrapped=False, 1232 ) 1233 1234 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1235 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1236 no_schema_binding = ( 1237 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1238 ) 1239 1240 clone = self.sql(expression, "clone") 1241 clone = f" {clone}" if clone else "" 1242 1243 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1244 properties_expression = f"{expression_sql}{properties_sql}" 1245 else: 1246 properties_expression = f"{properties_sql}{expression_sql}" 1247 1248 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1249 return self.prepend_ctes(expression, expression_sql)
1251 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1252 start = self.sql(expression, "start") 1253 start = f"START WITH {start}" if start else "" 1254 increment = self.sql(expression, "increment") 1255 increment = f" INCREMENT BY {increment}" if increment else "" 1256 minvalue = self.sql(expression, "minvalue") 1257 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1258 maxvalue = self.sql(expression, "maxvalue") 1259 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1260 owned = self.sql(expression, "owned") 1261 owned = f" OWNED BY {owned}" if owned else "" 1262 1263 cache = expression.args.get("cache") 1264 if cache is None: 1265 cache_str = "" 1266 elif cache is True: 1267 cache_str = " CACHE" 1268 else: 1269 cache_str = f" CACHE {cache}" 1270 1271 options = self.expressions(expression, key="options", flat=True, sep=" ") 1272 options = f" {options}" if options else "" 1273 1274 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1276 def clone_sql(self, expression: exp.Clone) -> str: 1277 this = self.sql(expression, "this") 1278 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1279 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1280 return f"{shallow}{keyword} {this}"
1282 def describe_sql(self, expression: exp.Describe) -> str: 1283 style = expression.args.get("style") 1284 style = f" {style}" if style else "" 1285 partition = self.sql(expression, "partition") 1286 partition = f" {partition}" if partition else "" 1287 format = self.sql(expression, "format") 1288 format = f" {format}" if format else "" 1289 1290 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1302 def with_sql(self, expression: exp.With) -> str: 1303 sql = self.expressions(expression, flat=True) 1304 recursive = ( 1305 "RECURSIVE " 1306 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1307 else "" 1308 ) 1309 search = self.sql(expression, "search") 1310 search = f" {search}" if search else "" 1311 1312 return f"WITH {recursive}{sql}{search}"
1314 def cte_sql(self, expression: exp.CTE) -> str: 1315 alias = expression.args.get("alias") 1316 if alias: 1317 alias.add_comments(expression.pop_comments()) 1318 1319 alias_sql = self.sql(expression, "alias") 1320 1321 materialized = expression.args.get("materialized") 1322 if materialized is False: 1323 materialized = "NOT MATERIALIZED " 1324 elif materialized: 1325 materialized = "MATERIALIZED " 1326 1327 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1329 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1330 alias = self.sql(expression, "this") 1331 columns = self.expressions(expression, key="columns", flat=True) 1332 columns = f"({columns})" if columns else "" 1333 1334 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1335 columns = "" 1336 self.unsupported("Named columns are not supported in table alias.") 1337 1338 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1339 alias = self._next_name() 1340 1341 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1349 def hexstring_sql( 1350 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1351 ) -> str: 1352 this = self.sql(expression, "this") 1353 is_integer_type = expression.args.get("is_integer") 1354 1355 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1356 not self.dialect.HEX_START and not binary_function_repr 1357 ): 1358 # Integer representation will be returned if: 1359 # - The read dialect treats the hex value as integer literal but not the write 1360 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1361 return f"{int(this, 16)}" 1362 1363 if not is_integer_type: 1364 # Read dialect treats the hex value as BINARY/BLOB 1365 if binary_function_repr: 1366 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1367 return self.func(binary_function_repr, exp.Literal.string(this)) 1368 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1369 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1370 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1371 1372 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1380 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1381 this = self.sql(expression, "this") 1382 escape = expression.args.get("escape") 1383 1384 if self.dialect.UNICODE_START: 1385 escape_substitute = r"\\\1" 1386 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1387 else: 1388 escape_substitute = r"\\u\1" 1389 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1390 1391 if escape: 1392 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1393 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1394 else: 1395 escape_pattern = ESCAPED_UNICODE_RE 1396 escape_sql = "" 1397 1398 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1399 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1400 1401 return f"{left_quote}{this}{right_quote}{escape_sql}"
1403 def rawstring_sql(self, expression: exp.RawString) -> str: 1404 string = expression.this 1405 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1406 string = string.replace("\\", "\\\\") 1407 1408 string = self.escape_str(string, escape_backslash=False) 1409 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1417 def datatype_sql(self, expression: exp.DataType) -> str: 1418 nested = "" 1419 values = "" 1420 interior = self.expressions(expression, flat=True) 1421 1422 type_value = expression.this 1423 if type_value in self.UNSUPPORTED_TYPES: 1424 self.unsupported( 1425 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1426 ) 1427 1428 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1429 type_sql = self.sql(expression, "kind") 1430 else: 1431 type_sql = ( 1432 self.TYPE_MAPPING.get(type_value, type_value.value) 1433 if isinstance(type_value, exp.DataType.Type) 1434 else type_value 1435 ) 1436 1437 if interior: 1438 if expression.args.get("nested"): 1439 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1440 if expression.args.get("values") is not None: 1441 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1442 values = self.expressions(expression, key="values", flat=True) 1443 values = f"{delimiters[0]}{values}{delimiters[1]}" 1444 elif type_value == exp.DataType.Type.INTERVAL: 1445 nested = f" {interior}" 1446 else: 1447 nested = f"({interior})" 1448 1449 type_sql = f"{type_sql}{nested}{values}" 1450 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1451 exp.DataType.Type.TIMETZ, 1452 exp.DataType.Type.TIMESTAMPTZ, 1453 ): 1454 type_sql = f"{type_sql} WITH TIME ZONE" 1455 1456 return type_sql
1458 def directory_sql(self, expression: exp.Directory) -> str: 1459 local = "LOCAL " if expression.args.get("local") else "" 1460 row_format = self.sql(expression, "row_format") 1461 row_format = f" {row_format}" if row_format else "" 1462 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1464 def delete_sql(self, expression: exp.Delete) -> str: 1465 this = self.sql(expression, "this") 1466 this = f" FROM {this}" if this else "" 1467 using = self.sql(expression, "using") 1468 using = f" USING {using}" if using else "" 1469 cluster = self.sql(expression, "cluster") 1470 cluster = f" {cluster}" if cluster else "" 1471 where = self.sql(expression, "where") 1472 returning = self.sql(expression, "returning") 1473 limit = self.sql(expression, "limit") 1474 tables = self.expressions(expression, key="tables") 1475 tables = f" {tables}" if tables else "" 1476 if self.RETURNING_END: 1477 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1478 else: 1479 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1480 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1482 def drop_sql(self, expression: exp.Drop) -> str: 1483 this = self.sql(expression, "this") 1484 expressions = self.expressions(expression, flat=True) 1485 expressions = f" ({expressions})" if expressions else "" 1486 kind = expression.args["kind"] 1487 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1488 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1489 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1490 on_cluster = self.sql(expression, "cluster") 1491 on_cluster = f" {on_cluster}" if on_cluster else "" 1492 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1493 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1494 cascade = " CASCADE" if expression.args.get("cascade") else "" 1495 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1496 purge = " PURGE" if expression.args.get("purge") else "" 1497 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1499 def set_operation(self, expression: exp.SetOperation) -> str: 1500 op_type = type(expression) 1501 op_name = op_type.key.upper() 1502 1503 distinct = expression.args.get("distinct") 1504 if ( 1505 distinct is False 1506 and op_type in (exp.Except, exp.Intersect) 1507 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1508 ): 1509 self.unsupported(f"{op_name} ALL is not supported") 1510 1511 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1512 1513 if distinct is None: 1514 distinct = default_distinct 1515 if distinct is None: 1516 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1517 1518 if distinct is default_distinct: 1519 distinct_or_all = "" 1520 else: 1521 distinct_or_all = " DISTINCT" if distinct else " ALL" 1522 1523 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1524 side_kind = f"{side_kind} " if side_kind else "" 1525 1526 by_name = " BY NAME" if expression.args.get("by_name") else "" 1527 on = self.expressions(expression, key="on", flat=True) 1528 on = f" ON ({on})" if on else "" 1529 1530 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1532 def set_operations(self, expression: exp.SetOperation) -> str: 1533 if not self.SET_OP_MODIFIERS: 1534 limit = expression.args.get("limit") 1535 order = expression.args.get("order") 1536 1537 if limit or order: 1538 select = self._move_ctes_to_top_level( 1539 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1540 ) 1541 1542 if limit: 1543 select = select.limit(limit.pop(), copy=False) 1544 if order: 1545 select = select.order_by(order.pop(), copy=False) 1546 return self.sql(select) 1547 1548 sqls: t.List[str] = [] 1549 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1550 1551 while stack: 1552 node = stack.pop() 1553 1554 if isinstance(node, exp.SetOperation): 1555 stack.append(node.expression) 1556 stack.append( 1557 self.maybe_comment( 1558 self.set_operation(node), comments=node.comments, separated=True 1559 ) 1560 ) 1561 stack.append(node.this) 1562 else: 1563 sqls.append(self.sql(node)) 1564 1565 this = self.sep().join(sqls) 1566 this = self.query_modifiers(expression, this) 1567 return self.prepend_ctes(expression, this)
1569 def fetch_sql(self, expression: exp.Fetch) -> str: 1570 direction = expression.args.get("direction") 1571 direction = f" {direction}" if direction else "" 1572 count = self.sql(expression, "count") 1573 count = f" {count}" if count else "" 1574 limit_options = self.sql(expression, "limit_options") 1575 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1576 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1578 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1579 percent = " PERCENT" if expression.args.get("percent") else "" 1580 rows = " ROWS" if expression.args.get("rows") else "" 1581 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1582 if not with_ties and rows: 1583 with_ties = " ONLY" 1584 return f"{percent}{rows}{with_ties}"
1586 def filter_sql(self, expression: exp.Filter) -> str: 1587 if self.AGGREGATE_FILTER_SUPPORTED: 1588 this = self.sql(expression, "this") 1589 where = self.sql(expression, "expression").strip() 1590 return f"{this} FILTER({where})" 1591 1592 agg = expression.this 1593 agg_arg = agg.this 1594 cond = expression.expression.this 1595 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1596 return self.sql(agg)
1605 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1606 using = self.sql(expression, "using") 1607 using = f" USING {using}" if using else "" 1608 columns = self.expressions(expression, key="columns", flat=True) 1609 columns = f"({columns})" if columns else "" 1610 partition_by = self.expressions(expression, key="partition_by", flat=True) 1611 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1612 where = self.sql(expression, "where") 1613 include = self.expressions(expression, key="include", flat=True) 1614 if include: 1615 include = f" INCLUDE ({include})" 1616 with_storage = self.expressions(expression, key="with_storage", flat=True) 1617 with_storage = f" WITH ({with_storage})" if with_storage else "" 1618 tablespace = self.sql(expression, "tablespace") 1619 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1620 on = self.sql(expression, "on") 1621 on = f" ON {on}" if on else "" 1622 1623 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1625 def index_sql(self, expression: exp.Index) -> str: 1626 unique = "UNIQUE " if expression.args.get("unique") else "" 1627 primary = "PRIMARY " if expression.args.get("primary") else "" 1628 amp = "AMP " if expression.args.get("amp") else "" 1629 name = self.sql(expression, "this") 1630 name = f"{name} " if name else "" 1631 table = self.sql(expression, "table") 1632 table = f"{self.INDEX_ON} {table}" if table else "" 1633 1634 index = "INDEX " if not table else "" 1635 1636 params = self.sql(expression, "params") 1637 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1639 def identifier_sql(self, expression: exp.Identifier) -> str: 1640 text = expression.name 1641 lower = text.lower() 1642 text = lower if self.normalize and not expression.quoted else text 1643 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1644 if ( 1645 expression.quoted 1646 or self.dialect.can_identify(text, self.identify) 1647 or lower in self.RESERVED_KEYWORDS 1648 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1649 ): 1650 text = f"{self._identifier_start}{text}{self._identifier_end}" 1651 return text
1666 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1667 input_format = self.sql(expression, "input_format") 1668 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1669 output_format = self.sql(expression, "output_format") 1670 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1671 return self.sep().join((input_format, output_format))
1681 def properties_sql(self, expression: exp.Properties) -> str: 1682 root_properties = [] 1683 with_properties = [] 1684 1685 for p in expression.expressions: 1686 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1687 if p_loc == exp.Properties.Location.POST_WITH: 1688 with_properties.append(p) 1689 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1690 root_properties.append(p) 1691 1692 root_props_ast = exp.Properties(expressions=root_properties) 1693 root_props_ast.parent = expression.parent 1694 1695 with_props_ast = exp.Properties(expressions=with_properties) 1696 with_props_ast.parent = expression.parent 1697 1698 root_props = self.root_properties(root_props_ast) 1699 with_props = self.with_properties(with_props_ast) 1700 1701 if root_props and with_props and not self.pretty: 1702 with_props = " " + with_props 1703 1704 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1711 def properties( 1712 self, 1713 properties: exp.Properties, 1714 prefix: str = "", 1715 sep: str = ", ", 1716 suffix: str = "", 1717 wrapped: bool = True, 1718 ) -> str: 1719 if properties.expressions: 1720 expressions = self.expressions(properties, sep=sep, indent=False) 1721 if expressions: 1722 expressions = self.wrap(expressions) if wrapped else expressions 1723 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1724 return ""
1729 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1730 properties_locs = defaultdict(list) 1731 for p in properties.expressions: 1732 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1733 if p_loc != exp.Properties.Location.UNSUPPORTED: 1734 properties_locs[p_loc].append(p) 1735 else: 1736 self.unsupported(f"Unsupported property {p.key}") 1737 1738 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1745 def property_sql(self, expression: exp.Property) -> str: 1746 property_cls = expression.__class__ 1747 if property_cls == exp.Property: 1748 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1749 1750 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1751 if not property_name: 1752 self.unsupported(f"Unsupported property {expression.key}") 1753 1754 return f"{property_name}={self.sql(expression, 'this')}"
1756 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1757 if self.SUPPORTS_CREATE_TABLE_LIKE: 1758 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1759 options = f" {options}" if options else "" 1760 1761 like = f"LIKE {self.sql(expression, 'this')}{options}" 1762 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1763 like = f"({like})" 1764 1765 return like 1766 1767 if expression.expressions: 1768 self.unsupported("Transpilation of LIKE property options is unsupported") 1769 1770 select = exp.select("*").from_(expression.this).limit(0) 1771 return f"AS {self.sql(select)}"
1778 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1779 no = "NO " if expression.args.get("no") else "" 1780 local = expression.args.get("local") 1781 local = f"{local} " if local else "" 1782 dual = "DUAL " if expression.args.get("dual") else "" 1783 before = "BEFORE " if expression.args.get("before") else "" 1784 after = "AFTER " if expression.args.get("after") else "" 1785 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1801 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1802 if expression.args.get("no"): 1803 return "NO MERGEBLOCKRATIO" 1804 if expression.args.get("default"): 1805 return "DEFAULT MERGEBLOCKRATIO" 1806 1807 percent = " PERCENT" if expression.args.get("percent") else "" 1808 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1810 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1811 default = expression.args.get("default") 1812 minimum = expression.args.get("minimum") 1813 maximum = expression.args.get("maximum") 1814 if default or minimum or maximum: 1815 if default: 1816 prop = "DEFAULT" 1817 elif minimum: 1818 prop = "MINIMUM" 1819 else: 1820 prop = "MAXIMUM" 1821 return f"{prop} DATABLOCKSIZE" 1822 units = expression.args.get("units") 1823 units = f" {units}" if units else "" 1824 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1826 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1827 autotemp = expression.args.get("autotemp") 1828 always = expression.args.get("always") 1829 default = expression.args.get("default") 1830 manual = expression.args.get("manual") 1831 never = expression.args.get("never") 1832 1833 if autotemp is not None: 1834 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1835 elif always: 1836 prop = "ALWAYS" 1837 elif default: 1838 prop = "DEFAULT" 1839 elif manual: 1840 prop = "MANUAL" 1841 elif never: 1842 prop = "NEVER" 1843 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1845 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1846 no = expression.args.get("no") 1847 no = " NO" if no else "" 1848 concurrent = expression.args.get("concurrent") 1849 concurrent = " CONCURRENT" if concurrent else "" 1850 target = self.sql(expression, "target") 1851 target = f" {target}" if target else "" 1852 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1854 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1855 if isinstance(expression.this, list): 1856 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1857 if expression.this: 1858 modulus = self.sql(expression, "this") 1859 remainder = self.sql(expression, "expression") 1860 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1861 1862 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1863 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1864 return f"FROM ({from_expressions}) TO ({to_expressions})"
1866 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1867 this = self.sql(expression, "this") 1868 1869 for_values_or_default = expression.expression 1870 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1871 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1872 else: 1873 for_values_or_default = " DEFAULT" 1874 1875 return f"PARTITION OF {this}{for_values_or_default}"
1877 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1878 kind = expression.args.get("kind") 1879 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1880 for_or_in = expression.args.get("for_or_in") 1881 for_or_in = f" {for_or_in}" if for_or_in else "" 1882 lock_type = expression.args.get("lock_type") 1883 override = " OVERRIDE" if expression.args.get("override") else "" 1884 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1886 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1887 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1888 statistics = expression.args.get("statistics") 1889 statistics_sql = "" 1890 if statistics is not None: 1891 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1892 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1894 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1895 this = self.sql(expression, "this") 1896 this = f"HISTORY_TABLE={this}" if this else "" 1897 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1898 data_consistency = ( 1899 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1900 ) 1901 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1902 retention_period = ( 1903 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1904 ) 1905 1906 if this: 1907 on_sql = self.func("ON", this, data_consistency, retention_period) 1908 else: 1909 on_sql = "ON" if expression.args.get("on") else "OFF" 1910 1911 sql = f"SYSTEM_VERSIONING={on_sql}" 1912 1913 return f"WITH({sql})" if expression.args.get("with") else sql
1915 def insert_sql(self, expression: exp.Insert) -> str: 1916 hint = self.sql(expression, "hint") 1917 overwrite = expression.args.get("overwrite") 1918 1919 if isinstance(expression.this, exp.Directory): 1920 this = " OVERWRITE" if overwrite else " INTO" 1921 else: 1922 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1923 1924 stored = self.sql(expression, "stored") 1925 stored = f" {stored}" if stored else "" 1926 alternative = expression.args.get("alternative") 1927 alternative = f" OR {alternative}" if alternative else "" 1928 ignore = " IGNORE" if expression.args.get("ignore") else "" 1929 is_function = expression.args.get("is_function") 1930 if is_function: 1931 this = f"{this} FUNCTION" 1932 this = f"{this} {self.sql(expression, 'this')}" 1933 1934 exists = " IF EXISTS" if expression.args.get("exists") else "" 1935 where = self.sql(expression, "where") 1936 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1937 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1938 on_conflict = self.sql(expression, "conflict") 1939 on_conflict = f" {on_conflict}" if on_conflict else "" 1940 by_name = " BY NAME" if expression.args.get("by_name") else "" 1941 returning = self.sql(expression, "returning") 1942 1943 if self.RETURNING_END: 1944 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1945 else: 1946 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1947 1948 partition_by = self.sql(expression, "partition") 1949 partition_by = f" {partition_by}" if partition_by else "" 1950 settings = self.sql(expression, "settings") 1951 settings = f" {settings}" if settings else "" 1952 1953 source = self.sql(expression, "source") 1954 source = f"TABLE {source}" if source else "" 1955 1956 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1957 return self.prepend_ctes(expression, sql)
1975 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1976 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1977 1978 constraint = self.sql(expression, "constraint") 1979 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1980 1981 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1982 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1983 action = self.sql(expression, "action") 1984 1985 expressions = self.expressions(expression, flat=True) 1986 if expressions: 1987 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1988 expressions = f" {set_keyword}{expressions}" 1989 1990 where = self.sql(expression, "where") 1991 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1996 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1997 fields = self.sql(expression, "fields") 1998 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1999 escaped = self.sql(expression, "escaped") 2000 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2001 items = self.sql(expression, "collection_items") 2002 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2003 keys = self.sql(expression, "map_keys") 2004 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2005 lines = self.sql(expression, "lines") 2006 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2007 null = self.sql(expression, "null") 2008 null = f" NULL DEFINED AS {null}" if null else "" 2009 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2037 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2038 table = self.table_parts(expression) 2039 only = "ONLY " if expression.args.get("only") else "" 2040 partition = self.sql(expression, "partition") 2041 partition = f" {partition}" if partition else "" 2042 version = self.sql(expression, "version") 2043 version = f" {version}" if version else "" 2044 alias = self.sql(expression, "alias") 2045 alias = f"{sep}{alias}" if alias else "" 2046 2047 sample = self.sql(expression, "sample") 2048 if self.dialect.ALIAS_POST_TABLESAMPLE: 2049 sample_pre_alias = sample 2050 sample_post_alias = "" 2051 else: 2052 sample_pre_alias = "" 2053 sample_post_alias = sample 2054 2055 hints = self.expressions(expression, key="hints", sep=" ") 2056 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2057 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2058 joins = self.indent( 2059 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2060 ) 2061 laterals = self.expressions(expression, key="laterals", sep="") 2062 2063 file_format = self.sql(expression, "format") 2064 if file_format: 2065 pattern = self.sql(expression, "pattern") 2066 pattern = f", PATTERN => {pattern}" if pattern else "" 2067 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2068 2069 ordinality = expression.args.get("ordinality") or "" 2070 if ordinality: 2071 ordinality = f" WITH ORDINALITY{alias}" 2072 alias = "" 2073 2074 when = self.sql(expression, "when") 2075 if when: 2076 table = f"{table} {when}" 2077 2078 changes = self.sql(expression, "changes") 2079 changes = f" {changes}" if changes else "" 2080 2081 rows_from = self.expressions(expression, key="rows_from") 2082 if rows_from: 2083 table = f"ROWS FROM {self.wrap(rows_from)}" 2084 2085 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2087 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2088 table = self.func("TABLE", expression.this) 2089 alias = self.sql(expression, "alias") 2090 alias = f" AS {alias}" if alias else "" 2091 sample = self.sql(expression, "sample") 2092 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2093 joins = self.indent( 2094 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2095 ) 2096 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2098 def tablesample_sql( 2099 self, 2100 expression: exp.TableSample, 2101 tablesample_keyword: t.Optional[str] = None, 2102 ) -> str: 2103 method = self.sql(expression, "method") 2104 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2105 numerator = self.sql(expression, "bucket_numerator") 2106 denominator = self.sql(expression, "bucket_denominator") 2107 field = self.sql(expression, "bucket_field") 2108 field = f" ON {field}" if field else "" 2109 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2110 seed = self.sql(expression, "seed") 2111 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2112 2113 size = self.sql(expression, "size") 2114 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2115 size = f"{size} ROWS" 2116 2117 percent = self.sql(expression, "percent") 2118 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2119 percent = f"{percent} PERCENT" 2120 2121 expr = f"{bucket}{percent}{size}" 2122 if self.TABLESAMPLE_REQUIRES_PARENS: 2123 expr = f"({expr})" 2124 2125 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2127 def pivot_sql(self, expression: exp.Pivot) -> str: 2128 expressions = self.expressions(expression, flat=True) 2129 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2130 2131 group = self.sql(expression, "group") 2132 2133 if expression.this: 2134 this = self.sql(expression, "this") 2135 if not expressions: 2136 return f"UNPIVOT {this}" 2137 2138 on = f"{self.seg('ON')} {expressions}" 2139 into = self.sql(expression, "into") 2140 into = f"{self.seg('INTO')} {into}" if into else "" 2141 using = self.expressions(expression, key="using", flat=True) 2142 using = f"{self.seg('USING')} {using}" if using else "" 2143 return f"{direction} {this}{on}{into}{using}{group}" 2144 2145 alias = self.sql(expression, "alias") 2146 alias = f" AS {alias}" if alias else "" 2147 2148 fields = self.expressions( 2149 expression, 2150 "fields", 2151 sep=" ", 2152 dynamic=True, 2153 new_line=True, 2154 skip_first=True, 2155 skip_last=True, 2156 ) 2157 2158 include_nulls = expression.args.get("include_nulls") 2159 if include_nulls is not None: 2160 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2161 else: 2162 nulls = "" 2163 2164 default_on_null = self.sql(expression, "default_on_null") 2165 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2166 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2177 def update_sql(self, expression: exp.Update) -> str: 2178 this = self.sql(expression, "this") 2179 set_sql = self.expressions(expression, flat=True) 2180 from_sql = self.sql(expression, "from") 2181 where_sql = self.sql(expression, "where") 2182 returning = self.sql(expression, "returning") 2183 order = self.sql(expression, "order") 2184 limit = self.sql(expression, "limit") 2185 if self.RETURNING_END: 2186 expression_sql = f"{from_sql}{where_sql}{returning}" 2187 else: 2188 expression_sql = f"{returning}{from_sql}{where_sql}" 2189 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2190 return self.prepend_ctes(expression, sql)
2192 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2193 values_as_table = values_as_table and self.VALUES_AS_TABLE 2194 2195 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2196 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2197 args = self.expressions(expression) 2198 alias = self.sql(expression, "alias") 2199 values = f"VALUES{self.seg('')}{args}" 2200 values = ( 2201 f"({values})" 2202 if self.WRAP_DERIVED_VALUES 2203 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2204 else values 2205 ) 2206 return f"{values} AS {alias}" if alias else values 2207 2208 # Converts `VALUES...` expression into a series of select unions. 2209 alias_node = expression.args.get("alias") 2210 column_names = alias_node and alias_node.columns 2211 2212 selects: t.List[exp.Query] = [] 2213 2214 for i, tup in enumerate(expression.expressions): 2215 row = tup.expressions 2216 2217 if i == 0 and column_names: 2218 row = [ 2219 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2220 ] 2221 2222 selects.append(exp.Select(expressions=row)) 2223 2224 if self.pretty: 2225 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2226 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2227 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2228 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2229 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2230 2231 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2232 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2233 return f"({unions}){alias}"
2238 @unsupported_args("expressions") 2239 def into_sql(self, expression: exp.Into) -> str: 2240 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2241 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2242 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2259 def group_sql(self, expression: exp.Group) -> str: 2260 group_by_all = expression.args.get("all") 2261 if group_by_all is True: 2262 modifier = " ALL" 2263 elif group_by_all is False: 2264 modifier = " DISTINCT" 2265 else: 2266 modifier = "" 2267 2268 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2269 2270 grouping_sets = self.expressions(expression, key="grouping_sets") 2271 cube = self.expressions(expression, key="cube") 2272 rollup = self.expressions(expression, key="rollup") 2273 2274 groupings = csv( 2275 self.seg(grouping_sets) if grouping_sets else "", 2276 self.seg(cube) if cube else "", 2277 self.seg(rollup) if rollup else "", 2278 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2279 sep=self.GROUPINGS_SEP, 2280 ) 2281 2282 if ( 2283 expression.expressions 2284 and groupings 2285 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2286 ): 2287 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2288 2289 return f"{group_by}{groupings}"
2295 def connect_sql(self, expression: exp.Connect) -> str: 2296 start = self.sql(expression, "start") 2297 start = self.seg(f"START WITH {start}") if start else "" 2298 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2299 connect = self.sql(expression, "connect") 2300 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2301 return start + connect
2306 def join_sql(self, expression: exp.Join) -> str: 2307 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2308 side = None 2309 else: 2310 side = expression.side 2311 2312 op_sql = " ".join( 2313 op 2314 for op in ( 2315 expression.method, 2316 "GLOBAL" if expression.args.get("global") else None, 2317 side, 2318 expression.kind, 2319 expression.hint if self.JOIN_HINTS else None, 2320 ) 2321 if op 2322 ) 2323 match_cond = self.sql(expression, "match_condition") 2324 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2325 on_sql = self.sql(expression, "on") 2326 using = expression.args.get("using") 2327 2328 if not on_sql and using: 2329 on_sql = csv(*(self.sql(column) for column in using)) 2330 2331 this = expression.this 2332 this_sql = self.sql(this) 2333 2334 exprs = self.expressions(expression) 2335 if exprs: 2336 this_sql = f"{this_sql},{self.seg(exprs)}" 2337 2338 if on_sql: 2339 on_sql = self.indent(on_sql, skip_first=True) 2340 space = self.seg(" " * self.pad) if self.pretty else " " 2341 if using: 2342 on_sql = f"{space}USING ({on_sql})" 2343 else: 2344 on_sql = f"{space}ON {on_sql}" 2345 elif not op_sql: 2346 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2347 return f" {this_sql}" 2348 2349 return f", {this_sql}" 2350 2351 if op_sql != "STRAIGHT_JOIN": 2352 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2353 2354 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2355 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:
2362 def lateral_op(self, expression: exp.Lateral) -> str: 2363 cross_apply = expression.args.get("cross_apply") 2364 2365 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2366 if cross_apply is True: 2367 op = "INNER JOIN " 2368 elif cross_apply is False: 2369 op = "LEFT JOIN " 2370 else: 2371 op = "" 2372 2373 return f"{op}LATERAL"
2375 def lateral_sql(self, expression: exp.Lateral) -> str: 2376 this = self.sql(expression, "this") 2377 2378 if expression.args.get("view"): 2379 alias = expression.args["alias"] 2380 columns = self.expressions(alias, key="columns", flat=True) 2381 table = f" {alias.name}" if alias.name else "" 2382 columns = f" AS {columns}" if columns else "" 2383 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2384 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2385 2386 alias = self.sql(expression, "alias") 2387 alias = f" AS {alias}" if alias else "" 2388 2389 ordinality = expression.args.get("ordinality") or "" 2390 if ordinality: 2391 ordinality = f" WITH ORDINALITY{alias}" 2392 alias = "" 2393 2394 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2396 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2397 this = self.sql(expression, "this") 2398 2399 args = [ 2400 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2401 for e in (expression.args.get(k) for k in ("offset", "expression")) 2402 if e 2403 ] 2404 2405 args_sql = ", ".join(self.sql(e) for e in args) 2406 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2407 expressions = self.expressions(expression, flat=True) 2408 limit_options = self.sql(expression, "limit_options") 2409 expressions = f" BY {expressions}" if expressions else "" 2410 2411 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2413 def offset_sql(self, expression: exp.Offset) -> str: 2414 this = self.sql(expression, "this") 2415 value = expression.expression 2416 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2417 expressions = self.expressions(expression, flat=True) 2418 expressions = f" BY {expressions}" if expressions else "" 2419 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2421 def setitem_sql(self, expression: exp.SetItem) -> str: 2422 kind = self.sql(expression, "kind") 2423 kind = f"{kind} " if kind else "" 2424 this = self.sql(expression, "this") 2425 expressions = self.expressions(expression) 2426 collate = self.sql(expression, "collate") 2427 collate = f" COLLATE {collate}" if collate else "" 2428 global_ = "GLOBAL " if expression.args.get("global") else "" 2429 return f"{global_}{kind}{this}{expressions}{collate}"
2436 def queryband_sql(self, expression: exp.QueryBand) -> str: 2437 this = self.sql(expression, "this") 2438 update = " UPDATE" if expression.args.get("update") else "" 2439 scope = self.sql(expression, "scope") 2440 scope = f" FOR {scope}" if scope else "" 2441 2442 return f"QUERY_BAND = {this}{update}{scope}"
2447 def lock_sql(self, expression: exp.Lock) -> str: 2448 if not self.LOCKING_READS_SUPPORTED: 2449 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2450 return "" 2451 2452 update = expression.args["update"] 2453 key = expression.args.get("key") 2454 if update: 2455 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2456 else: 2457 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2458 expressions = self.expressions(expression, flat=True) 2459 expressions = f" OF {expressions}" if expressions else "" 2460 wait = expression.args.get("wait") 2461 2462 if wait is not None: 2463 if isinstance(wait, exp.Literal): 2464 wait = f" WAIT {self.sql(wait)}" 2465 else: 2466 wait = " NOWAIT" if wait else " SKIP LOCKED" 2467 2468 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2476 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2477 if self.dialect.ESCAPED_SEQUENCES: 2478 to_escaped = self.dialect.ESCAPED_SEQUENCES 2479 text = "".join( 2480 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2481 ) 2482 2483 return self._replace_line_breaks(text).replace( 2484 self.dialect.QUOTE_END, self._escaped_quote_end 2485 )
2487 def loaddata_sql(self, expression: exp.LoadData) -> str: 2488 local = " LOCAL" if expression.args.get("local") else "" 2489 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2490 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2491 this = f" INTO TABLE {self.sql(expression, 'this')}" 2492 partition = self.sql(expression, "partition") 2493 partition = f" {partition}" if partition else "" 2494 input_format = self.sql(expression, "input_format") 2495 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2496 serde = self.sql(expression, "serde") 2497 serde = f" SERDE {serde}" if serde else "" 2498 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2506 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2507 this = self.sql(expression, "this") 2508 this = f"{this} " if this else this 2509 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2510 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2512 def withfill_sql(self, expression: exp.WithFill) -> str: 2513 from_sql = self.sql(expression, "from") 2514 from_sql = f" FROM {from_sql}" if from_sql else "" 2515 to_sql = self.sql(expression, "to") 2516 to_sql = f" TO {to_sql}" if to_sql else "" 2517 step_sql = self.sql(expression, "step") 2518 step_sql = f" STEP {step_sql}" if step_sql else "" 2519 interpolated_values = [ 2520 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2521 if isinstance(e, exp.Alias) 2522 else self.sql(e, "this") 2523 for e in expression.args.get("interpolate") or [] 2524 ] 2525 interpolate = ( 2526 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2527 ) 2528 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2539 def ordered_sql(self, expression: exp.Ordered) -> str: 2540 desc = expression.args.get("desc") 2541 asc = not desc 2542 2543 nulls_first = expression.args.get("nulls_first") 2544 nulls_last = not nulls_first 2545 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2546 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2547 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2548 2549 this = self.sql(expression, "this") 2550 2551 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2552 nulls_sort_change = "" 2553 if nulls_first and ( 2554 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2555 ): 2556 nulls_sort_change = " NULLS FIRST" 2557 elif ( 2558 nulls_last 2559 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2560 and not nulls_are_last 2561 ): 2562 nulls_sort_change = " NULLS LAST" 2563 2564 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2565 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2566 window = expression.find_ancestor(exp.Window, exp.Select) 2567 if isinstance(window, exp.Window) and window.args.get("spec"): 2568 self.unsupported( 2569 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2570 ) 2571 nulls_sort_change = "" 2572 elif self.NULL_ORDERING_SUPPORTED is False and ( 2573 (asc and nulls_sort_change == " NULLS LAST") 2574 or (desc and nulls_sort_change == " NULLS FIRST") 2575 ): 2576 # BigQuery does not allow these ordering/nulls combinations when used under 2577 # an aggregation func or under a window containing one 2578 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2579 2580 if isinstance(ancestor, exp.Window): 2581 ancestor = ancestor.this 2582 if isinstance(ancestor, exp.AggFunc): 2583 self.unsupported( 2584 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2585 ) 2586 nulls_sort_change = "" 2587 elif self.NULL_ORDERING_SUPPORTED is None: 2588 if expression.this.is_int: 2589 self.unsupported( 2590 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2591 ) 2592 elif not isinstance(expression.this, exp.Rand): 2593 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2594 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2595 nulls_sort_change = "" 2596 2597 with_fill = self.sql(expression, "with_fill") 2598 with_fill = f" {with_fill}" if with_fill else "" 2599 2600 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2610 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2611 partition = self.partition_by_sql(expression) 2612 order = self.sql(expression, "order") 2613 measures = self.expressions(expression, key="measures") 2614 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2615 rows = self.sql(expression, "rows") 2616 rows = self.seg(rows) if rows else "" 2617 after = self.sql(expression, "after") 2618 after = self.seg(after) if after else "" 2619 pattern = self.sql(expression, "pattern") 2620 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2621 definition_sqls = [ 2622 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2623 for definition in expression.args.get("define", []) 2624 ] 2625 definitions = self.expressions(sqls=definition_sqls) 2626 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2627 body = "".join( 2628 ( 2629 partition, 2630 order, 2631 measures, 2632 rows, 2633 after, 2634 pattern, 2635 define, 2636 ) 2637 ) 2638 alias = self.sql(expression, "alias") 2639 alias = f" {alias}" if alias else "" 2640 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2642 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2643 limit = expression.args.get("limit") 2644 2645 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2646 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2647 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2648 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2649 2650 return csv( 2651 *sqls, 2652 *[self.sql(join) for join in expression.args.get("joins") or []], 2653 self.sql(expression, "match"), 2654 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2655 self.sql(expression, "prewhere"), 2656 self.sql(expression, "where"), 2657 self.sql(expression, "connect"), 2658 self.sql(expression, "group"), 2659 self.sql(expression, "having"), 2660 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2661 self.sql(expression, "order"), 2662 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2663 *self.after_limit_modifiers(expression), 2664 self.options_modifier(expression), 2665 self.for_modifiers(expression), 2666 sep="", 2667 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2681 def offset_limit_modifiers( 2682 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2683 ) -> t.List[str]: 2684 return [ 2685 self.sql(expression, "offset") if fetch else self.sql(limit), 2686 self.sql(limit) if fetch else self.sql(expression, "offset"), 2687 ]
2694 def select_sql(self, expression: exp.Select) -> str: 2695 into = expression.args.get("into") 2696 if not self.SUPPORTS_SELECT_INTO and into: 2697 into.pop() 2698 2699 hint = self.sql(expression, "hint") 2700 distinct = self.sql(expression, "distinct") 2701 distinct = f" {distinct}" if distinct else "" 2702 kind = self.sql(expression, "kind") 2703 2704 limit = expression.args.get("limit") 2705 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2706 top = self.limit_sql(limit, top=True) 2707 limit.pop() 2708 else: 2709 top = "" 2710 2711 expressions = self.expressions(expression) 2712 2713 if kind: 2714 if kind in self.SELECT_KINDS: 2715 kind = f" AS {kind}" 2716 else: 2717 if kind == "STRUCT": 2718 expressions = self.expressions( 2719 sqls=[ 2720 self.sql( 2721 exp.Struct( 2722 expressions=[ 2723 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2724 if isinstance(e, exp.Alias) 2725 else e 2726 for e in expression.expressions 2727 ] 2728 ) 2729 ) 2730 ] 2731 ) 2732 kind = "" 2733 2734 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2735 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2736 2737 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2738 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2739 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2740 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2741 sql = self.query_modifiers( 2742 expression, 2743 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2744 self.sql(expression, "into", comment=False), 2745 self.sql(expression, "from", comment=False), 2746 ) 2747 2748 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2749 if expression.args.get("with"): 2750 sql = self.maybe_comment(sql, expression) 2751 expression.pop_comments() 2752 2753 sql = self.prepend_ctes(expression, sql) 2754 2755 if not self.SUPPORTS_SELECT_INTO and into: 2756 if into.args.get("temporary"): 2757 table_kind = " TEMPORARY" 2758 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2759 table_kind = " UNLOGGED" 2760 else: 2761 table_kind = "" 2762 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2763 2764 return sql
2776 def star_sql(self, expression: exp.Star) -> str: 2777 except_ = self.expressions(expression, key="except", flat=True) 2778 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2779 replace = self.expressions(expression, key="replace", flat=True) 2780 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2781 rename = self.expressions(expression, key="rename", flat=True) 2782 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2783 return f"*{except_}{replace}{rename}"
2799 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2800 alias = self.sql(expression, "alias") 2801 alias = f"{sep}{alias}" if alias else "" 2802 sample = self.sql(expression, "sample") 2803 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2804 alias = f"{sample}{alias}" 2805 2806 # Set to None so it's not generated again by self.query_modifiers() 2807 expression.set("sample", None) 2808 2809 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2810 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2811 return self.prepend_ctes(expression, sql)
2817 def unnest_sql(self, expression: exp.Unnest) -> str: 2818 args = self.expressions(expression, flat=True) 2819 2820 alias = expression.args.get("alias") 2821 offset = expression.args.get("offset") 2822 2823 if self.UNNEST_WITH_ORDINALITY: 2824 if alias and isinstance(offset, exp.Expression): 2825 alias.append("columns", offset) 2826 2827 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2828 columns = alias.columns 2829 alias = self.sql(columns[0]) if columns else "" 2830 else: 2831 alias = self.sql(alias) 2832 2833 alias = f" AS {alias}" if alias else alias 2834 if self.UNNEST_WITH_ORDINALITY: 2835 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2836 else: 2837 if isinstance(offset, exp.Expression): 2838 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2839 elif offset: 2840 suffix = f"{alias} WITH OFFSET" 2841 else: 2842 suffix = alias 2843 2844 return f"UNNEST({args}){suffix}"
2853 def window_sql(self, expression: exp.Window) -> str: 2854 this = self.sql(expression, "this") 2855 partition = self.partition_by_sql(expression) 2856 order = expression.args.get("order") 2857 order = self.order_sql(order, flat=True) if order else "" 2858 spec = self.sql(expression, "spec") 2859 alias = self.sql(expression, "alias") 2860 over = self.sql(expression, "over") or "OVER" 2861 2862 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2863 2864 first = expression.args.get("first") 2865 if first is None: 2866 first = "" 2867 else: 2868 first = "FIRST" if first else "LAST" 2869 2870 if not partition and not order and not spec and alias: 2871 return f"{this} {alias}" 2872 2873 args = self.format_args( 2874 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2875 ) 2876 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2882 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2883 kind = self.sql(expression, "kind") 2884 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2885 end = ( 2886 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2887 or "CURRENT ROW" 2888 ) 2889 2890 window_spec = f"{kind} BETWEEN {start} AND {end}" 2891 2892 exclude = self.sql(expression, "exclude") 2893 if exclude: 2894 if self.SUPPORTS_WINDOW_EXCLUDE: 2895 window_spec += f" EXCLUDE {exclude}" 2896 else: 2897 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2898 2899 return window_spec
2906 def between_sql(self, expression: exp.Between) -> str: 2907 this = self.sql(expression, "this") 2908 low = self.sql(expression, "low") 2909 high = self.sql(expression, "high") 2910 symmetric = expression.args.get("symmetric") 2911 2912 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2913 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2914 2915 flag = ( 2916 " SYMMETRIC" 2917 if symmetric 2918 else " ASYMMETRIC" 2919 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2920 else "" # silently drop ASYMMETRIC – semantics identical 2921 ) 2922 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]:
2924 def bracket_offset_expressions( 2925 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2926 ) -> t.List[exp.Expression]: 2927 return apply_index_offset( 2928 expression.this, 2929 expression.expressions, 2930 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2931 dialect=self.dialect, 2932 )
2945 def any_sql(self, expression: exp.Any) -> str: 2946 this = self.sql(expression, "this") 2947 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2948 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2949 this = self.wrap(this) 2950 return f"ANY{this}" 2951 return f"ANY {this}"
2956 def case_sql(self, expression: exp.Case) -> str: 2957 this = self.sql(expression, "this") 2958 statements = [f"CASE {this}" if this else "CASE"] 2959 2960 for e in expression.args["ifs"]: 2961 statements.append(f"WHEN {self.sql(e, 'this')}") 2962 statements.append(f"THEN {self.sql(e, 'true')}") 2963 2964 default = self.sql(expression, "default") 2965 2966 if default: 2967 statements.append(f"ELSE {default}") 2968 2969 statements.append("END") 2970 2971 if self.pretty and self.too_wide(statements): 2972 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2973 2974 return " ".join(statements)
2986 def extract_sql(self, expression: exp.Extract) -> str: 2987 from sqlglot.dialects.dialect import map_date_part 2988 2989 this = ( 2990 map_date_part(expression.this, self.dialect) 2991 if self.NORMALIZE_EXTRACT_DATE_PARTS 2992 else expression.this 2993 ) 2994 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2995 expression_sql = self.sql(expression, "expression") 2996 2997 return f"EXTRACT({this_sql} FROM {expression_sql})"
2999 def trim_sql(self, expression: exp.Trim) -> str: 3000 trim_type = self.sql(expression, "position") 3001 3002 if trim_type == "LEADING": 3003 func_name = "LTRIM" 3004 elif trim_type == "TRAILING": 3005 func_name = "RTRIM" 3006 else: 3007 func_name = "TRIM" 3008 3009 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]:
3011 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3012 args = expression.expressions 3013 if isinstance(expression, exp.ConcatWs): 3014 args = args[1:] # Skip the delimiter 3015 3016 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3017 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3018 3019 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3020 3021 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3022 if not e.type: 3023 from sqlglot.optimizer.annotate_types import annotate_types 3024 3025 e = annotate_types(e, dialect=self.dialect) 3026 3027 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3028 return e 3029 3030 return exp.func("coalesce", e, exp.Literal.string("")) 3031 3032 args = [_wrap_with_coalesce(e) for e in args] 3033 3034 return args
3036 def concat_sql(self, expression: exp.Concat) -> str: 3037 expressions = self.convert_concat_args(expression) 3038 3039 # Some dialects don't allow a single-argument CONCAT call 3040 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3041 return self.sql(expressions[0]) 3042 3043 return self.func("CONCAT", *expressions)
3054 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3055 expressions = self.expressions(expression, flat=True) 3056 expressions = f" ({expressions})" if expressions else "" 3057 reference = self.sql(expression, "reference") 3058 reference = f" {reference}" if reference else "" 3059 delete = self.sql(expression, "delete") 3060 delete = f" ON DELETE {delete}" if delete else "" 3061 update = self.sql(expression, "update") 3062 update = f" ON UPDATE {update}" if update else "" 3063 options = self.expressions(expression, key="options", flat=True, sep=" ") 3064 options = f" {options}" if options else "" 3065 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3067 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3068 expressions = self.expressions(expression, flat=True) 3069 include = self.sql(expression, "include") 3070 options = self.expressions(expression, key="options", flat=True, sep=" ") 3071 options = f" {options}" if options else "" 3072 return f"PRIMARY KEY ({expressions}){include}{options}"
3077 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3078 if self.MATCH_AGAINST_TABLE_PREFIX: 3079 expressions = [] 3080 for expr in expression.expressions: 3081 if isinstance(expr, exp.Table): 3082 expressions.append(f"TABLE {self.sql(expr)}") 3083 else: 3084 expressions.append(expr) 3085 else: 3086 expressions = expression.expressions 3087 3088 modifier = expression.args.get("modifier") 3089 modifier = f" {modifier}" if modifier else "" 3090 return ( 3091 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3092 )
3097 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3098 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3099 3100 if expression.args.get("escape"): 3101 path = self.escape_str(path) 3102 3103 if self.QUOTE_JSON_PATH: 3104 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3105 3106 return path
3108 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3109 if isinstance(expression, exp.JSONPathPart): 3110 transform = self.TRANSFORMS.get(expression.__class__) 3111 if not callable(transform): 3112 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3113 return "" 3114 3115 return transform(self, expression) 3116 3117 if isinstance(expression, int): 3118 return str(expression) 3119 3120 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3121 escaped = expression.replace("'", "\\'") 3122 escaped = f"\\'{expression}\\'" 3123 else: 3124 escaped = expression.replace('"', '\\"') 3125 escaped = f'"{escaped}"' 3126 3127 return escaped
3132 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3133 # Output the Teradata column FORMAT override. 3134 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3135 this = self.sql(expression, "this") 3136 fmt = self.sql(expression, "format") 3137 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3139 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3140 null_handling = expression.args.get("null_handling") 3141 null_handling = f" {null_handling}" if null_handling else "" 3142 3143 unique_keys = expression.args.get("unique_keys") 3144 if unique_keys is not None: 3145 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3146 else: 3147 unique_keys = "" 3148 3149 return_type = self.sql(expression, "return_type") 3150 return_type = f" RETURNING {return_type}" if return_type else "" 3151 encoding = self.sql(expression, "encoding") 3152 encoding = f" ENCODING {encoding}" if encoding else "" 3153 3154 return self.func( 3155 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3156 *expression.expressions, 3157 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3158 )
3163 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3164 null_handling = expression.args.get("null_handling") 3165 null_handling = f" {null_handling}" if null_handling else "" 3166 return_type = self.sql(expression, "return_type") 3167 return_type = f" RETURNING {return_type}" if return_type else "" 3168 strict = " STRICT" if expression.args.get("strict") else "" 3169 return self.func( 3170 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3171 )
3173 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3174 this = self.sql(expression, "this") 3175 order = self.sql(expression, "order") 3176 null_handling = expression.args.get("null_handling") 3177 null_handling = f" {null_handling}" if null_handling else "" 3178 return_type = self.sql(expression, "return_type") 3179 return_type = f" RETURNING {return_type}" if return_type else "" 3180 strict = " STRICT" if expression.args.get("strict") else "" 3181 return self.func( 3182 "JSON_ARRAYAGG", 3183 this, 3184 suffix=f"{order}{null_handling}{return_type}{strict})", 3185 )
3187 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3188 path = self.sql(expression, "path") 3189 path = f" PATH {path}" if path else "" 3190 nested_schema = self.sql(expression, "nested_schema") 3191 3192 if nested_schema: 3193 return f"NESTED{path} {nested_schema}" 3194 3195 this = self.sql(expression, "this") 3196 kind = self.sql(expression, "kind") 3197 kind = f" {kind}" if kind else "" 3198 return f"{this}{kind}{path}"
3203 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3204 this = self.sql(expression, "this") 3205 path = self.sql(expression, "path") 3206 path = f", {path}" if path else "" 3207 error_handling = expression.args.get("error_handling") 3208 error_handling = f" {error_handling}" if error_handling else "" 3209 empty_handling = expression.args.get("empty_handling") 3210 empty_handling = f" {empty_handling}" if empty_handling else "" 3211 schema = self.sql(expression, "schema") 3212 return self.func( 3213 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3214 )
3216 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3217 this = self.sql(expression, "this") 3218 kind = self.sql(expression, "kind") 3219 path = self.sql(expression, "path") 3220 path = f" {path}" if path else "" 3221 as_json = " AS JSON" if expression.args.get("as_json") else "" 3222 return f"{this} {kind}{path}{as_json}"
3224 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3225 this = self.sql(expression, "this") 3226 path = self.sql(expression, "path") 3227 path = f", {path}" if path else "" 3228 expressions = self.expressions(expression) 3229 with_ = ( 3230 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3231 if expressions 3232 else "" 3233 ) 3234 return f"OPENJSON({this}{path}){with_}"
3236 def in_sql(self, expression: exp.In) -> str: 3237 query = expression.args.get("query") 3238 unnest = expression.args.get("unnest") 3239 field = expression.args.get("field") 3240 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3241 3242 if query: 3243 in_sql = self.sql(query) 3244 elif unnest: 3245 in_sql = self.in_unnest_op(unnest) 3246 elif field: 3247 in_sql = self.sql(field) 3248 else: 3249 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3250 3251 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3256 def interval_sql(self, expression: exp.Interval) -> str: 3257 unit = self.sql(expression, "unit") 3258 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3259 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3260 unit = f" {unit}" if unit else "" 3261 3262 if self.SINGLE_STRING_INTERVAL: 3263 this = expression.this.name if expression.this else "" 3264 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3265 3266 this = self.sql(expression, "this") 3267 if this: 3268 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3269 this = f" {this}" if unwrapped else f" ({this})" 3270 3271 return f"INTERVAL{this}{unit}"
3276 def reference_sql(self, expression: exp.Reference) -> str: 3277 this = self.sql(expression, "this") 3278 expressions = self.expressions(expression, flat=True) 3279 expressions = f"({expressions})" if expressions else "" 3280 options = self.expressions(expression, key="options", flat=True, sep=" ") 3281 options = f" {options}" if options else "" 3282 return f"REFERENCES {this}{expressions}{options}"
3284 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3285 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3286 parent = expression.parent 3287 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3288 return self.func( 3289 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3290 )
3310 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3311 alias = expression.args["alias"] 3312 3313 parent = expression.parent 3314 pivot = parent and parent.parent 3315 3316 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3317 identifier_alias = isinstance(alias, exp.Identifier) 3318 literal_alias = isinstance(alias, exp.Literal) 3319 3320 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3321 alias.replace(exp.Literal.string(alias.output_name)) 3322 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3323 alias.replace(exp.to_identifier(alias.output_name)) 3324 3325 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:
3363 def connector_sql( 3364 self, 3365 expression: exp.Connector, 3366 op: str, 3367 stack: t.Optional[t.List[str | exp.Expression]] = None, 3368 ) -> str: 3369 if stack is not None: 3370 if expression.expressions: 3371 stack.append(self.expressions(expression, sep=f" {op} ")) 3372 else: 3373 stack.append(expression.right) 3374 if expression.comments and self.comments: 3375 for comment in expression.comments: 3376 if comment: 3377 op += f" /*{self.sanitize_comment(comment)}*/" 3378 stack.extend((op, expression.left)) 3379 return op 3380 3381 stack = [expression] 3382 sqls: t.List[str] = [] 3383 ops = set() 3384 3385 while stack: 3386 node = stack.pop() 3387 if isinstance(node, exp.Connector): 3388 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3389 else: 3390 sql = self.sql(node) 3391 if sqls and sqls[-1] in ops: 3392 sqls[-1] += f" {sql}" 3393 else: 3394 sqls.append(sql) 3395 3396 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3397 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3417 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3418 format_sql = self.sql(expression, "format") 3419 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3420 to_sql = self.sql(expression, "to") 3421 to_sql = f" {to_sql}" if to_sql else "" 3422 action = self.sql(expression, "action") 3423 action = f" {action}" if action else "" 3424 default = self.sql(expression, "default") 3425 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3426 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3440 def comment_sql(self, expression: exp.Comment) -> str: 3441 this = self.sql(expression, "this") 3442 kind = expression.args["kind"] 3443 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3444 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3445 expression_sql = self.sql(expression, "expression") 3446 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3448 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3449 this = self.sql(expression, "this") 3450 delete = " DELETE" if expression.args.get("delete") else "" 3451 recompress = self.sql(expression, "recompress") 3452 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3453 to_disk = self.sql(expression, "to_disk") 3454 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3455 to_volume = self.sql(expression, "to_volume") 3456 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3457 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3459 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3460 where = self.sql(expression, "where") 3461 group = self.sql(expression, "group") 3462 aggregates = self.expressions(expression, key="aggregates") 3463 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3464 3465 if not (where or group or aggregates) and len(expression.expressions) == 1: 3466 return f"TTL {self.expressions(expression, flat=True)}" 3467 3468 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3487 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3488 this = self.sql(expression, "this") 3489 3490 dtype = self.sql(expression, "dtype") 3491 if dtype: 3492 collate = self.sql(expression, "collate") 3493 collate = f" COLLATE {collate}" if collate else "" 3494 using = self.sql(expression, "using") 3495 using = f" USING {using}" if using else "" 3496 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3497 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3498 3499 default = self.sql(expression, "default") 3500 if default: 3501 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3502 3503 comment = self.sql(expression, "comment") 3504 if comment: 3505 return f"ALTER COLUMN {this} COMMENT {comment}" 3506 3507 visible = expression.args.get("visible") 3508 if visible: 3509 return f"ALTER COLUMN {this} SET {visible}" 3510 3511 allow_null = expression.args.get("allow_null") 3512 drop = expression.args.get("drop") 3513 3514 if not drop and not allow_null: 3515 self.unsupported("Unsupported ALTER COLUMN syntax") 3516 3517 if allow_null is not None: 3518 keyword = "DROP" if drop else "SET" 3519 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3520 3521 return f"ALTER COLUMN {this} DROP DEFAULT"
3537 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3538 compound = " COMPOUND" if expression.args.get("compound") else "" 3539 this = self.sql(expression, "this") 3540 expressions = self.expressions(expression, flat=True) 3541 expressions = f"({expressions})" if expressions else "" 3542 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3544 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3545 if not self.RENAME_TABLE_WITH_DB: 3546 # Remove db from tables 3547 expression = expression.transform( 3548 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3549 ).assert_is(exp.AlterRename) 3550 this = self.sql(expression, "this") 3551 to_kw = " TO" if include_to else "" 3552 return f"RENAME{to_kw} {this}"
3567 def alter_sql(self, expression: exp.Alter) -> str: 3568 actions = expression.args["actions"] 3569 3570 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3571 actions[0], exp.ColumnDef 3572 ): 3573 actions_sql = self.expressions(expression, key="actions", flat=True) 3574 actions_sql = f"ADD {actions_sql}" 3575 else: 3576 actions_list = [] 3577 for action in actions: 3578 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3579 action_sql = self.add_column_sql(action) 3580 else: 3581 action_sql = self.sql(action) 3582 if isinstance(action, exp.Query): 3583 action_sql = f"AS {action_sql}" 3584 3585 actions_list.append(action_sql) 3586 3587 actions_sql = self.format_args(*actions_list).lstrip("\n") 3588 3589 exists = " IF EXISTS" if expression.args.get("exists") else "" 3590 on_cluster = self.sql(expression, "cluster") 3591 on_cluster = f" {on_cluster}" if on_cluster else "" 3592 only = " ONLY" if expression.args.get("only") else "" 3593 options = self.expressions(expression, key="options") 3594 options = f", {options}" if options else "" 3595 kind = self.sql(expression, "kind") 3596 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3597 check = " WITH CHECK" if expression.args.get("check") else "" 3598 this = self.sql(expression, "this") 3599 this = f" {this}" if this else "" 3600 3601 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}"
3608 def add_column_sql(self, expression: exp.Expression) -> str: 3609 sql = self.sql(expression) 3610 if isinstance(expression, exp.Schema): 3611 column_text = " COLUMNS" 3612 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3613 column_text = " COLUMN" 3614 else: 3615 column_text = "" 3616 3617 return f"ADD{column_text} {sql}"
3627 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3628 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3629 location = self.sql(expression, "location") 3630 location = f" {location}" if location else "" 3631 return f"ADD {exists}{self.sql(expression.this)}{location}"
3633 def distinct_sql(self, expression: exp.Distinct) -> str: 3634 this = self.expressions(expression, flat=True) 3635 3636 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3637 case = exp.case() 3638 for arg in expression.expressions: 3639 case = case.when(arg.is_(exp.null()), exp.null()) 3640 this = self.sql(case.else_(f"({this})")) 3641 3642 this = f" {this}" if this else "" 3643 3644 on = self.sql(expression, "on") 3645 on = f" ON {on}" if on else "" 3646 return f"DISTINCT{this}{on}"
3675 def div_sql(self, expression: exp.Div) -> str: 3676 l, r = expression.left, expression.right 3677 3678 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3679 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3680 3681 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3682 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3683 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3684 3685 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3686 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3687 return self.sql( 3688 exp.cast( 3689 l / r, 3690 to=exp.DataType.Type.BIGINT, 3691 ) 3692 ) 3693 3694 return self.binary(expression, "/")
3811 def log_sql(self, expression: exp.Log) -> str: 3812 this = expression.this 3813 expr = expression.expression 3814 3815 if self.dialect.LOG_BASE_FIRST is False: 3816 this, expr = expr, this 3817 elif self.dialect.LOG_BASE_FIRST is None and expr: 3818 if this.name in ("2", "10"): 3819 return self.func(f"LOG{this.name}", expr) 3820 3821 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3822 3823 return self.func("LOG", this, expr)
3832 def binary(self, expression: exp.Binary, op: str) -> str: 3833 sqls: t.List[str] = [] 3834 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3835 binary_type = type(expression) 3836 3837 while stack: 3838 node = stack.pop() 3839 3840 if type(node) is binary_type: 3841 op_func = node.args.get("operator") 3842 if op_func: 3843 op = f"OPERATOR({self.sql(op_func)})" 3844 3845 stack.append(node.right) 3846 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3847 stack.append(node.left) 3848 else: 3849 sqls.append(self.sql(node)) 3850 3851 return "".join(sqls)
3860 def function_fallback_sql(self, expression: exp.Func) -> str: 3861 args = [] 3862 3863 for key in expression.arg_types: 3864 arg_value = expression.args.get(key) 3865 3866 if isinstance(arg_value, list): 3867 for value in arg_value: 3868 args.append(value) 3869 elif arg_value is not None: 3870 args.append(arg_value) 3871 3872 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3873 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3874 else: 3875 name = expression.sql_name() 3876 3877 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:
3879 def func( 3880 self, 3881 name: str, 3882 *args: t.Optional[exp.Expression | str], 3883 prefix: str = "(", 3884 suffix: str = ")", 3885 normalize: bool = True, 3886 ) -> str: 3887 name = self.normalize_func(name) if normalize else name 3888 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3890 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3891 arg_sqls = tuple( 3892 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3893 ) 3894 if self.pretty and self.too_wide(arg_sqls): 3895 return self.indent( 3896 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3897 ) 3898 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]:
3903 def format_time( 3904 self, 3905 expression: exp.Expression, 3906 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3907 inverse_time_trie: t.Optional[t.Dict] = None, 3908 ) -> t.Optional[str]: 3909 return format_time( 3910 self.sql(expression, "format"), 3911 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3912 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3913 )
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:
3915 def expressions( 3916 self, 3917 expression: t.Optional[exp.Expression] = None, 3918 key: t.Optional[str] = None, 3919 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3920 flat: bool = False, 3921 indent: bool = True, 3922 skip_first: bool = False, 3923 skip_last: bool = False, 3924 sep: str = ", ", 3925 prefix: str = "", 3926 dynamic: bool = False, 3927 new_line: bool = False, 3928 ) -> str: 3929 expressions = expression.args.get(key or "expressions") if expression else sqls 3930 3931 if not expressions: 3932 return "" 3933 3934 if flat: 3935 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3936 3937 num_sqls = len(expressions) 3938 result_sqls = [] 3939 3940 for i, e in enumerate(expressions): 3941 sql = self.sql(e, comment=False) 3942 if not sql: 3943 continue 3944 3945 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3946 3947 if self.pretty: 3948 if self.leading_comma: 3949 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3950 else: 3951 result_sqls.append( 3952 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3953 ) 3954 else: 3955 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3956 3957 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3958 if new_line: 3959 result_sqls.insert(0, "") 3960 result_sqls.append("") 3961 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3962 else: 3963 result_sql = "".join(result_sqls) 3964 3965 return ( 3966 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3967 if indent 3968 else result_sql 3969 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3971 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3972 flat = flat or isinstance(expression.parent, exp.Properties) 3973 expressions_sql = self.expressions(expression, flat=flat) 3974 if flat: 3975 return f"{op} {expressions_sql}" 3976 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3978 def naked_property(self, expression: exp.Property) -> str: 3979 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3980 if not property_name: 3981 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3982 return f"{property_name} {self.sql(expression, 'this')}"
3990 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3991 this = self.sql(expression, "this") 3992 expressions = self.no_identify(self.expressions, expression) 3993 expressions = ( 3994 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3995 ) 3996 return f"{this}{expressions}" if expressions.strip() != "" else this
4006 def when_sql(self, expression: exp.When) -> str: 4007 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4008 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4009 condition = self.sql(expression, "condition") 4010 condition = f" AND {condition}" if condition else "" 4011 4012 then_expression = expression.args.get("then") 4013 if isinstance(then_expression, exp.Insert): 4014 this = self.sql(then_expression, "this") 4015 this = f"INSERT {this}" if this else "INSERT" 4016 then = self.sql(then_expression, "expression") 4017 then = f"{this} VALUES {then}" if then else this 4018 elif isinstance(then_expression, exp.Update): 4019 if isinstance(then_expression.args.get("expressions"), exp.Star): 4020 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4021 else: 4022 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4023 else: 4024 then = self.sql(then_expression) 4025 return f"WHEN {matched}{source}{condition} THEN {then}"
4030 def merge_sql(self, expression: exp.Merge) -> str: 4031 table = expression.this 4032 table_alias = "" 4033 4034 hints = table.args.get("hints") 4035 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4036 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4037 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4038 4039 this = self.sql(table) 4040 using = f"USING {self.sql(expression, 'using')}" 4041 on = f"ON {self.sql(expression, 'on')}" 4042 whens = self.sql(expression, "whens") 4043 4044 returning = self.sql(expression, "returning") 4045 if returning: 4046 whens = f"{whens}{returning}" 4047 4048 sep = self.sep() 4049 4050 return self.prepend_ctes( 4051 expression, 4052 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4053 )
4059 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4060 if not self.SUPPORTS_TO_NUMBER: 4061 self.unsupported("Unsupported TO_NUMBER function") 4062 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4063 4064 fmt = expression.args.get("format") 4065 if not fmt: 4066 self.unsupported("Conversion format is required for TO_NUMBER") 4067 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4068 4069 return self.func("TO_NUMBER", expression.this, fmt)
4071 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4072 this = self.sql(expression, "this") 4073 kind = self.sql(expression, "kind") 4074 settings_sql = self.expressions(expression, key="settings", sep=" ") 4075 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4076 return f"{this}({kind}{args})"
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4097 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4098 expressions = self.expressions(expression, flat=True) 4099 expressions = f" {self.wrap(expressions)}" if expressions else "" 4100 buckets = self.sql(expression, "buckets") 4101 kind = self.sql(expression, "kind") 4102 buckets = f" BUCKETS {buckets}" if buckets else "" 4103 order = self.sql(expression, "order") 4104 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4109 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4110 expressions = self.expressions(expression, key="expressions", flat=True) 4111 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4112 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4113 buckets = self.sql(expression, "buckets") 4114 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4116 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4117 this = self.sql(expression, "this") 4118 having = self.sql(expression, "having") 4119 4120 if having: 4121 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4122 4123 return self.func("ANY_VALUE", this)
4125 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4126 transform = self.func("TRANSFORM", *expression.expressions) 4127 row_format_before = self.sql(expression, "row_format_before") 4128 row_format_before = f" {row_format_before}" if row_format_before else "" 4129 record_writer = self.sql(expression, "record_writer") 4130 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4131 using = f" USING {self.sql(expression, 'command_script')}" 4132 schema = self.sql(expression, "schema") 4133 schema = f" AS {schema}" if schema else "" 4134 row_format_after = self.sql(expression, "row_format_after") 4135 row_format_after = f" {row_format_after}" if row_format_after else "" 4136 record_reader = self.sql(expression, "record_reader") 4137 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4138 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4140 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4141 key_block_size = self.sql(expression, "key_block_size") 4142 if key_block_size: 4143 return f"KEY_BLOCK_SIZE = {key_block_size}" 4144 4145 using = self.sql(expression, "using") 4146 if using: 4147 return f"USING {using}" 4148 4149 parser = self.sql(expression, "parser") 4150 if parser: 4151 return f"WITH PARSER {parser}" 4152 4153 comment = self.sql(expression, "comment") 4154 if comment: 4155 return f"COMMENT {comment}" 4156 4157 visible = expression.args.get("visible") 4158 if visible is not None: 4159 return "VISIBLE" if visible else "INVISIBLE" 4160 4161 engine_attr = self.sql(expression, "engine_attr") 4162 if engine_attr: 4163 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4164 4165 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4166 if secondary_engine_attr: 4167 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4168 4169 self.unsupported("Unsupported index constraint option.") 4170 return ""
4176 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4177 kind = self.sql(expression, "kind") 4178 kind = f"{kind} INDEX" if kind else "INDEX" 4179 this = self.sql(expression, "this") 4180 this = f" {this}" if this else "" 4181 index_type = self.sql(expression, "index_type") 4182 index_type = f" USING {index_type}" if index_type else "" 4183 expressions = self.expressions(expression, flat=True) 4184 expressions = f" ({expressions})" if expressions else "" 4185 options = self.expressions(expression, key="options", sep=" ") 4186 options = f" {options}" if options else "" 4187 return f"{kind}{this}{index_type}{expressions}{options}"
4189 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4190 if self.NVL2_SUPPORTED: 4191 return self.function_fallback_sql(expression) 4192 4193 case = exp.Case().when( 4194 expression.this.is_(exp.null()).not_(copy=False), 4195 expression.args["true"], 4196 copy=False, 4197 ) 4198 else_cond = expression.args.get("false") 4199 if else_cond: 4200 case.else_(else_cond, copy=False) 4201 4202 return self.sql(case)
4204 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4205 this = self.sql(expression, "this") 4206 expr = self.sql(expression, "expression") 4207 iterator = self.sql(expression, "iterator") 4208 condition = self.sql(expression, "condition") 4209 condition = f" IF {condition}" if condition else "" 4210 return f"{this} FOR {expr} IN {iterator}{condition}"
4218 def predict_sql(self, expression: exp.Predict) -> str: 4219 model = self.sql(expression, "this") 4220 model = f"MODEL {model}" 4221 table = self.sql(expression, "expression") 4222 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4223 parameters = self.sql(expression, "params_struct") 4224 return self.func("PREDICT", model, table, parameters or None)
4226 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4227 model = self.sql(expression, "this") 4228 model = f"MODEL {model}" 4229 table = self.sql(expression, "expression") 4230 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4231 parameters = self.sql(expression, "params_struct") 4232 return self.func("GENERATE_EMBEDDING", model, table, parameters or None)
4234 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4235 this_sql = self.sql(expression, "this") 4236 if isinstance(expression.this, exp.Table): 4237 this_sql = f"TABLE {this_sql}" 4238 4239 return self.func( 4240 "FEATURES_AT_TIME", 4241 this_sql, 4242 expression.args.get("time"), 4243 expression.args.get("num_rows"), 4244 expression.args.get("ignore_feature_nulls"), 4245 )
4247 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4248 this_sql = self.sql(expression, "this") 4249 if isinstance(expression.this, exp.Table): 4250 this_sql = f"TABLE {this_sql}" 4251 4252 query_table = self.sql(expression, "query_table") 4253 if isinstance(expression.args["query_table"], exp.Table): 4254 query_table = f"TABLE {query_table}" 4255 4256 return self.func( 4257 "VECTOR_SEARCH", 4258 this_sql, 4259 expression.args.get("column_to_search"), 4260 query_table, 4261 expression.args.get("query_column_to_search"), 4262 expression.args.get("top_k"), 4263 expression.args.get("distance_type"), 4264 expression.args.get("options"), 4265 )
4277 def toarray_sql(self, expression: exp.ToArray) -> str: 4278 arg = expression.this 4279 if not arg.type: 4280 from sqlglot.optimizer.annotate_types import annotate_types 4281 4282 arg = annotate_types(arg, dialect=self.dialect) 4283 4284 if arg.is_type(exp.DataType.Type.ARRAY): 4285 return self.sql(arg) 4286 4287 cond_for_null = arg.is_(exp.null()) 4288 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4290 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4291 this = expression.this 4292 time_format = self.format_time(expression) 4293 4294 if time_format: 4295 return self.sql( 4296 exp.cast( 4297 exp.StrToTime(this=this, format=expression.args["format"]), 4298 exp.DataType.Type.TIME, 4299 ) 4300 ) 4301 4302 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4303 return self.sql(this) 4304 4305 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4307 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4308 this = expression.this 4309 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4310 return self.sql(this) 4311 4312 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4314 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4315 this = expression.this 4316 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4317 return self.sql(this) 4318 4319 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4321 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4322 this = expression.this 4323 time_format = self.format_time(expression) 4324 4325 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4326 return self.sql( 4327 exp.cast( 4328 exp.StrToTime(this=this, format=expression.args["format"]), 4329 exp.DataType.Type.DATE, 4330 ) 4331 ) 4332 4333 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4334 return self.sql(this) 4335 4336 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4348 def lastday_sql(self, expression: exp.LastDay) -> str: 4349 if self.LAST_DAY_SUPPORTS_DATE_PART: 4350 return self.function_fallback_sql(expression) 4351 4352 unit = expression.text("unit") 4353 if unit and unit != "MONTH": 4354 self.unsupported("Date parts are not supported in LAST_DAY.") 4355 4356 return self.func("LAST_DAY", expression.this)
4365 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4366 if self.CAN_IMPLEMENT_ARRAY_ANY: 4367 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4368 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4369 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4370 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4371 4372 from sqlglot.dialects import Dialect 4373 4374 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4375 if self.dialect.__class__ != Dialect: 4376 self.unsupported("ARRAY_ANY is unsupported") 4377 4378 return self.function_fallback_sql(expression)
4380 def struct_sql(self, expression: exp.Struct) -> str: 4381 expression.set( 4382 "expressions", 4383 [ 4384 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4385 if isinstance(e, exp.PropertyEQ) 4386 else e 4387 for e in expression.expressions 4388 ], 4389 ) 4390 4391 return self.function_fallback_sql(expression)
4399 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4400 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4401 tables = f" {self.expressions(expression)}" 4402 4403 exists = " IF EXISTS" if expression.args.get("exists") else "" 4404 4405 on_cluster = self.sql(expression, "cluster") 4406 on_cluster = f" {on_cluster}" if on_cluster else "" 4407 4408 identity = self.sql(expression, "identity") 4409 identity = f" {identity} IDENTITY" if identity else "" 4410 4411 option = self.sql(expression, "option") 4412 option = f" {option}" if option else "" 4413 4414 partition = self.sql(expression, "partition") 4415 partition = f" {partition}" if partition else "" 4416 4417 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4421 def convert_sql(self, expression: exp.Convert) -> str: 4422 to = expression.this 4423 value = expression.expression 4424 style = expression.args.get("style") 4425 safe = expression.args.get("safe") 4426 strict = expression.args.get("strict") 4427 4428 if not to or not value: 4429 return "" 4430 4431 # Retrieve length of datatype and override to default if not specified 4432 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4433 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4434 4435 transformed: t.Optional[exp.Expression] = None 4436 cast = exp.Cast if strict else exp.TryCast 4437 4438 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4439 if isinstance(style, exp.Literal) and style.is_int: 4440 from sqlglot.dialects.tsql import TSQL 4441 4442 style_value = style.name 4443 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4444 if not converted_style: 4445 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4446 4447 fmt = exp.Literal.string(converted_style) 4448 4449 if to.this == exp.DataType.Type.DATE: 4450 transformed = exp.StrToDate(this=value, format=fmt) 4451 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4452 transformed = exp.StrToTime(this=value, format=fmt) 4453 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4454 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4455 elif to.this == exp.DataType.Type.TEXT: 4456 transformed = exp.TimeToStr(this=value, format=fmt) 4457 4458 if not transformed: 4459 transformed = cast(this=value, to=to, safe=safe) 4460 4461 return self.sql(transformed)
4529 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4530 option = self.sql(expression, "this") 4531 4532 if expression.expressions: 4533 upper = option.upper() 4534 4535 # Snowflake FILE_FORMAT options are separated by whitespace 4536 sep = " " if upper == "FILE_FORMAT" else ", " 4537 4538 # Databricks copy/format options do not set their list of values with EQ 4539 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4540 values = self.expressions(expression, flat=True, sep=sep) 4541 return f"{option}{op}({values})" 4542 4543 value = self.sql(expression, "expression") 4544 4545 if not value: 4546 return option 4547 4548 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4549 4550 return f"{option}{op}{value}"
4552 def credentials_sql(self, expression: exp.Credentials) -> str: 4553 cred_expr = expression.args.get("credentials") 4554 if isinstance(cred_expr, exp.Literal): 4555 # Redshift case: CREDENTIALS <string> 4556 credentials = self.sql(expression, "credentials") 4557 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4558 else: 4559 # Snowflake case: CREDENTIALS = (...) 4560 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4561 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4562 4563 storage = self.sql(expression, "storage") 4564 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4565 4566 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4567 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4568 4569 iam_role = self.sql(expression, "iam_role") 4570 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4571 4572 region = self.sql(expression, "region") 4573 region = f" REGION {region}" if region else "" 4574 4575 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4577 def copy_sql(self, expression: exp.Copy) -> str: 4578 this = self.sql(expression, "this") 4579 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4580 4581 credentials = self.sql(expression, "credentials") 4582 credentials = self.seg(credentials) if credentials else "" 4583 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4584 files = self.expressions(expression, key="files", flat=True) 4585 4586 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4587 params = self.expressions( 4588 expression, 4589 key="params", 4590 sep=sep, 4591 new_line=True, 4592 skip_last=True, 4593 skip_first=True, 4594 indent=self.COPY_PARAMS_ARE_WRAPPED, 4595 ) 4596 4597 if params: 4598 if self.COPY_PARAMS_ARE_WRAPPED: 4599 params = f" WITH ({params})" 4600 elif not self.pretty: 4601 params = f" {params}" 4602 4603 return f"COPY{this}{kind} {files}{credentials}{params}"
4608 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4609 on_sql = "ON" if expression.args.get("on") else "OFF" 4610 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4611 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4612 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4613 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4614 4615 if filter_col or retention_period: 4616 on_sql = self.func("ON", filter_col, retention_period) 4617 4618 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4620 def maskingpolicycolumnconstraint_sql( 4621 self, expression: exp.MaskingPolicyColumnConstraint 4622 ) -> str: 4623 this = self.sql(expression, "this") 4624 expressions = self.expressions(expression, flat=True) 4625 expressions = f" USING ({expressions})" if expressions else "" 4626 return f"MASKING POLICY {this}{expressions}"
4636 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4637 this = self.sql(expression, "this") 4638 expr = expression.expression 4639 4640 if isinstance(expr, exp.Func): 4641 # T-SQL's CLR functions are case sensitive 4642 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4643 else: 4644 expr = self.sql(expression, "expression") 4645 4646 return self.scope_resolution(expr, this)
4654 def rand_sql(self, expression: exp.Rand) -> str: 4655 lower = self.sql(expression, "lower") 4656 upper = self.sql(expression, "upper") 4657 4658 if lower and upper: 4659 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4660 return self.func("RAND", expression.this)
4662 def changes_sql(self, expression: exp.Changes) -> str: 4663 information = self.sql(expression, "information") 4664 information = f"INFORMATION => {information}" 4665 at_before = self.sql(expression, "at_before") 4666 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4667 end = self.sql(expression, "end") 4668 end = f"{self.seg('')}{end}" if end else "" 4669 4670 return f"CHANGES ({information}){at_before}{end}"
4672 def pad_sql(self, expression: exp.Pad) -> str: 4673 prefix = "L" if expression.args.get("is_left") else "R" 4674 4675 fill_pattern = self.sql(expression, "fill_pattern") or None 4676 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4677 fill_pattern = "' '" 4678 4679 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4685 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4686 generate_series = exp.GenerateSeries(**expression.args) 4687 4688 parent = expression.parent 4689 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4690 parent = parent.parent 4691 4692 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4693 return self.sql(exp.Unnest(expressions=[generate_series])) 4694 4695 if isinstance(parent, exp.Select): 4696 self.unsupported("GenerateSeries projection unnesting is not supported.") 4697 4698 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4700 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4701 exprs = expression.expressions 4702 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4703 if len(exprs) == 0: 4704 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4705 else: 4706 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4707 else: 4708 rhs = self.expressions(expression) # type: ignore 4709 4710 return self.func(name, expression.this, rhs or None)
4712 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4713 if self.SUPPORTS_CONVERT_TIMEZONE: 4714 return self.function_fallback_sql(expression) 4715 4716 source_tz = expression.args.get("source_tz") 4717 target_tz = expression.args.get("target_tz") 4718 timestamp = expression.args.get("timestamp") 4719 4720 if source_tz and timestamp: 4721 timestamp = exp.AtTimeZone( 4722 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4723 ) 4724 4725 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4726 4727 return self.sql(expr)
4729 def json_sql(self, expression: exp.JSON) -> str: 4730 this = self.sql(expression, "this") 4731 this = f" {this}" if this else "" 4732 4733 _with = expression.args.get("with") 4734 4735 if _with is None: 4736 with_sql = "" 4737 elif not _with: 4738 with_sql = " WITHOUT" 4739 else: 4740 with_sql = " WITH" 4741 4742 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4743 4744 return f"JSON{this}{with_sql}{unique_sql}"
4746 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4747 def _generate_on_options(arg: t.Any) -> str: 4748 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4749 4750 path = self.sql(expression, "path") 4751 returning = self.sql(expression, "returning") 4752 returning = f" RETURNING {returning}" if returning else "" 4753 4754 on_condition = self.sql(expression, "on_condition") 4755 on_condition = f" {on_condition}" if on_condition else "" 4756 4757 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4759 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4760 else_ = "ELSE " if expression.args.get("else_") else "" 4761 condition = self.sql(expression, "expression") 4762 condition = f"WHEN {condition} THEN " if condition else else_ 4763 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4764 return f"{condition}{insert}"
4772 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4773 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4774 empty = expression.args.get("empty") 4775 empty = ( 4776 f"DEFAULT {empty} ON EMPTY" 4777 if isinstance(empty, exp.Expression) 4778 else self.sql(expression, "empty") 4779 ) 4780 4781 error = expression.args.get("error") 4782 error = ( 4783 f"DEFAULT {error} ON ERROR" 4784 if isinstance(error, exp.Expression) 4785 else self.sql(expression, "error") 4786 ) 4787 4788 if error and empty: 4789 error = ( 4790 f"{empty} {error}" 4791 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4792 else f"{error} {empty}" 4793 ) 4794 empty = "" 4795 4796 null = self.sql(expression, "null") 4797 4798 return f"{empty}{error}{null}"
4804 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4805 this = self.sql(expression, "this") 4806 path = self.sql(expression, "path") 4807 4808 passing = self.expressions(expression, "passing") 4809 passing = f" PASSING {passing}" if passing else "" 4810 4811 on_condition = self.sql(expression, "on_condition") 4812 on_condition = f" {on_condition}" if on_condition else "" 4813 4814 path = f"{path}{passing}{on_condition}" 4815 4816 return self.func("JSON_EXISTS", this, path)
4818 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4819 array_agg = self.function_fallback_sql(expression) 4820 4821 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4822 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4823 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4824 parent = expression.parent 4825 if isinstance(parent, exp.Filter): 4826 parent_cond = parent.expression.this 4827 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4828 else: 4829 this = expression.this 4830 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4831 if this.find(exp.Column): 4832 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4833 this_sql = ( 4834 self.expressions(this) 4835 if isinstance(this, exp.Distinct) 4836 else self.sql(expression, "this") 4837 ) 4838 4839 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4840 4841 return array_agg
4914 def overlay_sql(self, expression: exp.Overlay): 4915 this = self.sql(expression, "this") 4916 expr = self.sql(expression, "expression") 4917 from_sql = self.sql(expression, "from") 4918 for_sql = self.sql(expression, "for") 4919 for_sql = f" FOR {for_sql}" if for_sql else "" 4920 4921 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4927 def string_sql(self, expression: exp.String) -> str: 4928 this = expression.this 4929 zone = expression.args.get("zone") 4930 4931 if zone: 4932 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4933 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4934 # set for source_tz to transpile the time conversion before the STRING cast 4935 this = exp.ConvertTimezone( 4936 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4937 ) 4938 4939 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4949 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4950 filler = self.sql(expression, "this") 4951 filler = f" {filler}" if filler else "" 4952 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4953 return f"TRUNCATE{filler} {with_count}"
4955 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4956 if self.SUPPORTS_UNIX_SECONDS: 4957 return self.function_fallback_sql(expression) 4958 4959 start_ts = exp.cast( 4960 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4961 ) 4962 4963 return self.sql( 4964 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4965 )
4967 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4968 dim = expression.expression 4969 4970 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4971 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4972 if not (dim.is_int and dim.name == "1"): 4973 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4974 dim = None 4975 4976 # If dimension is required but not specified, default initialize it 4977 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4978 dim = exp.Literal.number(1) 4979 4980 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4982 def attach_sql(self, expression: exp.Attach) -> str: 4983 this = self.sql(expression, "this") 4984 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4985 expressions = self.expressions(expression) 4986 expressions = f" ({expressions})" if expressions else "" 4987 4988 return f"ATTACH{exists_sql} {this}{expressions}"
4990 def detach_sql(self, expression: exp.Detach) -> str: 4991 this = self.sql(expression, "this") 4992 # the DATABASE keyword is required if IF EXISTS is set 4993 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4994 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4995 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4996 4997 return f"DETACH{exists_sql} {this}"
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
5010 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5011 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5012 encode = f"{encode} {self.sql(expression, 'this')}" 5013 5014 properties = expression.args.get("properties") 5015 if properties: 5016 encode = f"{encode} {self.properties(properties)}" 5017 5018 return encode
5020 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5021 this = self.sql(expression, "this") 5022 include = f"INCLUDE {this}" 5023 5024 column_def = self.sql(expression, "column_def") 5025 if column_def: 5026 include = f"{include} {column_def}" 5027 5028 alias = self.sql(expression, "alias") 5029 if alias: 5030 include = f"{include} AS {alias}" 5031 5032 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
5044 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5045 partitions = self.expressions(expression, "partition_expressions") 5046 create = self.expressions(expression, "create_expressions") 5047 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
5049 def partitionbyrangepropertydynamic_sql( 5050 self, expression: exp.PartitionByRangePropertyDynamic 5051 ) -> str: 5052 start = self.sql(expression, "start") 5053 end = self.sql(expression, "end") 5054 5055 every = expression.args["every"] 5056 if isinstance(every, exp.Interval) and every.this.is_string: 5057 every.this.replace(exp.Literal.number(every.name)) 5058 5059 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5072 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5073 kind = self.sql(expression, "kind") 5074 option = self.sql(expression, "option") 5075 option = f" {option}" if option else "" 5076 this = self.sql(expression, "this") 5077 this = f" {this}" if this else "" 5078 columns = self.expressions(expression) 5079 columns = f" {columns}" if columns else "" 5080 return f"{kind}{option} STATISTICS{this}{columns}"
5082 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5083 this = self.sql(expression, "this") 5084 columns = self.expressions(expression) 5085 inner_expression = self.sql(expression, "expression") 5086 inner_expression = f" {inner_expression}" if inner_expression else "" 5087 update_options = self.sql(expression, "update_options") 5088 update_options = f" {update_options} UPDATE" if update_options else "" 5089 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
5100 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5101 kind = self.sql(expression, "kind") 5102 this = self.sql(expression, "this") 5103 this = f" {this}" if this else "" 5104 inner_expression = self.sql(expression, "expression") 5105 return f"VALIDATE {kind}{this}{inner_expression}"
5107 def analyze_sql(self, expression: exp.Analyze) -> str: 5108 options = self.expressions(expression, key="options", sep=" ") 5109 options = f" {options}" if options else "" 5110 kind = self.sql(expression, "kind") 5111 kind = f" {kind}" if kind else "" 5112 this = self.sql(expression, "this") 5113 this = f" {this}" if this else "" 5114 mode = self.sql(expression, "mode") 5115 mode = f" {mode}" if mode else "" 5116 properties = self.sql(expression, "properties") 5117 properties = f" {properties}" if properties else "" 5118 partition = self.sql(expression, "partition") 5119 partition = f" {partition}" if partition else "" 5120 inner_expression = self.sql(expression, "expression") 5121 inner_expression = f" {inner_expression}" if inner_expression else "" 5122 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5124 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5125 this = self.sql(expression, "this") 5126 namespaces = self.expressions(expression, key="namespaces") 5127 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5128 passing = self.expressions(expression, key="passing") 5129 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5130 columns = self.expressions(expression, key="columns") 5131 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5132 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5133 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5139 def export_sql(self, expression: exp.Export) -> str: 5140 this = self.sql(expression, "this") 5141 connection = self.sql(expression, "connection") 5142 connection = f"WITH CONNECTION {connection} " if connection else "" 5143 options = self.sql(expression, "options") 5144 return f"EXPORT DATA {connection}{options} AS {this}"
5149 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5150 variable = self.sql(expression, "this") 5151 default = self.sql(expression, "default") 5152 default = f" = {default}" if default else "" 5153 5154 kind = self.sql(expression, "kind") 5155 if isinstance(expression.args.get("kind"), exp.Schema): 5156 kind = f"TABLE {kind}" 5157 5158 return f"{variable} AS {kind}{default}"
5160 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5161 kind = self.sql(expression, "kind") 5162 this = self.sql(expression, "this") 5163 set = self.sql(expression, "expression") 5164 using = self.sql(expression, "using") 5165 using = f" USING {using}" if using else "" 5166 5167 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5168 5169 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5188 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5189 # Snowflake GET/PUT statements: 5190 # PUT <file> <internalStage> <properties> 5191 # GET <internalStage> <file> <properties> 5192 props = expression.args.get("properties") 5193 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5194 this = self.sql(expression, "this") 5195 target = self.sql(expression, "target") 5196 5197 if isinstance(expression, exp.Put): 5198 return f"PUT {this} {target}{props_sql}" 5199 else: 5200 return f"GET {target} {this}{props_sql}"
5208 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5209 if self.SUPPORTS_DECODE_CASE: 5210 return self.func("DECODE", *expression.expressions) 5211 5212 expression, *expressions = expression.expressions 5213 5214 ifs = [] 5215 for search, result in zip(expressions[::2], expressions[1::2]): 5216 if isinstance(search, exp.Literal): 5217 ifs.append(exp.If(this=expression.eq(search), true=result)) 5218 elif isinstance(search, exp.Null): 5219 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5220 else: 5221 if isinstance(search, exp.Binary): 5222 search = exp.paren(search) 5223 5224 cond = exp.or_( 5225 expression.eq(search), 5226 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5227 copy=False, 5228 ) 5229 ifs.append(exp.If(this=cond, true=result)) 5230 5231 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5232 return self.sql(case)
5234 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5235 this = self.sql(expression, "this") 5236 this = self.seg(this, sep="") 5237 dimensions = self.expressions( 5238 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5239 ) 5240 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5241 metrics = self.expressions( 5242 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5243 ) 5244 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5245 where = self.sql(expression, "where") 5246 where = self.seg(f"WHERE {where}") if where else "" 5247 body = self.indent(this + metrics + dimensions + where, skip_first=True) 5248 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5250 def getextract_sql(self, expression: exp.GetExtract) -> str: 5251 this = expression.this 5252 expr = expression.expression 5253 5254 if not this.type or not expression.type: 5255 from sqlglot.optimizer.annotate_types import annotate_types 5256 5257 this = annotate_types(this, dialect=self.dialect) 5258 5259 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5260 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5261 5262 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql(self, expression: sqlglot.expressions.RefreshTriggerProperty) -> str:
5279 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5280 method = self.sql(expression, "method") 5281 kind = expression.args.get("kind") 5282 if not kind: 5283 return f"REFRESH {method}" 5284 5285 every = self.sql(expression, "every") 5286 unit = self.sql(expression, "unit") 5287 every = f" EVERY {every} {unit}" if every else "" 5288 starts = self.sql(expression, "starts") 5289 starts = f" STARTS {starts}" if starts else "" 5290 5291 return f"REFRESH {method} ON {kind}{every}{starts}"