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.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 164 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 165 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 166 exp.LanguageProperty: lambda self, e: self.naked_property(e), 167 exp.LocationProperty: lambda self, e: self.naked_property(e), 168 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 169 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 170 exp.NonClusteredColumnConstraint: lambda self, 171 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 172 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 173 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 174 exp.OnCommitProperty: lambda _, 175 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 176 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 177 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 178 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 179 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 180 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 181 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 182 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 183 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 184 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 185 exp.ProjectionPolicyColumnConstraint: lambda self, 186 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 187 exp.Put: lambda self, e: self.get_put_sql(e), 188 exp.RemoteWithConnectionModelProperty: lambda self, 189 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 190 exp.ReturnsProperty: lambda self, e: ( 191 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 192 ), 193 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 194 exp.SecureProperty: lambda *_: "SECURE", 195 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 196 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 197 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 198 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 199 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 200 exp.SqlReadWriteProperty: lambda _, e: e.name, 201 exp.SqlSecurityProperty: lambda _, 202 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 203 exp.StabilityProperty: lambda _, e: e.name, 204 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 205 exp.StreamingTableProperty: lambda *_: "STREAMING", 206 exp.StrictProperty: lambda *_: "STRICT", 207 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 208 exp.TableColumn: lambda self, e: self.sql(e.this), 209 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 210 exp.TemporaryProperty: lambda *_: "TEMPORARY", 211 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 212 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 213 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 214 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 215 exp.TransientProperty: lambda *_: "TRANSIENT", 216 exp.Union: lambda self, e: self.set_operations(e), 217 exp.UnloggedProperty: lambda *_: "UNLOGGED", 218 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 219 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 220 exp.Uuid: lambda *_: "UUID()", 221 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 222 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 223 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 224 exp.UtcTimestamp: lambda self, e: self.sql( 225 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 226 ), 227 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 228 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 229 exp.VolatileProperty: lambda *_: "VOLATILE", 230 exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})", 231 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 232 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 233 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 234 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 235 exp.ForceProperty: lambda *_: "FORCE", 236 } 237 238 # Whether null ordering is supported in order by 239 # True: Full Support, None: No support, False: No support for certain cases 240 # such as window specifications, aggregate functions etc 241 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 242 243 # Whether ignore nulls is inside the agg or outside. 244 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 245 IGNORE_NULLS_IN_FUNC = False 246 247 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 248 LOCKING_READS_SUPPORTED = False 249 250 # Whether the EXCEPT and INTERSECT operations can return duplicates 251 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 252 253 # Wrap derived values in parens, usually standard but spark doesn't support it 254 WRAP_DERIVED_VALUES = True 255 256 # Whether create function uses an AS before the RETURN 257 CREATE_FUNCTION_RETURN_AS = True 258 259 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 260 MATCHED_BY_SOURCE = True 261 262 # Whether the INTERVAL expression works only with values like '1 day' 263 SINGLE_STRING_INTERVAL = False 264 265 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 266 INTERVAL_ALLOWS_PLURAL_FORM = True 267 268 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 269 LIMIT_FETCH = "ALL" 270 271 # Whether limit and fetch allows expresions or just limits 272 LIMIT_ONLY_LITERALS = False 273 274 # Whether a table is allowed to be renamed with a db 275 RENAME_TABLE_WITH_DB = True 276 277 # The separator for grouping sets and rollups 278 GROUPINGS_SEP = "," 279 280 # The string used for creating an index on a table 281 INDEX_ON = "ON" 282 283 # Whether join hints should be generated 284 JOIN_HINTS = True 285 286 # Whether table hints should be generated 287 TABLE_HINTS = True 288 289 # Whether query hints should be generated 290 QUERY_HINTS = True 291 292 # What kind of separator to use for query hints 293 QUERY_HINT_SEP = ", " 294 295 # Whether comparing against booleans (e.g. x IS TRUE) is supported 296 IS_BOOL_ALLOWED = True 297 298 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 299 DUPLICATE_KEY_UPDATE_WITH_SET = True 300 301 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 302 LIMIT_IS_TOP = False 303 304 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 305 RETURNING_END = True 306 307 # Whether to generate an unquoted value for EXTRACT's date part argument 308 EXTRACT_ALLOWS_QUOTES = True 309 310 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 311 TZ_TO_WITH_TIME_ZONE = False 312 313 # Whether the NVL2 function is supported 314 NVL2_SUPPORTED = True 315 316 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 317 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 318 319 # Whether VALUES statements can be used as derived tables. 320 # MySQL 5 and Redshift do not allow this, so when False, it will convert 321 # SELECT * VALUES into SELECT UNION 322 VALUES_AS_TABLE = True 323 324 # Whether the word COLUMN is included when adding a column with ALTER TABLE 325 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 326 327 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 328 UNNEST_WITH_ORDINALITY = True 329 330 # Whether FILTER (WHERE cond) can be used for conditional aggregation 331 AGGREGATE_FILTER_SUPPORTED = True 332 333 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 334 SEMI_ANTI_JOIN_WITH_SIDE = True 335 336 # Whether to include the type of a computed column in the CREATE DDL 337 COMPUTED_COLUMN_WITH_TYPE = True 338 339 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 340 SUPPORTS_TABLE_COPY = True 341 342 # Whether parentheses are required around the table sample's expression 343 TABLESAMPLE_REQUIRES_PARENS = True 344 345 # Whether a table sample clause's size needs to be followed by the ROWS keyword 346 TABLESAMPLE_SIZE_IS_ROWS = True 347 348 # The keyword(s) to use when generating a sample clause 349 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 350 351 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 352 TABLESAMPLE_WITH_METHOD = True 353 354 # The keyword to use when specifying the seed of a sample clause 355 TABLESAMPLE_SEED_KEYWORD = "SEED" 356 357 # Whether COLLATE is a function instead of a binary operator 358 COLLATE_IS_FUNC = False 359 360 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 361 DATA_TYPE_SPECIFIERS_ALLOWED = False 362 363 # Whether conditions require booleans WHERE x = 0 vs WHERE x 364 ENSURE_BOOLS = False 365 366 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 367 CTE_RECURSIVE_KEYWORD_REQUIRED = True 368 369 # Whether CONCAT requires >1 arguments 370 SUPPORTS_SINGLE_ARG_CONCAT = True 371 372 # Whether LAST_DAY function supports a date part argument 373 LAST_DAY_SUPPORTS_DATE_PART = True 374 375 # Whether named columns are allowed in table aliases 376 SUPPORTS_TABLE_ALIAS_COLUMNS = True 377 378 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 379 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 380 381 # What delimiter to use for separating JSON key/value pairs 382 JSON_KEY_VALUE_PAIR_SEP = ":" 383 384 # INSERT OVERWRITE TABLE x override 385 INSERT_OVERWRITE = " OVERWRITE TABLE" 386 387 # Whether the SELECT .. INTO syntax is used instead of CTAS 388 SUPPORTS_SELECT_INTO = False 389 390 # Whether UNLOGGED tables can be created 391 SUPPORTS_UNLOGGED_TABLES = False 392 393 # Whether the CREATE TABLE LIKE statement is supported 394 SUPPORTS_CREATE_TABLE_LIKE = True 395 396 # Whether the LikeProperty needs to be specified inside of the schema clause 397 LIKE_PROPERTY_INSIDE_SCHEMA = False 398 399 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 400 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 401 MULTI_ARG_DISTINCT = True 402 403 # Whether the JSON extraction operators expect a value of type JSON 404 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 405 406 # Whether bracketed keys like ["foo"] are supported in JSON paths 407 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 408 409 # Whether to escape keys using single quotes in JSON paths 410 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 411 412 # The JSONPathPart expressions supported by this dialect 413 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 414 415 # Whether any(f(x) for x in array) can be implemented by this dialect 416 CAN_IMPLEMENT_ARRAY_ANY = False 417 418 # Whether the function TO_NUMBER is supported 419 SUPPORTS_TO_NUMBER = True 420 421 # Whether EXCLUDE in window specification is supported 422 SUPPORTS_WINDOW_EXCLUDE = False 423 424 # Whether or not set op modifiers apply to the outer set op or select. 425 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 426 # True means limit 1 happens after the set op, False means it it happens on y. 427 SET_OP_MODIFIERS = True 428 429 # Whether parameters from COPY statement are wrapped in parentheses 430 COPY_PARAMS_ARE_WRAPPED = True 431 432 # Whether values of params are set with "=" token or empty space 433 COPY_PARAMS_EQ_REQUIRED = False 434 435 # Whether COPY statement has INTO keyword 436 COPY_HAS_INTO_KEYWORD = True 437 438 # Whether the conditional TRY(expression) function is supported 439 TRY_SUPPORTED = True 440 441 # Whether the UESCAPE syntax in unicode strings is supported 442 SUPPORTS_UESCAPE = True 443 444 # Function used to replace escaped unicode codes in unicode strings 445 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 446 447 # The keyword to use when generating a star projection with excluded columns 448 STAR_EXCEPT = "EXCEPT" 449 450 # The HEX function name 451 HEX_FUNC = "HEX" 452 453 # The keywords to use when prefixing & separating WITH based properties 454 WITH_PROPERTIES_PREFIX = "WITH" 455 456 # Whether to quote the generated expression of exp.JsonPath 457 QUOTE_JSON_PATH = True 458 459 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 460 PAD_FILL_PATTERN_IS_REQUIRED = False 461 462 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 463 SUPPORTS_EXPLODING_PROJECTIONS = True 464 465 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 466 ARRAY_CONCAT_IS_VAR_LEN = True 467 468 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 469 SUPPORTS_CONVERT_TIMEZONE = False 470 471 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 472 SUPPORTS_MEDIAN = True 473 474 # Whether UNIX_SECONDS(timestamp) is supported 475 SUPPORTS_UNIX_SECONDS = False 476 477 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 478 ALTER_SET_WRAPPED = False 479 480 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 481 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 482 # TODO: The normalization should be done by default once we've tested it across all dialects. 483 NORMALIZE_EXTRACT_DATE_PARTS = False 484 485 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 486 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 487 488 # The function name of the exp.ArraySize expression 489 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 490 491 # The syntax to use when altering the type of a column 492 ALTER_SET_TYPE = "SET DATA TYPE" 493 494 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 495 # None -> Doesn't support it at all 496 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 497 # True (Postgres) -> Explicitly requires it 498 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 499 500 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 501 SUPPORTS_DECODE_CASE = True 502 503 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 504 SUPPORTS_BETWEEN_FLAGS = False 505 506 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 507 SUPPORTS_LIKE_QUANTIFIERS = True 508 509 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 510 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 511 512 TYPE_MAPPING = { 513 exp.DataType.Type.DATETIME2: "TIMESTAMP", 514 exp.DataType.Type.NCHAR: "CHAR", 515 exp.DataType.Type.NVARCHAR: "VARCHAR", 516 exp.DataType.Type.MEDIUMTEXT: "TEXT", 517 exp.DataType.Type.LONGTEXT: "TEXT", 518 exp.DataType.Type.TINYTEXT: "TEXT", 519 exp.DataType.Type.BLOB: "VARBINARY", 520 exp.DataType.Type.MEDIUMBLOB: "BLOB", 521 exp.DataType.Type.LONGBLOB: "BLOB", 522 exp.DataType.Type.TINYBLOB: "BLOB", 523 exp.DataType.Type.INET: "INET", 524 exp.DataType.Type.ROWVERSION: "VARBINARY", 525 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 526 } 527 528 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 529 530 TIME_PART_SINGULARS = { 531 "MICROSECONDS": "MICROSECOND", 532 "SECONDS": "SECOND", 533 "MINUTES": "MINUTE", 534 "HOURS": "HOUR", 535 "DAYS": "DAY", 536 "WEEKS": "WEEK", 537 "MONTHS": "MONTH", 538 "QUARTERS": "QUARTER", 539 "YEARS": "YEAR", 540 } 541 542 AFTER_HAVING_MODIFIER_TRANSFORMS = { 543 "cluster": lambda self, e: self.sql(e, "cluster"), 544 "distribute": lambda self, e: self.sql(e, "distribute"), 545 "sort": lambda self, e: self.sql(e, "sort"), 546 "windows": lambda self, e: ( 547 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 548 if e.args.get("windows") 549 else "" 550 ), 551 "qualify": lambda self, e: self.sql(e, "qualify"), 552 } 553 554 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 555 556 STRUCT_DELIMITER = ("<", ">") 557 558 PARAMETER_TOKEN = "@" 559 NAMED_PLACEHOLDER_TOKEN = ":" 560 561 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 562 563 PROPERTIES_LOCATION = { 564 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 566 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 570 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 572 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 573 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 575 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 579 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 581 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 582 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 584 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 588 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 592 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 593 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 594 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 595 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 596 exp.HeapProperty: exp.Properties.Location.POST_WITH, 597 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 599 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 600 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 602 exp.JournalProperty: exp.Properties.Location.POST_NAME, 603 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 608 exp.LogProperty: exp.Properties.Location.POST_NAME, 609 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 610 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 611 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 612 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 614 exp.Order: exp.Properties.Location.POST_SCHEMA, 615 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 617 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 619 exp.Property: exp.Properties.Location.POST_WITH, 620 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 628 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 630 exp.Set: exp.Properties.Location.POST_SCHEMA, 631 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.SetProperty: exp.Properties.Location.POST_CREATE, 633 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 635 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 636 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 639 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 640 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 641 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 642 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.Tags: exp.Properties.Location.POST_WITH, 644 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 645 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 646 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 647 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 648 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 649 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 650 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 651 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 652 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 653 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 654 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 655 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 656 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 657 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 658 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 659 } 660 661 # Keywords that can't be used as unquoted identifier names 662 RESERVED_KEYWORDS: t.Set[str] = set() 663 664 # Expressions whose comments are separated from them for better formatting 665 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 666 exp.Command, 667 exp.Create, 668 exp.Describe, 669 exp.Delete, 670 exp.Drop, 671 exp.From, 672 exp.Insert, 673 exp.Join, 674 exp.MultitableInserts, 675 exp.Order, 676 exp.Group, 677 exp.Having, 678 exp.Select, 679 exp.SetOperation, 680 exp.Update, 681 exp.Where, 682 exp.With, 683 ) 684 685 # Expressions that should not have their comments generated in maybe_comment 686 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 687 exp.Binary, 688 exp.SetOperation, 689 ) 690 691 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 692 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 693 exp.Column, 694 exp.Literal, 695 exp.Neg, 696 exp.Paren, 697 ) 698 699 PARAMETERIZABLE_TEXT_TYPES = { 700 exp.DataType.Type.NVARCHAR, 701 exp.DataType.Type.VARCHAR, 702 exp.DataType.Type.CHAR, 703 exp.DataType.Type.NCHAR, 704 } 705 706 # Expressions that need to have all CTEs under them bubbled up to them 707 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 708 709 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 710 711 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 712 713 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 714 715 __slots__ = ( 716 "pretty", 717 "identify", 718 "normalize", 719 "pad", 720 "_indent", 721 "normalize_functions", 722 "unsupported_level", 723 "max_unsupported", 724 "leading_comma", 725 "max_text_width", 726 "comments", 727 "dialect", 728 "unsupported_messages", 729 "_escaped_quote_end", 730 "_escaped_identifier_end", 731 "_next_name", 732 "_identifier_start", 733 "_identifier_end", 734 "_quote_json_path_key_using_brackets", 735 ) 736 737 def __init__( 738 self, 739 pretty: t.Optional[bool] = None, 740 identify: str | bool = False, 741 normalize: bool = False, 742 pad: int = 2, 743 indent: int = 2, 744 normalize_functions: t.Optional[str | bool] = None, 745 unsupported_level: ErrorLevel = ErrorLevel.WARN, 746 max_unsupported: int = 3, 747 leading_comma: bool = False, 748 max_text_width: int = 80, 749 comments: bool = True, 750 dialect: DialectType = None, 751 ): 752 import sqlglot 753 from sqlglot.dialects import Dialect 754 755 self.pretty = pretty if pretty is not None else sqlglot.pretty 756 self.identify = identify 757 self.normalize = normalize 758 self.pad = pad 759 self._indent = indent 760 self.unsupported_level = unsupported_level 761 self.max_unsupported = max_unsupported 762 self.leading_comma = leading_comma 763 self.max_text_width = max_text_width 764 self.comments = comments 765 self.dialect = Dialect.get_or_raise(dialect) 766 767 # This is both a Dialect property and a Generator argument, so we prioritize the latter 768 self.normalize_functions = ( 769 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 770 ) 771 772 self.unsupported_messages: t.List[str] = [] 773 self._escaped_quote_end: str = ( 774 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 775 ) 776 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 777 778 self._next_name = name_sequence("_t") 779 780 self._identifier_start = self.dialect.IDENTIFIER_START 781 self._identifier_end = self.dialect.IDENTIFIER_END 782 783 self._quote_json_path_key_using_brackets = True 784 785 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 786 """ 787 Generates the SQL string corresponding to the given syntax tree. 788 789 Args: 790 expression: The syntax tree. 791 copy: Whether to copy the expression. The generator performs mutations so 792 it is safer to copy. 793 794 Returns: 795 The SQL string corresponding to `expression`. 796 """ 797 if copy: 798 expression = expression.copy() 799 800 expression = self.preprocess(expression) 801 802 self.unsupported_messages = [] 803 sql = self.sql(expression).strip() 804 805 if self.pretty: 806 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 807 808 if self.unsupported_level == ErrorLevel.IGNORE: 809 return sql 810 811 if self.unsupported_level == ErrorLevel.WARN: 812 for msg in self.unsupported_messages: 813 logger.warning(msg) 814 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 815 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 816 817 return sql 818 819 def preprocess(self, expression: exp.Expression) -> exp.Expression: 820 """Apply generic preprocessing transformations to a given expression.""" 821 expression = self._move_ctes_to_top_level(expression) 822 823 if self.ENSURE_BOOLS: 824 from sqlglot.transforms import ensure_bools 825 826 expression = ensure_bools(expression) 827 828 return expression 829 830 def _move_ctes_to_top_level(self, expression: E) -> E: 831 if ( 832 not expression.parent 833 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 834 and any(node.parent is not expression for node in expression.find_all(exp.With)) 835 ): 836 from sqlglot.transforms import move_ctes_to_top_level 837 838 expression = move_ctes_to_top_level(expression) 839 return expression 840 841 def unsupported(self, message: str) -> None: 842 if self.unsupported_level == ErrorLevel.IMMEDIATE: 843 raise UnsupportedError(message) 844 self.unsupported_messages.append(message) 845 846 def sep(self, sep: str = " ") -> str: 847 return f"{sep.strip()}\n" if self.pretty else sep 848 849 def seg(self, sql: str, sep: str = " ") -> str: 850 return f"{self.sep(sep)}{sql}" 851 852 def sanitize_comment(self, comment: str) -> str: 853 comment = " " + comment if comment[0].strip() else comment 854 comment = comment + " " if comment[-1].strip() else comment 855 856 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 857 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 858 comment = comment.replace("*/", "* /") 859 860 return comment 861 862 def maybe_comment( 863 self, 864 sql: str, 865 expression: t.Optional[exp.Expression] = None, 866 comments: t.Optional[t.List[str]] = None, 867 separated: bool = False, 868 ) -> str: 869 comments = ( 870 ((expression and expression.comments) if comments is None else comments) # type: ignore 871 if self.comments 872 else None 873 ) 874 875 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 876 return sql 877 878 comments_sql = " ".join( 879 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 880 ) 881 882 if not comments_sql: 883 return sql 884 885 comments_sql = self._replace_line_breaks(comments_sql) 886 887 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 888 return ( 889 f"{self.sep()}{comments_sql}{sql}" 890 if not sql or sql[0].isspace() 891 else f"{comments_sql}{self.sep()}{sql}" 892 ) 893 894 return f"{sql} {comments_sql}" 895 896 def wrap(self, expression: exp.Expression | str) -> str: 897 this_sql = ( 898 self.sql(expression) 899 if isinstance(expression, exp.UNWRAPPED_QUERIES) 900 else self.sql(expression, "this") 901 ) 902 if not this_sql: 903 return "()" 904 905 this_sql = self.indent(this_sql, level=1, pad=0) 906 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 907 908 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 909 original = self.identify 910 self.identify = False 911 result = func(*args, **kwargs) 912 self.identify = original 913 return result 914 915 def normalize_func(self, name: str) -> str: 916 if self.normalize_functions == "upper" or self.normalize_functions is True: 917 return name.upper() 918 if self.normalize_functions == "lower": 919 return name.lower() 920 return name 921 922 def indent( 923 self, 924 sql: str, 925 level: int = 0, 926 pad: t.Optional[int] = None, 927 skip_first: bool = False, 928 skip_last: bool = False, 929 ) -> str: 930 if not self.pretty or not sql: 931 return sql 932 933 pad = self.pad if pad is None else pad 934 lines = sql.split("\n") 935 936 return "\n".join( 937 ( 938 line 939 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 940 else f"{' ' * (level * self._indent + pad)}{line}" 941 ) 942 for i, line in enumerate(lines) 943 ) 944 945 def sql( 946 self, 947 expression: t.Optional[str | exp.Expression], 948 key: t.Optional[str] = None, 949 comment: bool = True, 950 ) -> str: 951 if not expression: 952 return "" 953 954 if isinstance(expression, str): 955 return expression 956 957 if key: 958 value = expression.args.get(key) 959 if value: 960 return self.sql(value) 961 return "" 962 963 transform = self.TRANSFORMS.get(expression.__class__) 964 965 if callable(transform): 966 sql = transform(self, expression) 967 elif isinstance(expression, exp.Expression): 968 exp_handler_name = f"{expression.key}_sql" 969 970 if hasattr(self, exp_handler_name): 971 sql = getattr(self, exp_handler_name)(expression) 972 elif isinstance(expression, exp.Func): 973 sql = self.function_fallback_sql(expression) 974 elif isinstance(expression, exp.Property): 975 sql = self.property_sql(expression) 976 else: 977 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 978 else: 979 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 980 981 return self.maybe_comment(sql, expression) if self.comments and comment else sql 982 983 def uncache_sql(self, expression: exp.Uncache) -> str: 984 table = self.sql(expression, "this") 985 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 986 return f"UNCACHE TABLE{exists_sql} {table}" 987 988 def cache_sql(self, expression: exp.Cache) -> str: 989 lazy = " LAZY" if expression.args.get("lazy") else "" 990 table = self.sql(expression, "this") 991 options = expression.args.get("options") 992 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 993 sql = self.sql(expression, "expression") 994 sql = f" AS{self.sep()}{sql}" if sql else "" 995 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 996 return self.prepend_ctes(expression, sql) 997 998 def characterset_sql(self, expression: exp.CharacterSet) -> str: 999 if isinstance(expression.parent, exp.Cast): 1000 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1001 default = "DEFAULT " if expression.args.get("default") else "" 1002 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1003 1004 def column_parts(self, expression: exp.Column) -> str: 1005 return ".".join( 1006 self.sql(part) 1007 for part in ( 1008 expression.args.get("catalog"), 1009 expression.args.get("db"), 1010 expression.args.get("table"), 1011 expression.args.get("this"), 1012 ) 1013 if part 1014 ) 1015 1016 def column_sql(self, expression: exp.Column) -> str: 1017 join_mark = " (+)" if expression.args.get("join_mark") else "" 1018 1019 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1020 join_mark = "" 1021 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1022 1023 return f"{self.column_parts(expression)}{join_mark}" 1024 1025 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1026 this = self.sql(expression, "this") 1027 this = f" {this}" if this else "" 1028 position = self.sql(expression, "position") 1029 return f"{position}{this}" 1030 1031 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1032 column = self.sql(expression, "this") 1033 kind = self.sql(expression, "kind") 1034 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1035 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1036 kind = f"{sep}{kind}" if kind else "" 1037 constraints = f" {constraints}" if constraints else "" 1038 position = self.sql(expression, "position") 1039 position = f" {position}" if position else "" 1040 1041 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1042 kind = "" 1043 1044 return f"{exists}{column}{kind}{constraints}{position}" 1045 1046 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1047 this = self.sql(expression, "this") 1048 kind_sql = self.sql(expression, "kind").strip() 1049 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1050 1051 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1052 this = self.sql(expression, "this") 1053 if expression.args.get("not_null"): 1054 persisted = " PERSISTED NOT NULL" 1055 elif expression.args.get("persisted"): 1056 persisted = " PERSISTED" 1057 else: 1058 persisted = "" 1059 1060 return f"AS {this}{persisted}" 1061 1062 def autoincrementcolumnconstraint_sql(self, _) -> str: 1063 return self.token_sql(TokenType.AUTO_INCREMENT) 1064 1065 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1066 if isinstance(expression.this, list): 1067 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1068 else: 1069 this = self.sql(expression, "this") 1070 1071 return f"COMPRESS {this}" 1072 1073 def generatedasidentitycolumnconstraint_sql( 1074 self, expression: exp.GeneratedAsIdentityColumnConstraint 1075 ) -> str: 1076 this = "" 1077 if expression.this is not None: 1078 on_null = " ON NULL" if expression.args.get("on_null") else "" 1079 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1080 1081 start = expression.args.get("start") 1082 start = f"START WITH {start}" if start else "" 1083 increment = expression.args.get("increment") 1084 increment = f" INCREMENT BY {increment}" if increment else "" 1085 minvalue = expression.args.get("minvalue") 1086 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1087 maxvalue = expression.args.get("maxvalue") 1088 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1089 cycle = expression.args.get("cycle") 1090 cycle_sql = "" 1091 1092 if cycle is not None: 1093 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1094 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1095 1096 sequence_opts = "" 1097 if start or increment or cycle_sql: 1098 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1099 sequence_opts = f" ({sequence_opts.strip()})" 1100 1101 expr = self.sql(expression, "expression") 1102 expr = f"({expr})" if expr else "IDENTITY" 1103 1104 return f"GENERATED{this} AS {expr}{sequence_opts}" 1105 1106 def generatedasrowcolumnconstraint_sql( 1107 self, expression: exp.GeneratedAsRowColumnConstraint 1108 ) -> str: 1109 start = "START" if expression.args.get("start") else "END" 1110 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1111 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1112 1113 def periodforsystemtimeconstraint_sql( 1114 self, expression: exp.PeriodForSystemTimeConstraint 1115 ) -> str: 1116 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1117 1118 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1119 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1120 1121 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1122 desc = expression.args.get("desc") 1123 if desc is not None: 1124 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1125 options = self.expressions(expression, key="options", flat=True, sep=" ") 1126 options = f" {options}" if options else "" 1127 return f"PRIMARY KEY{options}" 1128 1129 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1130 this = self.sql(expression, "this") 1131 this = f" {this}" if this else "" 1132 index_type = expression.args.get("index_type") 1133 index_type = f" USING {index_type}" if index_type else "" 1134 on_conflict = self.sql(expression, "on_conflict") 1135 on_conflict = f" {on_conflict}" if on_conflict else "" 1136 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1137 options = self.expressions(expression, key="options", flat=True, sep=" ") 1138 options = f" {options}" if options else "" 1139 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1140 1141 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1142 return self.sql(expression, "this") 1143 1144 def create_sql(self, expression: exp.Create) -> str: 1145 kind = self.sql(expression, "kind") 1146 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1147 properties = expression.args.get("properties") 1148 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1149 1150 this = self.createable_sql(expression, properties_locs) 1151 1152 properties_sql = "" 1153 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1154 exp.Properties.Location.POST_WITH 1155 ): 1156 props_ast = exp.Properties( 1157 expressions=[ 1158 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1159 *properties_locs[exp.Properties.Location.POST_WITH], 1160 ] 1161 ) 1162 props_ast.parent = expression 1163 properties_sql = self.sql(props_ast) 1164 1165 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1166 properties_sql = self.sep() + properties_sql 1167 elif not self.pretty: 1168 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1169 properties_sql = f" {properties_sql}" 1170 1171 begin = " BEGIN" if expression.args.get("begin") else "" 1172 end = " END" if expression.args.get("end") else "" 1173 1174 expression_sql = self.sql(expression, "expression") 1175 if expression_sql: 1176 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1177 1178 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1179 postalias_props_sql = "" 1180 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1181 postalias_props_sql = self.properties( 1182 exp.Properties( 1183 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1184 ), 1185 wrapped=False, 1186 ) 1187 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1188 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1189 1190 postindex_props_sql = "" 1191 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1192 postindex_props_sql = self.properties( 1193 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1194 wrapped=False, 1195 prefix=" ", 1196 ) 1197 1198 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1199 indexes = f" {indexes}" if indexes else "" 1200 index_sql = indexes + postindex_props_sql 1201 1202 replace = " OR REPLACE" if expression.args.get("replace") else "" 1203 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1204 unique = " UNIQUE" if expression.args.get("unique") else "" 1205 1206 clustered = expression.args.get("clustered") 1207 if clustered is None: 1208 clustered_sql = "" 1209 elif clustered: 1210 clustered_sql = " CLUSTERED COLUMNSTORE" 1211 else: 1212 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1213 1214 postcreate_props_sql = "" 1215 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1216 postcreate_props_sql = self.properties( 1217 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1218 sep=" ", 1219 prefix=" ", 1220 wrapped=False, 1221 ) 1222 1223 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1224 1225 postexpression_props_sql = "" 1226 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1227 postexpression_props_sql = self.properties( 1228 exp.Properties( 1229 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1230 ), 1231 sep=" ", 1232 prefix=" ", 1233 wrapped=False, 1234 ) 1235 1236 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1237 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1238 no_schema_binding = ( 1239 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1240 ) 1241 1242 clone = self.sql(expression, "clone") 1243 clone = f" {clone}" if clone else "" 1244 1245 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1246 properties_expression = f"{expression_sql}{properties_sql}" 1247 else: 1248 properties_expression = f"{properties_sql}{expression_sql}" 1249 1250 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1251 return self.prepend_ctes(expression, expression_sql) 1252 1253 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1254 start = self.sql(expression, "start") 1255 start = f"START WITH {start}" if start else "" 1256 increment = self.sql(expression, "increment") 1257 increment = f" INCREMENT BY {increment}" if increment else "" 1258 minvalue = self.sql(expression, "minvalue") 1259 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1260 maxvalue = self.sql(expression, "maxvalue") 1261 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1262 owned = self.sql(expression, "owned") 1263 owned = f" OWNED BY {owned}" if owned else "" 1264 1265 cache = expression.args.get("cache") 1266 if cache is None: 1267 cache_str = "" 1268 elif cache is True: 1269 cache_str = " CACHE" 1270 else: 1271 cache_str = f" CACHE {cache}" 1272 1273 options = self.expressions(expression, key="options", flat=True, sep=" ") 1274 options = f" {options}" if options else "" 1275 1276 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1277 1278 def clone_sql(self, expression: exp.Clone) -> str: 1279 this = self.sql(expression, "this") 1280 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1281 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1282 return f"{shallow}{keyword} {this}" 1283 1284 def describe_sql(self, expression: exp.Describe) -> str: 1285 style = expression.args.get("style") 1286 style = f" {style}" if style else "" 1287 partition = self.sql(expression, "partition") 1288 partition = f" {partition}" if partition else "" 1289 format = self.sql(expression, "format") 1290 format = f" {format}" if format else "" 1291 1292 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1293 1294 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1295 tag = self.sql(expression, "tag") 1296 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1297 1298 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1299 with_ = self.sql(expression, "with") 1300 if with_: 1301 sql = f"{with_}{self.sep()}{sql}" 1302 return sql 1303 1304 def with_sql(self, expression: exp.With) -> str: 1305 sql = self.expressions(expression, flat=True) 1306 recursive = ( 1307 "RECURSIVE " 1308 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1309 else "" 1310 ) 1311 search = self.sql(expression, "search") 1312 search = f" {search}" if search else "" 1313 1314 return f"WITH {recursive}{sql}{search}" 1315 1316 def cte_sql(self, expression: exp.CTE) -> str: 1317 alias = expression.args.get("alias") 1318 if alias: 1319 alias.add_comments(expression.pop_comments()) 1320 1321 alias_sql = self.sql(expression, "alias") 1322 1323 materialized = expression.args.get("materialized") 1324 if materialized is False: 1325 materialized = "NOT MATERIALIZED " 1326 elif materialized: 1327 materialized = "MATERIALIZED " 1328 1329 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1330 1331 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1332 alias = self.sql(expression, "this") 1333 columns = self.expressions(expression, key="columns", flat=True) 1334 columns = f"({columns})" if columns else "" 1335 1336 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1337 columns = "" 1338 self.unsupported("Named columns are not supported in table alias.") 1339 1340 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1341 alias = self._next_name() 1342 1343 return f"{alias}{columns}" 1344 1345 def bitstring_sql(self, expression: exp.BitString) -> str: 1346 this = self.sql(expression, "this") 1347 if self.dialect.BIT_START: 1348 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1349 return f"{int(this, 2)}" 1350 1351 def hexstring_sql( 1352 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1353 ) -> str: 1354 this = self.sql(expression, "this") 1355 is_integer_type = expression.args.get("is_integer") 1356 1357 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1358 not self.dialect.HEX_START and not binary_function_repr 1359 ): 1360 # Integer representation will be returned if: 1361 # - The read dialect treats the hex value as integer literal but not the write 1362 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1363 return f"{int(this, 16)}" 1364 1365 if not is_integer_type: 1366 # Read dialect treats the hex value as BINARY/BLOB 1367 if binary_function_repr: 1368 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1369 return self.func(binary_function_repr, exp.Literal.string(this)) 1370 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1371 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1372 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1373 1374 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1375 1376 def bytestring_sql(self, expression: exp.ByteString) -> str: 1377 this = self.sql(expression, "this") 1378 if self.dialect.BYTE_START: 1379 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1380 return this 1381 1382 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1383 this = self.sql(expression, "this") 1384 escape = expression.args.get("escape") 1385 1386 if self.dialect.UNICODE_START: 1387 escape_substitute = r"\\\1" 1388 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1389 else: 1390 escape_substitute = r"\\u\1" 1391 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1392 1393 if escape: 1394 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1395 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1396 else: 1397 escape_pattern = ESCAPED_UNICODE_RE 1398 escape_sql = "" 1399 1400 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1401 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1402 1403 return f"{left_quote}{this}{right_quote}{escape_sql}" 1404 1405 def rawstring_sql(self, expression: exp.RawString) -> str: 1406 string = expression.this 1407 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1408 string = string.replace("\\", "\\\\") 1409 1410 string = self.escape_str(string, escape_backslash=False) 1411 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1412 1413 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1414 this = self.sql(expression, "this") 1415 specifier = self.sql(expression, "expression") 1416 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1417 return f"{this}{specifier}" 1418 1419 def datatype_sql(self, expression: exp.DataType) -> str: 1420 nested = "" 1421 values = "" 1422 interior = self.expressions(expression, flat=True) 1423 1424 type_value = expression.this 1425 if type_value in self.UNSUPPORTED_TYPES: 1426 self.unsupported( 1427 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1428 ) 1429 1430 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1431 type_sql = self.sql(expression, "kind") 1432 else: 1433 type_sql = ( 1434 self.TYPE_MAPPING.get(type_value, type_value.value) 1435 if isinstance(type_value, exp.DataType.Type) 1436 else type_value 1437 ) 1438 1439 if interior: 1440 if expression.args.get("nested"): 1441 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1442 if expression.args.get("values") is not None: 1443 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1444 values = self.expressions(expression, key="values", flat=True) 1445 values = f"{delimiters[0]}{values}{delimiters[1]}" 1446 elif type_value == exp.DataType.Type.INTERVAL: 1447 nested = f" {interior}" 1448 else: 1449 nested = f"({interior})" 1450 1451 type_sql = f"{type_sql}{nested}{values}" 1452 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1453 exp.DataType.Type.TIMETZ, 1454 exp.DataType.Type.TIMESTAMPTZ, 1455 ): 1456 type_sql = f"{type_sql} WITH TIME ZONE" 1457 1458 return type_sql 1459 1460 def directory_sql(self, expression: exp.Directory) -> str: 1461 local = "LOCAL " if expression.args.get("local") else "" 1462 row_format = self.sql(expression, "row_format") 1463 row_format = f" {row_format}" if row_format else "" 1464 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1465 1466 def delete_sql(self, expression: exp.Delete) -> str: 1467 this = self.sql(expression, "this") 1468 this = f" FROM {this}" if this else "" 1469 using = self.sql(expression, "using") 1470 using = f" USING {using}" if using else "" 1471 cluster = self.sql(expression, "cluster") 1472 cluster = f" {cluster}" if cluster else "" 1473 where = self.sql(expression, "where") 1474 returning = self.sql(expression, "returning") 1475 limit = self.sql(expression, "limit") 1476 tables = self.expressions(expression, key="tables") 1477 tables = f" {tables}" if tables else "" 1478 if self.RETURNING_END: 1479 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1480 else: 1481 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1482 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1483 1484 def drop_sql(self, expression: exp.Drop) -> str: 1485 this = self.sql(expression, "this") 1486 expressions = self.expressions(expression, flat=True) 1487 expressions = f" ({expressions})" if expressions else "" 1488 kind = expression.args["kind"] 1489 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1490 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1491 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1492 on_cluster = self.sql(expression, "cluster") 1493 on_cluster = f" {on_cluster}" if on_cluster else "" 1494 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1495 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1496 cascade = " CASCADE" if expression.args.get("cascade") else "" 1497 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1498 purge = " PURGE" if expression.args.get("purge") else "" 1499 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1500 1501 def set_operation(self, expression: exp.SetOperation) -> str: 1502 op_type = type(expression) 1503 op_name = op_type.key.upper() 1504 1505 distinct = expression.args.get("distinct") 1506 if ( 1507 distinct is False 1508 and op_type in (exp.Except, exp.Intersect) 1509 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1510 ): 1511 self.unsupported(f"{op_name} ALL is not supported") 1512 1513 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1514 1515 if distinct is None: 1516 distinct = default_distinct 1517 if distinct is None: 1518 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1519 1520 if distinct is default_distinct: 1521 distinct_or_all = "" 1522 else: 1523 distinct_or_all = " DISTINCT" if distinct else " ALL" 1524 1525 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1526 side_kind = f"{side_kind} " if side_kind else "" 1527 1528 by_name = " BY NAME" if expression.args.get("by_name") else "" 1529 on = self.expressions(expression, key="on", flat=True) 1530 on = f" ON ({on})" if on else "" 1531 1532 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1533 1534 def set_operations(self, expression: exp.SetOperation) -> str: 1535 if not self.SET_OP_MODIFIERS: 1536 limit = expression.args.get("limit") 1537 order = expression.args.get("order") 1538 1539 if limit or order: 1540 select = self._move_ctes_to_top_level( 1541 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1542 ) 1543 1544 if limit: 1545 select = select.limit(limit.pop(), copy=False) 1546 if order: 1547 select = select.order_by(order.pop(), copy=False) 1548 return self.sql(select) 1549 1550 sqls: t.List[str] = [] 1551 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1552 1553 while stack: 1554 node = stack.pop() 1555 1556 if isinstance(node, exp.SetOperation): 1557 stack.append(node.expression) 1558 stack.append( 1559 self.maybe_comment( 1560 self.set_operation(node), comments=node.comments, separated=True 1561 ) 1562 ) 1563 stack.append(node.this) 1564 else: 1565 sqls.append(self.sql(node)) 1566 1567 this = self.sep().join(sqls) 1568 this = self.query_modifiers(expression, this) 1569 return self.prepend_ctes(expression, this) 1570 1571 def fetch_sql(self, expression: exp.Fetch) -> str: 1572 direction = expression.args.get("direction") 1573 direction = f" {direction}" if direction else "" 1574 count = self.sql(expression, "count") 1575 count = f" {count}" if count else "" 1576 limit_options = self.sql(expression, "limit_options") 1577 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1578 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1579 1580 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1581 percent = " PERCENT" if expression.args.get("percent") else "" 1582 rows = " ROWS" if expression.args.get("rows") else "" 1583 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1584 if not with_ties and rows: 1585 with_ties = " ONLY" 1586 return f"{percent}{rows}{with_ties}" 1587 1588 def filter_sql(self, expression: exp.Filter) -> str: 1589 if self.AGGREGATE_FILTER_SUPPORTED: 1590 this = self.sql(expression, "this") 1591 where = self.sql(expression, "expression").strip() 1592 return f"{this} FILTER({where})" 1593 1594 agg = expression.this 1595 agg_arg = agg.this 1596 cond = expression.expression.this 1597 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1598 return self.sql(agg) 1599 1600 def hint_sql(self, expression: exp.Hint) -> str: 1601 if not self.QUERY_HINTS: 1602 self.unsupported("Hints are not supported") 1603 return "" 1604 1605 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1606 1607 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1608 using = self.sql(expression, "using") 1609 using = f" USING {using}" if using else "" 1610 columns = self.expressions(expression, key="columns", flat=True) 1611 columns = f"({columns})" if columns else "" 1612 partition_by = self.expressions(expression, key="partition_by", flat=True) 1613 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1614 where = self.sql(expression, "where") 1615 include = self.expressions(expression, key="include", flat=True) 1616 if include: 1617 include = f" INCLUDE ({include})" 1618 with_storage = self.expressions(expression, key="with_storage", flat=True) 1619 with_storage = f" WITH ({with_storage})" if with_storage else "" 1620 tablespace = self.sql(expression, "tablespace") 1621 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1622 on = self.sql(expression, "on") 1623 on = f" ON {on}" if on else "" 1624 1625 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1626 1627 def index_sql(self, expression: exp.Index) -> str: 1628 unique = "UNIQUE " if expression.args.get("unique") else "" 1629 primary = "PRIMARY " if expression.args.get("primary") else "" 1630 amp = "AMP " if expression.args.get("amp") else "" 1631 name = self.sql(expression, "this") 1632 name = f"{name} " if name else "" 1633 table = self.sql(expression, "table") 1634 table = f"{self.INDEX_ON} {table}" if table else "" 1635 1636 index = "INDEX " if not table else "" 1637 1638 params = self.sql(expression, "params") 1639 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1640 1641 def identifier_sql(self, expression: exp.Identifier) -> str: 1642 text = expression.name 1643 lower = text.lower() 1644 text = lower if self.normalize and not expression.quoted else text 1645 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1646 if ( 1647 expression.quoted 1648 or self.dialect.can_identify(text, self.identify) 1649 or lower in self.RESERVED_KEYWORDS 1650 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1651 ): 1652 text = f"{self._identifier_start}{text}{self._identifier_end}" 1653 return text 1654 1655 def hex_sql(self, expression: exp.Hex) -> str: 1656 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1657 if self.dialect.HEX_LOWERCASE: 1658 text = self.func("LOWER", text) 1659 1660 return text 1661 1662 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1663 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1664 if not self.dialect.HEX_LOWERCASE: 1665 text = self.func("LOWER", text) 1666 return text 1667 1668 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1669 input_format = self.sql(expression, "input_format") 1670 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1671 output_format = self.sql(expression, "output_format") 1672 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1673 return self.sep().join((input_format, output_format)) 1674 1675 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1676 string = self.sql(exp.Literal.string(expression.name)) 1677 return f"{prefix}{string}" 1678 1679 def partition_sql(self, expression: exp.Partition) -> str: 1680 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1681 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1682 1683 def properties_sql(self, expression: exp.Properties) -> str: 1684 root_properties = [] 1685 with_properties = [] 1686 1687 for p in expression.expressions: 1688 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1689 if p_loc == exp.Properties.Location.POST_WITH: 1690 with_properties.append(p) 1691 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1692 root_properties.append(p) 1693 1694 root_props_ast = exp.Properties(expressions=root_properties) 1695 root_props_ast.parent = expression.parent 1696 1697 with_props_ast = exp.Properties(expressions=with_properties) 1698 with_props_ast.parent = expression.parent 1699 1700 root_props = self.root_properties(root_props_ast) 1701 with_props = self.with_properties(with_props_ast) 1702 1703 if root_props and with_props and not self.pretty: 1704 with_props = " " + with_props 1705 1706 return root_props + with_props 1707 1708 def root_properties(self, properties: exp.Properties) -> str: 1709 if properties.expressions: 1710 return self.expressions(properties, indent=False, sep=" ") 1711 return "" 1712 1713 def properties( 1714 self, 1715 properties: exp.Properties, 1716 prefix: str = "", 1717 sep: str = ", ", 1718 suffix: str = "", 1719 wrapped: bool = True, 1720 ) -> str: 1721 if properties.expressions: 1722 expressions = self.expressions(properties, sep=sep, indent=False) 1723 if expressions: 1724 expressions = self.wrap(expressions) if wrapped else expressions 1725 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1726 return "" 1727 1728 def with_properties(self, properties: exp.Properties) -> str: 1729 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1730 1731 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1732 properties_locs = defaultdict(list) 1733 for p in properties.expressions: 1734 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1735 if p_loc != exp.Properties.Location.UNSUPPORTED: 1736 properties_locs[p_loc].append(p) 1737 else: 1738 self.unsupported(f"Unsupported property {p.key}") 1739 1740 return properties_locs 1741 1742 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1743 if isinstance(expression.this, exp.Dot): 1744 return self.sql(expression, "this") 1745 return f"'{expression.name}'" if string_key else expression.name 1746 1747 def property_sql(self, expression: exp.Property) -> str: 1748 property_cls = expression.__class__ 1749 if property_cls == exp.Property: 1750 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1751 1752 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1753 if not property_name: 1754 self.unsupported(f"Unsupported property {expression.key}") 1755 1756 return f"{property_name}={self.sql(expression, 'this')}" 1757 1758 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1759 if self.SUPPORTS_CREATE_TABLE_LIKE: 1760 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1761 options = f" {options}" if options else "" 1762 1763 like = f"LIKE {self.sql(expression, 'this')}{options}" 1764 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1765 like = f"({like})" 1766 1767 return like 1768 1769 if expression.expressions: 1770 self.unsupported("Transpilation of LIKE property options is unsupported") 1771 1772 select = exp.select("*").from_(expression.this).limit(0) 1773 return f"AS {self.sql(select)}" 1774 1775 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1776 no = "NO " if expression.args.get("no") else "" 1777 protection = " PROTECTION" if expression.args.get("protection") else "" 1778 return f"{no}FALLBACK{protection}" 1779 1780 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1781 no = "NO " if expression.args.get("no") else "" 1782 local = expression.args.get("local") 1783 local = f"{local} " if local else "" 1784 dual = "DUAL " if expression.args.get("dual") else "" 1785 before = "BEFORE " if expression.args.get("before") else "" 1786 after = "AFTER " if expression.args.get("after") else "" 1787 return f"{no}{local}{dual}{before}{after}JOURNAL" 1788 1789 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1790 freespace = self.sql(expression, "this") 1791 percent = " PERCENT" if expression.args.get("percent") else "" 1792 return f"FREESPACE={freespace}{percent}" 1793 1794 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1795 if expression.args.get("default"): 1796 property = "DEFAULT" 1797 elif expression.args.get("on"): 1798 property = "ON" 1799 else: 1800 property = "OFF" 1801 return f"CHECKSUM={property}" 1802 1803 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1804 if expression.args.get("no"): 1805 return "NO MERGEBLOCKRATIO" 1806 if expression.args.get("default"): 1807 return "DEFAULT MERGEBLOCKRATIO" 1808 1809 percent = " PERCENT" if expression.args.get("percent") else "" 1810 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1811 1812 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1813 default = expression.args.get("default") 1814 minimum = expression.args.get("minimum") 1815 maximum = expression.args.get("maximum") 1816 if default or minimum or maximum: 1817 if default: 1818 prop = "DEFAULT" 1819 elif minimum: 1820 prop = "MINIMUM" 1821 else: 1822 prop = "MAXIMUM" 1823 return f"{prop} DATABLOCKSIZE" 1824 units = expression.args.get("units") 1825 units = f" {units}" if units else "" 1826 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1827 1828 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1829 autotemp = expression.args.get("autotemp") 1830 always = expression.args.get("always") 1831 default = expression.args.get("default") 1832 manual = expression.args.get("manual") 1833 never = expression.args.get("never") 1834 1835 if autotemp is not None: 1836 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1837 elif always: 1838 prop = "ALWAYS" 1839 elif default: 1840 prop = "DEFAULT" 1841 elif manual: 1842 prop = "MANUAL" 1843 elif never: 1844 prop = "NEVER" 1845 return f"BLOCKCOMPRESSION={prop}" 1846 1847 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1848 no = expression.args.get("no") 1849 no = " NO" if no else "" 1850 concurrent = expression.args.get("concurrent") 1851 concurrent = " CONCURRENT" if concurrent else "" 1852 target = self.sql(expression, "target") 1853 target = f" {target}" if target else "" 1854 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1855 1856 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1857 if isinstance(expression.this, list): 1858 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1859 if expression.this: 1860 modulus = self.sql(expression, "this") 1861 remainder = self.sql(expression, "expression") 1862 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1863 1864 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1865 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1866 return f"FROM ({from_expressions}) TO ({to_expressions})" 1867 1868 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1869 this = self.sql(expression, "this") 1870 1871 for_values_or_default = expression.expression 1872 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1873 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1874 else: 1875 for_values_or_default = " DEFAULT" 1876 1877 return f"PARTITION OF {this}{for_values_or_default}" 1878 1879 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1880 kind = expression.args.get("kind") 1881 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1882 for_or_in = expression.args.get("for_or_in") 1883 for_or_in = f" {for_or_in}" if for_or_in else "" 1884 lock_type = expression.args.get("lock_type") 1885 override = " OVERRIDE" if expression.args.get("override") else "" 1886 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1887 1888 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1889 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1890 statistics = expression.args.get("statistics") 1891 statistics_sql = "" 1892 if statistics is not None: 1893 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1894 return f"{data_sql}{statistics_sql}" 1895 1896 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1897 this = self.sql(expression, "this") 1898 this = f"HISTORY_TABLE={this}" if this else "" 1899 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1900 data_consistency = ( 1901 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1902 ) 1903 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1904 retention_period = ( 1905 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1906 ) 1907 1908 if this: 1909 on_sql = self.func("ON", this, data_consistency, retention_period) 1910 else: 1911 on_sql = "ON" if expression.args.get("on") else "OFF" 1912 1913 sql = f"SYSTEM_VERSIONING={on_sql}" 1914 1915 return f"WITH({sql})" if expression.args.get("with") else sql 1916 1917 def insert_sql(self, expression: exp.Insert) -> str: 1918 hint = self.sql(expression, "hint") 1919 overwrite = expression.args.get("overwrite") 1920 1921 if isinstance(expression.this, exp.Directory): 1922 this = " OVERWRITE" if overwrite else " INTO" 1923 else: 1924 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1925 1926 stored = self.sql(expression, "stored") 1927 stored = f" {stored}" if stored else "" 1928 alternative = expression.args.get("alternative") 1929 alternative = f" OR {alternative}" if alternative else "" 1930 ignore = " IGNORE" if expression.args.get("ignore") else "" 1931 is_function = expression.args.get("is_function") 1932 if is_function: 1933 this = f"{this} FUNCTION" 1934 this = f"{this} {self.sql(expression, 'this')}" 1935 1936 exists = " IF EXISTS" if expression.args.get("exists") else "" 1937 where = self.sql(expression, "where") 1938 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1939 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1940 on_conflict = self.sql(expression, "conflict") 1941 on_conflict = f" {on_conflict}" if on_conflict else "" 1942 by_name = " BY NAME" if expression.args.get("by_name") else "" 1943 returning = self.sql(expression, "returning") 1944 1945 if self.RETURNING_END: 1946 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1947 else: 1948 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1949 1950 partition_by = self.sql(expression, "partition") 1951 partition_by = f" {partition_by}" if partition_by else "" 1952 settings = self.sql(expression, "settings") 1953 settings = f" {settings}" if settings else "" 1954 1955 source = self.sql(expression, "source") 1956 source = f"TABLE {source}" if source else "" 1957 1958 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1959 return self.prepend_ctes(expression, sql) 1960 1961 def introducer_sql(self, expression: exp.Introducer) -> str: 1962 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1963 1964 def kill_sql(self, expression: exp.Kill) -> str: 1965 kind = self.sql(expression, "kind") 1966 kind = f" {kind}" if kind else "" 1967 this = self.sql(expression, "this") 1968 this = f" {this}" if this else "" 1969 return f"KILL{kind}{this}" 1970 1971 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1972 return expression.name 1973 1974 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1975 return expression.name 1976 1977 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1978 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1979 1980 constraint = self.sql(expression, "constraint") 1981 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1982 1983 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1984 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1985 action = self.sql(expression, "action") 1986 1987 expressions = self.expressions(expression, flat=True) 1988 if expressions: 1989 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1990 expressions = f" {set_keyword}{expressions}" 1991 1992 where = self.sql(expression, "where") 1993 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1994 1995 def returning_sql(self, expression: exp.Returning) -> str: 1996 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1997 1998 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1999 fields = self.sql(expression, "fields") 2000 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2001 escaped = self.sql(expression, "escaped") 2002 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2003 items = self.sql(expression, "collection_items") 2004 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2005 keys = self.sql(expression, "map_keys") 2006 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2007 lines = self.sql(expression, "lines") 2008 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2009 null = self.sql(expression, "null") 2010 null = f" NULL DEFINED AS {null}" if null else "" 2011 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2012 2013 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2014 return f"WITH ({self.expressions(expression, flat=True)})" 2015 2016 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2017 this = f"{self.sql(expression, 'this')} INDEX" 2018 target = self.sql(expression, "target") 2019 target = f" FOR {target}" if target else "" 2020 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2021 2022 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2023 this = self.sql(expression, "this") 2024 kind = self.sql(expression, "kind") 2025 expr = self.sql(expression, "expression") 2026 return f"{this} ({kind} => {expr})" 2027 2028 def table_parts(self, expression: exp.Table) -> str: 2029 return ".".join( 2030 self.sql(part) 2031 for part in ( 2032 expression.args.get("catalog"), 2033 expression.args.get("db"), 2034 expression.args.get("this"), 2035 ) 2036 if part is not None 2037 ) 2038 2039 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2040 table = self.table_parts(expression) 2041 only = "ONLY " if expression.args.get("only") else "" 2042 partition = self.sql(expression, "partition") 2043 partition = f" {partition}" if partition else "" 2044 version = self.sql(expression, "version") 2045 version = f" {version}" if version else "" 2046 alias = self.sql(expression, "alias") 2047 alias = f"{sep}{alias}" if alias else "" 2048 2049 sample = self.sql(expression, "sample") 2050 if self.dialect.ALIAS_POST_TABLESAMPLE: 2051 sample_pre_alias = sample 2052 sample_post_alias = "" 2053 else: 2054 sample_pre_alias = "" 2055 sample_post_alias = sample 2056 2057 hints = self.expressions(expression, key="hints", sep=" ") 2058 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2059 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2060 joins = self.indent( 2061 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2062 ) 2063 laterals = self.expressions(expression, key="laterals", sep="") 2064 2065 file_format = self.sql(expression, "format") 2066 if file_format: 2067 pattern = self.sql(expression, "pattern") 2068 pattern = f", PATTERN => {pattern}" if pattern else "" 2069 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2070 2071 ordinality = expression.args.get("ordinality") or "" 2072 if ordinality: 2073 ordinality = f" WITH ORDINALITY{alias}" 2074 alias = "" 2075 2076 when = self.sql(expression, "when") 2077 if when: 2078 table = f"{table} {when}" 2079 2080 changes = self.sql(expression, "changes") 2081 changes = f" {changes}" if changes else "" 2082 2083 rows_from = self.expressions(expression, key="rows_from") 2084 if rows_from: 2085 table = f"ROWS FROM {self.wrap(rows_from)}" 2086 2087 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2088 2089 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2090 table = self.func("TABLE", expression.this) 2091 alias = self.sql(expression, "alias") 2092 alias = f" AS {alias}" if alias else "" 2093 sample = self.sql(expression, "sample") 2094 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2095 joins = self.indent( 2096 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2097 ) 2098 return f"{table}{alias}{pivots}{sample}{joins}" 2099 2100 def tablesample_sql( 2101 self, 2102 expression: exp.TableSample, 2103 tablesample_keyword: t.Optional[str] = None, 2104 ) -> str: 2105 method = self.sql(expression, "method") 2106 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2107 numerator = self.sql(expression, "bucket_numerator") 2108 denominator = self.sql(expression, "bucket_denominator") 2109 field = self.sql(expression, "bucket_field") 2110 field = f" ON {field}" if field else "" 2111 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2112 seed = self.sql(expression, "seed") 2113 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2114 2115 size = self.sql(expression, "size") 2116 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2117 size = f"{size} ROWS" 2118 2119 percent = self.sql(expression, "percent") 2120 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2121 percent = f"{percent} PERCENT" 2122 2123 expr = f"{bucket}{percent}{size}" 2124 if self.TABLESAMPLE_REQUIRES_PARENS: 2125 expr = f"({expr})" 2126 2127 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2128 2129 def pivot_sql(self, expression: exp.Pivot) -> str: 2130 expressions = self.expressions(expression, flat=True) 2131 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2132 2133 group = self.sql(expression, "group") 2134 2135 if expression.this: 2136 this = self.sql(expression, "this") 2137 if not expressions: 2138 return f"UNPIVOT {this}" 2139 2140 on = f"{self.seg('ON')} {expressions}" 2141 into = self.sql(expression, "into") 2142 into = f"{self.seg('INTO')} {into}" if into else "" 2143 using = self.expressions(expression, key="using", flat=True) 2144 using = f"{self.seg('USING')} {using}" if using else "" 2145 return f"{direction} {this}{on}{into}{using}{group}" 2146 2147 alias = self.sql(expression, "alias") 2148 alias = f" AS {alias}" if alias else "" 2149 2150 fields = self.expressions( 2151 expression, 2152 "fields", 2153 sep=" ", 2154 dynamic=True, 2155 new_line=True, 2156 skip_first=True, 2157 skip_last=True, 2158 ) 2159 2160 include_nulls = expression.args.get("include_nulls") 2161 if include_nulls is not None: 2162 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2163 else: 2164 nulls = "" 2165 2166 default_on_null = self.sql(expression, "default_on_null") 2167 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2168 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2169 2170 def version_sql(self, expression: exp.Version) -> str: 2171 this = f"FOR {expression.name}" 2172 kind = expression.text("kind") 2173 expr = self.sql(expression, "expression") 2174 return f"{this} {kind} {expr}" 2175 2176 def tuple_sql(self, expression: exp.Tuple) -> str: 2177 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2178 2179 def update_sql(self, expression: exp.Update) -> str: 2180 this = self.sql(expression, "this") 2181 set_sql = self.expressions(expression, flat=True) 2182 from_sql = self.sql(expression, "from") 2183 where_sql = self.sql(expression, "where") 2184 returning = self.sql(expression, "returning") 2185 order = self.sql(expression, "order") 2186 limit = self.sql(expression, "limit") 2187 if self.RETURNING_END: 2188 expression_sql = f"{from_sql}{where_sql}{returning}" 2189 else: 2190 expression_sql = f"{returning}{from_sql}{where_sql}" 2191 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2192 return self.prepend_ctes(expression, sql) 2193 2194 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2195 values_as_table = values_as_table and self.VALUES_AS_TABLE 2196 2197 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2198 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2199 args = self.expressions(expression) 2200 alias = self.sql(expression, "alias") 2201 values = f"VALUES{self.seg('')}{args}" 2202 values = ( 2203 f"({values})" 2204 if self.WRAP_DERIVED_VALUES 2205 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2206 else values 2207 ) 2208 return f"{values} AS {alias}" if alias else values 2209 2210 # Converts `VALUES...` expression into a series of select unions. 2211 alias_node = expression.args.get("alias") 2212 column_names = alias_node and alias_node.columns 2213 2214 selects: t.List[exp.Query] = [] 2215 2216 for i, tup in enumerate(expression.expressions): 2217 row = tup.expressions 2218 2219 if i == 0 and column_names: 2220 row = [ 2221 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2222 ] 2223 2224 selects.append(exp.Select(expressions=row)) 2225 2226 if self.pretty: 2227 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2228 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2229 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2230 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2231 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2232 2233 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2234 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2235 return f"({unions}){alias}" 2236 2237 def var_sql(self, expression: exp.Var) -> str: 2238 return self.sql(expression, "this") 2239 2240 @unsupported_args("expressions") 2241 def into_sql(self, expression: exp.Into) -> str: 2242 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2243 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2244 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2245 2246 def from_sql(self, expression: exp.From) -> str: 2247 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2248 2249 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2250 grouping_sets = self.expressions(expression, indent=False) 2251 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2252 2253 def rollup_sql(self, expression: exp.Rollup) -> str: 2254 expressions = self.expressions(expression, indent=False) 2255 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2256 2257 def cube_sql(self, expression: exp.Cube) -> str: 2258 expressions = self.expressions(expression, indent=False) 2259 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2260 2261 def group_sql(self, expression: exp.Group) -> str: 2262 group_by_all = expression.args.get("all") 2263 if group_by_all is True: 2264 modifier = " ALL" 2265 elif group_by_all is False: 2266 modifier = " DISTINCT" 2267 else: 2268 modifier = "" 2269 2270 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2271 2272 grouping_sets = self.expressions(expression, key="grouping_sets") 2273 cube = self.expressions(expression, key="cube") 2274 rollup = self.expressions(expression, key="rollup") 2275 2276 groupings = csv( 2277 self.seg(grouping_sets) if grouping_sets else "", 2278 self.seg(cube) if cube else "", 2279 self.seg(rollup) if rollup else "", 2280 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2281 sep=self.GROUPINGS_SEP, 2282 ) 2283 2284 if ( 2285 expression.expressions 2286 and groupings 2287 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2288 ): 2289 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2290 2291 return f"{group_by}{groupings}" 2292 2293 def having_sql(self, expression: exp.Having) -> str: 2294 this = self.indent(self.sql(expression, "this")) 2295 return f"{self.seg('HAVING')}{self.sep()}{this}" 2296 2297 def connect_sql(self, expression: exp.Connect) -> str: 2298 start = self.sql(expression, "start") 2299 start = self.seg(f"START WITH {start}") if start else "" 2300 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2301 connect = self.sql(expression, "connect") 2302 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2303 return start + connect 2304 2305 def prior_sql(self, expression: exp.Prior) -> str: 2306 return f"PRIOR {self.sql(expression, 'this')}" 2307 2308 def join_sql(self, expression: exp.Join) -> str: 2309 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2310 side = None 2311 else: 2312 side = expression.side 2313 2314 op_sql = " ".join( 2315 op 2316 for op in ( 2317 expression.method, 2318 "GLOBAL" if expression.args.get("global") else None, 2319 side, 2320 expression.kind, 2321 expression.hint if self.JOIN_HINTS else None, 2322 ) 2323 if op 2324 ) 2325 match_cond = self.sql(expression, "match_condition") 2326 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2327 on_sql = self.sql(expression, "on") 2328 using = expression.args.get("using") 2329 2330 if not on_sql and using: 2331 on_sql = csv(*(self.sql(column) for column in using)) 2332 2333 this = expression.this 2334 this_sql = self.sql(this) 2335 2336 exprs = self.expressions(expression) 2337 if exprs: 2338 this_sql = f"{this_sql},{self.seg(exprs)}" 2339 2340 if on_sql: 2341 on_sql = self.indent(on_sql, skip_first=True) 2342 space = self.seg(" " * self.pad) if self.pretty else " " 2343 if using: 2344 on_sql = f"{space}USING ({on_sql})" 2345 else: 2346 on_sql = f"{space}ON {on_sql}" 2347 elif not op_sql: 2348 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2349 return f" {this_sql}" 2350 2351 return f", {this_sql}" 2352 2353 if op_sql != "STRAIGHT_JOIN": 2354 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2355 2356 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2357 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2358 2359 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2360 args = self.expressions(expression, flat=True) 2361 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2362 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2363 2364 def lateral_op(self, expression: exp.Lateral) -> str: 2365 cross_apply = expression.args.get("cross_apply") 2366 2367 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2368 if cross_apply is True: 2369 op = "INNER JOIN " 2370 elif cross_apply is False: 2371 op = "LEFT JOIN " 2372 else: 2373 op = "" 2374 2375 return f"{op}LATERAL" 2376 2377 def lateral_sql(self, expression: exp.Lateral) -> str: 2378 this = self.sql(expression, "this") 2379 2380 if expression.args.get("view"): 2381 alias = expression.args["alias"] 2382 columns = self.expressions(alias, key="columns", flat=True) 2383 table = f" {alias.name}" if alias.name else "" 2384 columns = f" AS {columns}" if columns else "" 2385 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2386 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2387 2388 alias = self.sql(expression, "alias") 2389 alias = f" AS {alias}" if alias else "" 2390 2391 ordinality = expression.args.get("ordinality") or "" 2392 if ordinality: 2393 ordinality = f" WITH ORDINALITY{alias}" 2394 alias = "" 2395 2396 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2397 2398 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2399 this = self.sql(expression, "this") 2400 2401 args = [ 2402 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2403 for e in (expression.args.get(k) for k in ("offset", "expression")) 2404 if e 2405 ] 2406 2407 args_sql = ", ".join(self.sql(e) for e in args) 2408 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2409 expressions = self.expressions(expression, flat=True) 2410 limit_options = self.sql(expression, "limit_options") 2411 expressions = f" BY {expressions}" if expressions else "" 2412 2413 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2414 2415 def offset_sql(self, expression: exp.Offset) -> str: 2416 this = self.sql(expression, "this") 2417 value = expression.expression 2418 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2419 expressions = self.expressions(expression, flat=True) 2420 expressions = f" BY {expressions}" if expressions else "" 2421 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2422 2423 def setitem_sql(self, expression: exp.SetItem) -> str: 2424 kind = self.sql(expression, "kind") 2425 kind = f"{kind} " if kind else "" 2426 this = self.sql(expression, "this") 2427 expressions = self.expressions(expression) 2428 collate = self.sql(expression, "collate") 2429 collate = f" COLLATE {collate}" if collate else "" 2430 global_ = "GLOBAL " if expression.args.get("global") else "" 2431 return f"{global_}{kind}{this}{expressions}{collate}" 2432 2433 def set_sql(self, expression: exp.Set) -> str: 2434 expressions = f" {self.expressions(expression, flat=True)}" 2435 tag = " TAG" if expression.args.get("tag") else "" 2436 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2437 2438 def queryband_sql(self, expression: exp.QueryBand) -> str: 2439 this = self.sql(expression, "this") 2440 update = " UPDATE" if expression.args.get("update") else "" 2441 scope = self.sql(expression, "scope") 2442 scope = f" FOR {scope}" if scope else "" 2443 2444 return f"QUERY_BAND = {this}{update}{scope}" 2445 2446 def pragma_sql(self, expression: exp.Pragma) -> str: 2447 return f"PRAGMA {self.sql(expression, 'this')}" 2448 2449 def lock_sql(self, expression: exp.Lock) -> str: 2450 if not self.LOCKING_READS_SUPPORTED: 2451 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2452 return "" 2453 2454 update = expression.args["update"] 2455 key = expression.args.get("key") 2456 if update: 2457 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2458 else: 2459 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2460 expressions = self.expressions(expression, flat=True) 2461 expressions = f" OF {expressions}" if expressions else "" 2462 wait = expression.args.get("wait") 2463 2464 if wait is not None: 2465 if isinstance(wait, exp.Literal): 2466 wait = f" WAIT {self.sql(wait)}" 2467 else: 2468 wait = " NOWAIT" if wait else " SKIP LOCKED" 2469 2470 return f"{lock_type}{expressions}{wait or ''}" 2471 2472 def literal_sql(self, expression: exp.Literal) -> str: 2473 text = expression.this or "" 2474 if expression.is_string: 2475 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2476 return text 2477 2478 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2479 if self.dialect.ESCAPED_SEQUENCES: 2480 to_escaped = self.dialect.ESCAPED_SEQUENCES 2481 text = "".join( 2482 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2483 ) 2484 2485 return self._replace_line_breaks(text).replace( 2486 self.dialect.QUOTE_END, self._escaped_quote_end 2487 ) 2488 2489 def loaddata_sql(self, expression: exp.LoadData) -> str: 2490 local = " LOCAL" if expression.args.get("local") else "" 2491 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2492 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2493 this = f" INTO TABLE {self.sql(expression, 'this')}" 2494 partition = self.sql(expression, "partition") 2495 partition = f" {partition}" if partition else "" 2496 input_format = self.sql(expression, "input_format") 2497 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2498 serde = self.sql(expression, "serde") 2499 serde = f" SERDE {serde}" if serde else "" 2500 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2501 2502 def null_sql(self, *_) -> str: 2503 return "NULL" 2504 2505 def boolean_sql(self, expression: exp.Boolean) -> str: 2506 return "TRUE" if expression.this else "FALSE" 2507 2508 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2509 this = self.sql(expression, "this") 2510 this = f"{this} " if this else this 2511 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2512 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2513 2514 def withfill_sql(self, expression: exp.WithFill) -> str: 2515 from_sql = self.sql(expression, "from") 2516 from_sql = f" FROM {from_sql}" if from_sql else "" 2517 to_sql = self.sql(expression, "to") 2518 to_sql = f" TO {to_sql}" if to_sql else "" 2519 step_sql = self.sql(expression, "step") 2520 step_sql = f" STEP {step_sql}" if step_sql else "" 2521 interpolated_values = [ 2522 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2523 if isinstance(e, exp.Alias) 2524 else self.sql(e, "this") 2525 for e in expression.args.get("interpolate") or [] 2526 ] 2527 interpolate = ( 2528 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2529 ) 2530 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2531 2532 def cluster_sql(self, expression: exp.Cluster) -> str: 2533 return self.op_expressions("CLUSTER BY", expression) 2534 2535 def distribute_sql(self, expression: exp.Distribute) -> str: 2536 return self.op_expressions("DISTRIBUTE BY", expression) 2537 2538 def sort_sql(self, expression: exp.Sort) -> str: 2539 return self.op_expressions("SORT BY", expression) 2540 2541 def ordered_sql(self, expression: exp.Ordered) -> str: 2542 desc = expression.args.get("desc") 2543 asc = not desc 2544 2545 nulls_first = expression.args.get("nulls_first") 2546 nulls_last = not nulls_first 2547 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2548 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2549 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2550 2551 this = self.sql(expression, "this") 2552 2553 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2554 nulls_sort_change = "" 2555 if nulls_first and ( 2556 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2557 ): 2558 nulls_sort_change = " NULLS FIRST" 2559 elif ( 2560 nulls_last 2561 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2562 and not nulls_are_last 2563 ): 2564 nulls_sort_change = " NULLS LAST" 2565 2566 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2567 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2568 window = expression.find_ancestor(exp.Window, exp.Select) 2569 if isinstance(window, exp.Window) and window.args.get("spec"): 2570 self.unsupported( 2571 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2572 ) 2573 nulls_sort_change = "" 2574 elif self.NULL_ORDERING_SUPPORTED is False and ( 2575 (asc and nulls_sort_change == " NULLS LAST") 2576 or (desc and nulls_sort_change == " NULLS FIRST") 2577 ): 2578 # BigQuery does not allow these ordering/nulls combinations when used under 2579 # an aggregation func or under a window containing one 2580 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2581 2582 if isinstance(ancestor, exp.Window): 2583 ancestor = ancestor.this 2584 if isinstance(ancestor, exp.AggFunc): 2585 self.unsupported( 2586 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2587 ) 2588 nulls_sort_change = "" 2589 elif self.NULL_ORDERING_SUPPORTED is None: 2590 if expression.this.is_int: 2591 self.unsupported( 2592 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2593 ) 2594 elif not isinstance(expression.this, exp.Rand): 2595 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2596 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2597 nulls_sort_change = "" 2598 2599 with_fill = self.sql(expression, "with_fill") 2600 with_fill = f" {with_fill}" if with_fill else "" 2601 2602 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2603 2604 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2605 window_frame = self.sql(expression, "window_frame") 2606 window_frame = f"{window_frame} " if window_frame else "" 2607 2608 this = self.sql(expression, "this") 2609 2610 return f"{window_frame}{this}" 2611 2612 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2613 partition = self.partition_by_sql(expression) 2614 order = self.sql(expression, "order") 2615 measures = self.expressions(expression, key="measures") 2616 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2617 rows = self.sql(expression, "rows") 2618 rows = self.seg(rows) if rows else "" 2619 after = self.sql(expression, "after") 2620 after = self.seg(after) if after else "" 2621 pattern = self.sql(expression, "pattern") 2622 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2623 definition_sqls = [ 2624 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2625 for definition in expression.args.get("define", []) 2626 ] 2627 definitions = self.expressions(sqls=definition_sqls) 2628 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2629 body = "".join( 2630 ( 2631 partition, 2632 order, 2633 measures, 2634 rows, 2635 after, 2636 pattern, 2637 define, 2638 ) 2639 ) 2640 alias = self.sql(expression, "alias") 2641 alias = f" {alias}" if alias else "" 2642 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2643 2644 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2645 limit = expression.args.get("limit") 2646 2647 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2648 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2649 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2650 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2651 2652 return csv( 2653 *sqls, 2654 *[self.sql(join) for join in expression.args.get("joins") or []], 2655 self.sql(expression, "match"), 2656 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2657 self.sql(expression, "prewhere"), 2658 self.sql(expression, "where"), 2659 self.sql(expression, "connect"), 2660 self.sql(expression, "group"), 2661 self.sql(expression, "having"), 2662 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2663 self.sql(expression, "order"), 2664 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2665 *self.after_limit_modifiers(expression), 2666 self.options_modifier(expression), 2667 self.for_modifiers(expression), 2668 sep="", 2669 ) 2670 2671 def options_modifier(self, expression: exp.Expression) -> str: 2672 options = self.expressions(expression, key="options") 2673 return f" {options}" if options else "" 2674 2675 def for_modifiers(self, expression: exp.Expression) -> str: 2676 for_modifiers = self.expressions(expression, key="for") 2677 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2678 2679 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2680 self.unsupported("Unsupported query option.") 2681 return "" 2682 2683 def offset_limit_modifiers( 2684 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2685 ) -> t.List[str]: 2686 return [ 2687 self.sql(expression, "offset") if fetch else self.sql(limit), 2688 self.sql(limit) if fetch else self.sql(expression, "offset"), 2689 ] 2690 2691 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2692 locks = self.expressions(expression, key="locks", sep=" ") 2693 locks = f" {locks}" if locks else "" 2694 return [locks, self.sql(expression, "sample")] 2695 2696 def select_sql(self, expression: exp.Select) -> str: 2697 into = expression.args.get("into") 2698 if not self.SUPPORTS_SELECT_INTO and into: 2699 into.pop() 2700 2701 hint = self.sql(expression, "hint") 2702 distinct = self.sql(expression, "distinct") 2703 distinct = f" {distinct}" if distinct else "" 2704 kind = self.sql(expression, "kind") 2705 2706 limit = expression.args.get("limit") 2707 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2708 top = self.limit_sql(limit, top=True) 2709 limit.pop() 2710 else: 2711 top = "" 2712 2713 expressions = self.expressions(expression) 2714 2715 if kind: 2716 if kind in self.SELECT_KINDS: 2717 kind = f" AS {kind}" 2718 else: 2719 if kind == "STRUCT": 2720 expressions = self.expressions( 2721 sqls=[ 2722 self.sql( 2723 exp.Struct( 2724 expressions=[ 2725 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2726 if isinstance(e, exp.Alias) 2727 else e 2728 for e in expression.expressions 2729 ] 2730 ) 2731 ) 2732 ] 2733 ) 2734 kind = "" 2735 2736 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2737 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2738 2739 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2740 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2741 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2742 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2743 sql = self.query_modifiers( 2744 expression, 2745 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2746 self.sql(expression, "into", comment=False), 2747 self.sql(expression, "from", comment=False), 2748 ) 2749 2750 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2751 if expression.args.get("with"): 2752 sql = self.maybe_comment(sql, expression) 2753 expression.pop_comments() 2754 2755 sql = self.prepend_ctes(expression, sql) 2756 2757 if not self.SUPPORTS_SELECT_INTO and into: 2758 if into.args.get("temporary"): 2759 table_kind = " TEMPORARY" 2760 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2761 table_kind = " UNLOGGED" 2762 else: 2763 table_kind = "" 2764 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2765 2766 return sql 2767 2768 def schema_sql(self, expression: exp.Schema) -> str: 2769 this = self.sql(expression, "this") 2770 sql = self.schema_columns_sql(expression) 2771 return f"{this} {sql}" if this and sql else this or sql 2772 2773 def schema_columns_sql(self, expression: exp.Schema) -> str: 2774 if expression.expressions: 2775 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2776 return "" 2777 2778 def star_sql(self, expression: exp.Star) -> str: 2779 except_ = self.expressions(expression, key="except", flat=True) 2780 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2781 replace = self.expressions(expression, key="replace", flat=True) 2782 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2783 rename = self.expressions(expression, key="rename", flat=True) 2784 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2785 return f"*{except_}{replace}{rename}" 2786 2787 def parameter_sql(self, expression: exp.Parameter) -> str: 2788 this = self.sql(expression, "this") 2789 return f"{self.PARAMETER_TOKEN}{this}" 2790 2791 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2792 this = self.sql(expression, "this") 2793 kind = expression.text("kind") 2794 if kind: 2795 kind = f"{kind}." 2796 return f"@@{kind}{this}" 2797 2798 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2799 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2800 2801 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2802 alias = self.sql(expression, "alias") 2803 alias = f"{sep}{alias}" if alias else "" 2804 sample = self.sql(expression, "sample") 2805 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2806 alias = f"{sample}{alias}" 2807 2808 # Set to None so it's not generated again by self.query_modifiers() 2809 expression.set("sample", None) 2810 2811 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2812 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2813 return self.prepend_ctes(expression, sql) 2814 2815 def qualify_sql(self, expression: exp.Qualify) -> str: 2816 this = self.indent(self.sql(expression, "this")) 2817 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2818 2819 def unnest_sql(self, expression: exp.Unnest) -> str: 2820 args = self.expressions(expression, flat=True) 2821 2822 alias = expression.args.get("alias") 2823 offset = expression.args.get("offset") 2824 2825 if self.UNNEST_WITH_ORDINALITY: 2826 if alias and isinstance(offset, exp.Expression): 2827 alias.append("columns", offset) 2828 2829 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2830 columns = alias.columns 2831 alias = self.sql(columns[0]) if columns else "" 2832 else: 2833 alias = self.sql(alias) 2834 2835 alias = f" AS {alias}" if alias else alias 2836 if self.UNNEST_WITH_ORDINALITY: 2837 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2838 else: 2839 if isinstance(offset, exp.Expression): 2840 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2841 elif offset: 2842 suffix = f"{alias} WITH OFFSET" 2843 else: 2844 suffix = alias 2845 2846 return f"UNNEST({args}){suffix}" 2847 2848 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2849 return "" 2850 2851 def where_sql(self, expression: exp.Where) -> str: 2852 this = self.indent(self.sql(expression, "this")) 2853 return f"{self.seg('WHERE')}{self.sep()}{this}" 2854 2855 def window_sql(self, expression: exp.Window) -> str: 2856 this = self.sql(expression, "this") 2857 partition = self.partition_by_sql(expression) 2858 order = expression.args.get("order") 2859 order = self.order_sql(order, flat=True) if order else "" 2860 spec = self.sql(expression, "spec") 2861 alias = self.sql(expression, "alias") 2862 over = self.sql(expression, "over") or "OVER" 2863 2864 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2865 2866 first = expression.args.get("first") 2867 if first is None: 2868 first = "" 2869 else: 2870 first = "FIRST" if first else "LAST" 2871 2872 if not partition and not order and not spec and alias: 2873 return f"{this} {alias}" 2874 2875 args = self.format_args( 2876 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2877 ) 2878 return f"{this} ({args})" 2879 2880 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2881 partition = self.expressions(expression, key="partition_by", flat=True) 2882 return f"PARTITION BY {partition}" if partition else "" 2883 2884 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2885 kind = self.sql(expression, "kind") 2886 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2887 end = ( 2888 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2889 or "CURRENT ROW" 2890 ) 2891 2892 window_spec = f"{kind} BETWEEN {start} AND {end}" 2893 2894 exclude = self.sql(expression, "exclude") 2895 if exclude: 2896 if self.SUPPORTS_WINDOW_EXCLUDE: 2897 window_spec += f" EXCLUDE {exclude}" 2898 else: 2899 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2900 2901 return window_spec 2902 2903 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2904 this = self.sql(expression, "this") 2905 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2906 return f"{this} WITHIN GROUP ({expression_sql})" 2907 2908 def between_sql(self, expression: exp.Between) -> str: 2909 this = self.sql(expression, "this") 2910 low = self.sql(expression, "low") 2911 high = self.sql(expression, "high") 2912 symmetric = expression.args.get("symmetric") 2913 2914 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2915 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2916 2917 flag = ( 2918 " SYMMETRIC" 2919 if symmetric 2920 else " ASYMMETRIC" 2921 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2922 else "" # silently drop ASYMMETRIC – semantics identical 2923 ) 2924 return f"{this} BETWEEN{flag} {low} AND {high}" 2925 2926 def bracket_offset_expressions( 2927 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2928 ) -> t.List[exp.Expression]: 2929 return apply_index_offset( 2930 expression.this, 2931 expression.expressions, 2932 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2933 dialect=self.dialect, 2934 ) 2935 2936 def bracket_sql(self, expression: exp.Bracket) -> str: 2937 expressions = self.bracket_offset_expressions(expression) 2938 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2939 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2940 2941 def all_sql(self, expression: exp.All) -> str: 2942 this = self.sql(expression, "this") 2943 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2944 this = self.wrap(this) 2945 return f"ALL {this}" 2946 2947 def any_sql(self, expression: exp.Any) -> str: 2948 this = self.sql(expression, "this") 2949 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2950 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2951 this = self.wrap(this) 2952 return f"ANY{this}" 2953 return f"ANY {this}" 2954 2955 def exists_sql(self, expression: exp.Exists) -> str: 2956 return f"EXISTS{self.wrap(expression)}" 2957 2958 def case_sql(self, expression: exp.Case) -> str: 2959 this = self.sql(expression, "this") 2960 statements = [f"CASE {this}" if this else "CASE"] 2961 2962 for e in expression.args["ifs"]: 2963 statements.append(f"WHEN {self.sql(e, 'this')}") 2964 statements.append(f"THEN {self.sql(e, 'true')}") 2965 2966 default = self.sql(expression, "default") 2967 2968 if default: 2969 statements.append(f"ELSE {default}") 2970 2971 statements.append("END") 2972 2973 if self.pretty and self.too_wide(statements): 2974 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2975 2976 return " ".join(statements) 2977 2978 def constraint_sql(self, expression: exp.Constraint) -> str: 2979 this = self.sql(expression, "this") 2980 expressions = self.expressions(expression, flat=True) 2981 return f"CONSTRAINT {this} {expressions}" 2982 2983 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2984 order = expression.args.get("order") 2985 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2986 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2987 2988 def extract_sql(self, expression: exp.Extract) -> str: 2989 from sqlglot.dialects.dialect import map_date_part 2990 2991 this = ( 2992 map_date_part(expression.this, self.dialect) 2993 if self.NORMALIZE_EXTRACT_DATE_PARTS 2994 else expression.this 2995 ) 2996 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2997 expression_sql = self.sql(expression, "expression") 2998 2999 return f"EXTRACT({this_sql} FROM {expression_sql})" 3000 3001 def trim_sql(self, expression: exp.Trim) -> str: 3002 trim_type = self.sql(expression, "position") 3003 3004 if trim_type == "LEADING": 3005 func_name = "LTRIM" 3006 elif trim_type == "TRAILING": 3007 func_name = "RTRIM" 3008 else: 3009 func_name = "TRIM" 3010 3011 return self.func(func_name, expression.this, expression.expression) 3012 3013 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3014 args = expression.expressions 3015 if isinstance(expression, exp.ConcatWs): 3016 args = args[1:] # Skip the delimiter 3017 3018 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3019 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3020 3021 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3022 3023 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3024 if not e.type: 3025 from sqlglot.optimizer.annotate_types import annotate_types 3026 3027 e = annotate_types(e, dialect=self.dialect) 3028 3029 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3030 return e 3031 3032 return exp.func("coalesce", e, exp.Literal.string("")) 3033 3034 args = [_wrap_with_coalesce(e) for e in args] 3035 3036 return args 3037 3038 def concat_sql(self, expression: exp.Concat) -> str: 3039 expressions = self.convert_concat_args(expression) 3040 3041 # Some dialects don't allow a single-argument CONCAT call 3042 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3043 return self.sql(expressions[0]) 3044 3045 return self.func("CONCAT", *expressions) 3046 3047 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3048 return self.func( 3049 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3050 ) 3051 3052 def check_sql(self, expression: exp.Check) -> str: 3053 this = self.sql(expression, key="this") 3054 return f"CHECK ({this})" 3055 3056 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3057 expressions = self.expressions(expression, flat=True) 3058 expressions = f" ({expressions})" if expressions else "" 3059 reference = self.sql(expression, "reference") 3060 reference = f" {reference}" if reference else "" 3061 delete = self.sql(expression, "delete") 3062 delete = f" ON DELETE {delete}" if delete else "" 3063 update = self.sql(expression, "update") 3064 update = f" ON UPDATE {update}" if update else "" 3065 options = self.expressions(expression, key="options", flat=True, sep=" ") 3066 options = f" {options}" if options else "" 3067 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3068 3069 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3070 expressions = self.expressions(expression, flat=True) 3071 include = self.sql(expression, "include") 3072 options = self.expressions(expression, key="options", flat=True, sep=" ") 3073 options = f" {options}" if options else "" 3074 return f"PRIMARY KEY ({expressions}){include}{options}" 3075 3076 def if_sql(self, expression: exp.If) -> str: 3077 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3078 3079 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3080 if self.MATCH_AGAINST_TABLE_PREFIX: 3081 expressions = [] 3082 for expr in expression.expressions: 3083 if isinstance(expr, exp.Table): 3084 expressions.append(f"TABLE {self.sql(expr)}") 3085 else: 3086 expressions.append(expr) 3087 else: 3088 expressions = expression.expressions 3089 3090 modifier = expression.args.get("modifier") 3091 modifier = f" {modifier}" if modifier else "" 3092 return ( 3093 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3094 ) 3095 3096 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3097 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3098 3099 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3100 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3101 3102 if expression.args.get("escape"): 3103 path = self.escape_str(path) 3104 3105 if self.QUOTE_JSON_PATH: 3106 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3107 3108 return path 3109 3110 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3111 if isinstance(expression, exp.JSONPathPart): 3112 transform = self.TRANSFORMS.get(expression.__class__) 3113 if not callable(transform): 3114 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3115 return "" 3116 3117 return transform(self, expression) 3118 3119 if isinstance(expression, int): 3120 return str(expression) 3121 3122 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3123 escaped = expression.replace("'", "\\'") 3124 escaped = f"\\'{expression}\\'" 3125 else: 3126 escaped = expression.replace('"', '\\"') 3127 escaped = f'"{escaped}"' 3128 3129 return escaped 3130 3131 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3132 return f"{self.sql(expression, 'this')} FORMAT JSON" 3133 3134 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3135 # Output the Teradata column FORMAT override. 3136 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3137 this = self.sql(expression, "this") 3138 fmt = self.sql(expression, "format") 3139 return f"{this} (FORMAT {fmt})" 3140 3141 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3142 null_handling = expression.args.get("null_handling") 3143 null_handling = f" {null_handling}" if null_handling else "" 3144 3145 unique_keys = expression.args.get("unique_keys") 3146 if unique_keys is not None: 3147 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3148 else: 3149 unique_keys = "" 3150 3151 return_type = self.sql(expression, "return_type") 3152 return_type = f" RETURNING {return_type}" if return_type else "" 3153 encoding = self.sql(expression, "encoding") 3154 encoding = f" ENCODING {encoding}" if encoding else "" 3155 3156 return self.func( 3157 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3158 *expression.expressions, 3159 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3160 ) 3161 3162 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3163 return self.jsonobject_sql(expression) 3164 3165 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3166 null_handling = expression.args.get("null_handling") 3167 null_handling = f" {null_handling}" if null_handling else "" 3168 return_type = self.sql(expression, "return_type") 3169 return_type = f" RETURNING {return_type}" if return_type else "" 3170 strict = " STRICT" if expression.args.get("strict") else "" 3171 return self.func( 3172 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3173 ) 3174 3175 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3176 this = self.sql(expression, "this") 3177 order = self.sql(expression, "order") 3178 null_handling = expression.args.get("null_handling") 3179 null_handling = f" {null_handling}" if null_handling else "" 3180 return_type = self.sql(expression, "return_type") 3181 return_type = f" RETURNING {return_type}" if return_type else "" 3182 strict = " STRICT" if expression.args.get("strict") else "" 3183 return self.func( 3184 "JSON_ARRAYAGG", 3185 this, 3186 suffix=f"{order}{null_handling}{return_type}{strict})", 3187 ) 3188 3189 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3190 path = self.sql(expression, "path") 3191 path = f" PATH {path}" if path else "" 3192 nested_schema = self.sql(expression, "nested_schema") 3193 3194 if nested_schema: 3195 return f"NESTED{path} {nested_schema}" 3196 3197 this = self.sql(expression, "this") 3198 kind = self.sql(expression, "kind") 3199 kind = f" {kind}" if kind else "" 3200 return f"{this}{kind}{path}" 3201 3202 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3203 return self.func("COLUMNS", *expression.expressions) 3204 3205 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3206 this = self.sql(expression, "this") 3207 path = self.sql(expression, "path") 3208 path = f", {path}" if path else "" 3209 error_handling = expression.args.get("error_handling") 3210 error_handling = f" {error_handling}" if error_handling else "" 3211 empty_handling = expression.args.get("empty_handling") 3212 empty_handling = f" {empty_handling}" if empty_handling else "" 3213 schema = self.sql(expression, "schema") 3214 return self.func( 3215 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3216 ) 3217 3218 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3219 this = self.sql(expression, "this") 3220 kind = self.sql(expression, "kind") 3221 path = self.sql(expression, "path") 3222 path = f" {path}" if path else "" 3223 as_json = " AS JSON" if expression.args.get("as_json") else "" 3224 return f"{this} {kind}{path}{as_json}" 3225 3226 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3227 this = self.sql(expression, "this") 3228 path = self.sql(expression, "path") 3229 path = f", {path}" if path else "" 3230 expressions = self.expressions(expression) 3231 with_ = ( 3232 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3233 if expressions 3234 else "" 3235 ) 3236 return f"OPENJSON({this}{path}){with_}" 3237 3238 def in_sql(self, expression: exp.In) -> str: 3239 query = expression.args.get("query") 3240 unnest = expression.args.get("unnest") 3241 field = expression.args.get("field") 3242 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3243 3244 if query: 3245 in_sql = self.sql(query) 3246 elif unnest: 3247 in_sql = self.in_unnest_op(unnest) 3248 elif field: 3249 in_sql = self.sql(field) 3250 else: 3251 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3252 3253 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3254 3255 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3256 return f"(SELECT {self.sql(unnest)})" 3257 3258 def interval_sql(self, expression: exp.Interval) -> str: 3259 unit = self.sql(expression, "unit") 3260 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3261 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3262 unit = f" {unit}" if unit else "" 3263 3264 if self.SINGLE_STRING_INTERVAL: 3265 this = expression.this.name if expression.this else "" 3266 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3267 3268 this = self.sql(expression, "this") 3269 if this: 3270 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3271 this = f" {this}" if unwrapped else f" ({this})" 3272 3273 return f"INTERVAL{this}{unit}" 3274 3275 def return_sql(self, expression: exp.Return) -> str: 3276 return f"RETURN {self.sql(expression, 'this')}" 3277 3278 def reference_sql(self, expression: exp.Reference) -> str: 3279 this = self.sql(expression, "this") 3280 expressions = self.expressions(expression, flat=True) 3281 expressions = f"({expressions})" if expressions else "" 3282 options = self.expressions(expression, key="options", flat=True, sep=" ") 3283 options = f" {options}" if options else "" 3284 return f"REFERENCES {this}{expressions}{options}" 3285 3286 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3287 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3288 parent = expression.parent 3289 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3290 return self.func( 3291 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3292 ) 3293 3294 def paren_sql(self, expression: exp.Paren) -> str: 3295 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3296 return f"({sql}{self.seg(')', sep='')}" 3297 3298 def neg_sql(self, expression: exp.Neg) -> str: 3299 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3300 this_sql = self.sql(expression, "this") 3301 sep = " " if this_sql[0] == "-" else "" 3302 return f"-{sep}{this_sql}" 3303 3304 def not_sql(self, expression: exp.Not) -> str: 3305 return f"NOT {self.sql(expression, 'this')}" 3306 3307 def alias_sql(self, expression: exp.Alias) -> str: 3308 alias = self.sql(expression, "alias") 3309 alias = f" AS {alias}" if alias else "" 3310 return f"{self.sql(expression, 'this')}{alias}" 3311 3312 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3313 alias = expression.args["alias"] 3314 3315 parent = expression.parent 3316 pivot = parent and parent.parent 3317 3318 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3319 identifier_alias = isinstance(alias, exp.Identifier) 3320 literal_alias = isinstance(alias, exp.Literal) 3321 3322 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3323 alias.replace(exp.Literal.string(alias.output_name)) 3324 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3325 alias.replace(exp.to_identifier(alias.output_name)) 3326 3327 return self.alias_sql(expression) 3328 3329 def aliases_sql(self, expression: exp.Aliases) -> str: 3330 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3331 3332 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3333 this = self.sql(expression, "this") 3334 index = self.sql(expression, "expression") 3335 return f"{this} AT {index}" 3336 3337 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3338 this = self.sql(expression, "this") 3339 zone = self.sql(expression, "zone") 3340 return f"{this} AT TIME ZONE {zone}" 3341 3342 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3343 this = self.sql(expression, "this") 3344 zone = self.sql(expression, "zone") 3345 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3346 3347 def add_sql(self, expression: exp.Add) -> str: 3348 return self.binary(expression, "+") 3349 3350 def and_sql( 3351 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3352 ) -> str: 3353 return self.connector_sql(expression, "AND", stack) 3354 3355 def or_sql( 3356 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3357 ) -> str: 3358 return self.connector_sql(expression, "OR", stack) 3359 3360 def xor_sql( 3361 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3362 ) -> str: 3363 return self.connector_sql(expression, "XOR", stack) 3364 3365 def connector_sql( 3366 self, 3367 expression: exp.Connector, 3368 op: str, 3369 stack: t.Optional[t.List[str | exp.Expression]] = None, 3370 ) -> str: 3371 if stack is not None: 3372 if expression.expressions: 3373 stack.append(self.expressions(expression, sep=f" {op} ")) 3374 else: 3375 stack.append(expression.right) 3376 if expression.comments and self.comments: 3377 for comment in expression.comments: 3378 if comment: 3379 op += f" /*{self.sanitize_comment(comment)}*/" 3380 stack.extend((op, expression.left)) 3381 return op 3382 3383 stack = [expression] 3384 sqls: t.List[str] = [] 3385 ops = set() 3386 3387 while stack: 3388 node = stack.pop() 3389 if isinstance(node, exp.Connector): 3390 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3391 else: 3392 sql = self.sql(node) 3393 if sqls and sqls[-1] in ops: 3394 sqls[-1] += f" {sql}" 3395 else: 3396 sqls.append(sql) 3397 3398 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3399 return sep.join(sqls) 3400 3401 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3402 return self.binary(expression, "&") 3403 3404 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3405 return self.binary(expression, "<<") 3406 3407 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3408 return f"~{self.sql(expression, 'this')}" 3409 3410 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3411 return self.binary(expression, "|") 3412 3413 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3414 return self.binary(expression, ">>") 3415 3416 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3417 return self.binary(expression, "^") 3418 3419 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3420 format_sql = self.sql(expression, "format") 3421 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3422 to_sql = self.sql(expression, "to") 3423 to_sql = f" {to_sql}" if to_sql else "" 3424 action = self.sql(expression, "action") 3425 action = f" {action}" if action else "" 3426 default = self.sql(expression, "default") 3427 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3428 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3429 3430 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3431 zone = self.sql(expression, "this") 3432 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3433 3434 def collate_sql(self, expression: exp.Collate) -> str: 3435 if self.COLLATE_IS_FUNC: 3436 return self.function_fallback_sql(expression) 3437 return self.binary(expression, "COLLATE") 3438 3439 def command_sql(self, expression: exp.Command) -> str: 3440 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3441 3442 def comment_sql(self, expression: exp.Comment) -> str: 3443 this = self.sql(expression, "this") 3444 kind = expression.args["kind"] 3445 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3446 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3447 expression_sql = self.sql(expression, "expression") 3448 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3449 3450 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3451 this = self.sql(expression, "this") 3452 delete = " DELETE" if expression.args.get("delete") else "" 3453 recompress = self.sql(expression, "recompress") 3454 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3455 to_disk = self.sql(expression, "to_disk") 3456 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3457 to_volume = self.sql(expression, "to_volume") 3458 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3459 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3460 3461 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3462 where = self.sql(expression, "where") 3463 group = self.sql(expression, "group") 3464 aggregates = self.expressions(expression, key="aggregates") 3465 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3466 3467 if not (where or group or aggregates) and len(expression.expressions) == 1: 3468 return f"TTL {self.expressions(expression, flat=True)}" 3469 3470 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3471 3472 def transaction_sql(self, expression: exp.Transaction) -> str: 3473 modes = self.expressions(expression, key="modes") 3474 modes = f" {modes}" if modes else "" 3475 return f"BEGIN{modes}" 3476 3477 def commit_sql(self, expression: exp.Commit) -> str: 3478 chain = expression.args.get("chain") 3479 if chain is not None: 3480 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3481 3482 return f"COMMIT{chain or ''}" 3483 3484 def rollback_sql(self, expression: exp.Rollback) -> str: 3485 savepoint = expression.args.get("savepoint") 3486 savepoint = f" TO {savepoint}" if savepoint else "" 3487 return f"ROLLBACK{savepoint}" 3488 3489 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3490 this = self.sql(expression, "this") 3491 3492 dtype = self.sql(expression, "dtype") 3493 if dtype: 3494 collate = self.sql(expression, "collate") 3495 collate = f" COLLATE {collate}" if collate else "" 3496 using = self.sql(expression, "using") 3497 using = f" USING {using}" if using else "" 3498 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3499 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3500 3501 default = self.sql(expression, "default") 3502 if default: 3503 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3504 3505 comment = self.sql(expression, "comment") 3506 if comment: 3507 return f"ALTER COLUMN {this} COMMENT {comment}" 3508 3509 visible = expression.args.get("visible") 3510 if visible: 3511 return f"ALTER COLUMN {this} SET {visible}" 3512 3513 allow_null = expression.args.get("allow_null") 3514 drop = expression.args.get("drop") 3515 3516 if not drop and not allow_null: 3517 self.unsupported("Unsupported ALTER COLUMN syntax") 3518 3519 if allow_null is not None: 3520 keyword = "DROP" if drop else "SET" 3521 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3522 3523 return f"ALTER COLUMN {this} DROP DEFAULT" 3524 3525 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3526 this = self.sql(expression, "this") 3527 3528 visible = expression.args.get("visible") 3529 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3530 3531 return f"ALTER INDEX {this} {visible_sql}" 3532 3533 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3534 this = self.sql(expression, "this") 3535 if not isinstance(expression.this, exp.Var): 3536 this = f"KEY DISTKEY {this}" 3537 return f"ALTER DISTSTYLE {this}" 3538 3539 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3540 compound = " COMPOUND" if expression.args.get("compound") else "" 3541 this = self.sql(expression, "this") 3542 expressions = self.expressions(expression, flat=True) 3543 expressions = f"({expressions})" if expressions else "" 3544 return f"ALTER{compound} SORTKEY {this or expressions}" 3545 3546 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3547 if not self.RENAME_TABLE_WITH_DB: 3548 # Remove db from tables 3549 expression = expression.transform( 3550 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3551 ).assert_is(exp.AlterRename) 3552 this = self.sql(expression, "this") 3553 to_kw = " TO" if include_to else "" 3554 return f"RENAME{to_kw} {this}" 3555 3556 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3557 exists = " IF EXISTS" if expression.args.get("exists") else "" 3558 old_column = self.sql(expression, "this") 3559 new_column = self.sql(expression, "to") 3560 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3561 3562 def alterset_sql(self, expression: exp.AlterSet) -> str: 3563 exprs = self.expressions(expression, flat=True) 3564 if self.ALTER_SET_WRAPPED: 3565 exprs = f"({exprs})" 3566 3567 return f"SET {exprs}" 3568 3569 def alter_sql(self, expression: exp.Alter) -> str: 3570 actions = expression.args["actions"] 3571 3572 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3573 actions[0], exp.ColumnDef 3574 ): 3575 actions_sql = self.expressions(expression, key="actions", flat=True) 3576 actions_sql = f"ADD {actions_sql}" 3577 else: 3578 actions_list = [] 3579 for action in actions: 3580 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3581 action_sql = self.add_column_sql(action) 3582 else: 3583 action_sql = self.sql(action) 3584 if isinstance(action, exp.Query): 3585 action_sql = f"AS {action_sql}" 3586 3587 actions_list.append(action_sql) 3588 3589 actions_sql = self.format_args(*actions_list).lstrip("\n") 3590 3591 exists = " IF EXISTS" if expression.args.get("exists") else "" 3592 on_cluster = self.sql(expression, "cluster") 3593 on_cluster = f" {on_cluster}" if on_cluster else "" 3594 only = " ONLY" if expression.args.get("only") else "" 3595 options = self.expressions(expression, key="options") 3596 options = f", {options}" if options else "" 3597 kind = self.sql(expression, "kind") 3598 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3599 check = " WITH CHECK" if expression.args.get("check") else "" 3600 this = self.sql(expression, "this") 3601 this = f" {this}" if this else "" 3602 3603 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}" 3604 3605 def altersession_sql(self, expression: exp.AlterSession) -> str: 3606 items_sql = self.expressions(expression, flat=True) 3607 keyword = "UNSET" if expression.args.get("unset") else "SET" 3608 return f"{keyword} {items_sql}" 3609 3610 def add_column_sql(self, expression: exp.Expression) -> str: 3611 sql = self.sql(expression) 3612 if isinstance(expression, exp.Schema): 3613 column_text = " COLUMNS" 3614 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3615 column_text = " COLUMN" 3616 else: 3617 column_text = "" 3618 3619 return f"ADD{column_text} {sql}" 3620 3621 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3622 expressions = self.expressions(expression) 3623 exists = " IF EXISTS " if expression.args.get("exists") else " " 3624 return f"DROP{exists}{expressions}" 3625 3626 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3627 return f"ADD {self.expressions(expression, indent=False)}" 3628 3629 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3630 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3631 location = self.sql(expression, "location") 3632 location = f" {location}" if location else "" 3633 return f"ADD {exists}{self.sql(expression.this)}{location}" 3634 3635 def distinct_sql(self, expression: exp.Distinct) -> str: 3636 this = self.expressions(expression, flat=True) 3637 3638 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3639 case = exp.case() 3640 for arg in expression.expressions: 3641 case = case.when(arg.is_(exp.null()), exp.null()) 3642 this = self.sql(case.else_(f"({this})")) 3643 3644 this = f" {this}" if this else "" 3645 3646 on = self.sql(expression, "on") 3647 on = f" ON {on}" if on else "" 3648 return f"DISTINCT{this}{on}" 3649 3650 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3651 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3652 3653 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3654 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3655 3656 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3657 this_sql = self.sql(expression, "this") 3658 expression_sql = self.sql(expression, "expression") 3659 kind = "MAX" if expression.args.get("max") else "MIN" 3660 return f"{this_sql} HAVING {kind} {expression_sql}" 3661 3662 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3663 return self.sql( 3664 exp.Cast( 3665 this=exp.Div(this=expression.this, expression=expression.expression), 3666 to=exp.DataType(this=exp.DataType.Type.INT), 3667 ) 3668 ) 3669 3670 def dpipe_sql(self, expression: exp.DPipe) -> str: 3671 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3672 return self.func( 3673 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3674 ) 3675 return self.binary(expression, "||") 3676 3677 def div_sql(self, expression: exp.Div) -> str: 3678 l, r = expression.left, expression.right 3679 3680 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3681 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3682 3683 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3684 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3685 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3686 3687 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3688 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3689 return self.sql( 3690 exp.cast( 3691 l / r, 3692 to=exp.DataType.Type.BIGINT, 3693 ) 3694 ) 3695 3696 return self.binary(expression, "/") 3697 3698 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3699 n = exp._wrap(expression.this, exp.Binary) 3700 d = exp._wrap(expression.expression, exp.Binary) 3701 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3702 3703 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3704 return self.binary(expression, "OVERLAPS") 3705 3706 def distance_sql(self, expression: exp.Distance) -> str: 3707 return self.binary(expression, "<->") 3708 3709 def dot_sql(self, expression: exp.Dot) -> str: 3710 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3711 3712 def eq_sql(self, expression: exp.EQ) -> str: 3713 return self.binary(expression, "=") 3714 3715 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3716 return self.binary(expression, ":=") 3717 3718 def escape_sql(self, expression: exp.Escape) -> str: 3719 return self.binary(expression, "ESCAPE") 3720 3721 def glob_sql(self, expression: exp.Glob) -> str: 3722 return self.binary(expression, "GLOB") 3723 3724 def gt_sql(self, expression: exp.GT) -> str: 3725 return self.binary(expression, ">") 3726 3727 def gte_sql(self, expression: exp.GTE) -> str: 3728 return self.binary(expression, ">=") 3729 3730 def is_sql(self, expression: exp.Is) -> str: 3731 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3732 return self.sql( 3733 expression.this if expression.expression.this else exp.not_(expression.this) 3734 ) 3735 return self.binary(expression, "IS") 3736 3737 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3738 this = expression.this 3739 rhs = expression.expression 3740 3741 if isinstance(expression, exp.Like): 3742 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3743 op = "LIKE" 3744 else: 3745 exp_class = exp.ILike 3746 op = "ILIKE" 3747 3748 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3749 exprs = rhs.this.unnest() 3750 3751 if isinstance(exprs, exp.Tuple): 3752 exprs = exprs.expressions 3753 3754 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3755 3756 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3757 for expr in exprs[1:]: 3758 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3759 3760 return self.sql(like_expr) 3761 3762 return self.binary(expression, op) 3763 3764 def like_sql(self, expression: exp.Like) -> str: 3765 return self._like_sql(expression) 3766 3767 def ilike_sql(self, expression: exp.ILike) -> str: 3768 return self._like_sql(expression) 3769 3770 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3771 return self.binary(expression, "SIMILAR TO") 3772 3773 def lt_sql(self, expression: exp.LT) -> str: 3774 return self.binary(expression, "<") 3775 3776 def lte_sql(self, expression: exp.LTE) -> str: 3777 return self.binary(expression, "<=") 3778 3779 def mod_sql(self, expression: exp.Mod) -> str: 3780 return self.binary(expression, "%") 3781 3782 def mul_sql(self, expression: exp.Mul) -> str: 3783 return self.binary(expression, "*") 3784 3785 def neq_sql(self, expression: exp.NEQ) -> str: 3786 return self.binary(expression, "<>") 3787 3788 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3789 return self.binary(expression, "IS NOT DISTINCT FROM") 3790 3791 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3792 return self.binary(expression, "IS DISTINCT FROM") 3793 3794 def slice_sql(self, expression: exp.Slice) -> str: 3795 return self.binary(expression, ":") 3796 3797 def sub_sql(self, expression: exp.Sub) -> str: 3798 return self.binary(expression, "-") 3799 3800 def trycast_sql(self, expression: exp.TryCast) -> str: 3801 return self.cast_sql(expression, safe_prefix="TRY_") 3802 3803 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3804 return self.cast_sql(expression) 3805 3806 def try_sql(self, expression: exp.Try) -> str: 3807 if not self.TRY_SUPPORTED: 3808 self.unsupported("Unsupported TRY function") 3809 return self.sql(expression, "this") 3810 3811 return self.func("TRY", expression.this) 3812 3813 def log_sql(self, expression: exp.Log) -> str: 3814 this = expression.this 3815 expr = expression.expression 3816 3817 if self.dialect.LOG_BASE_FIRST is False: 3818 this, expr = expr, this 3819 elif self.dialect.LOG_BASE_FIRST is None and expr: 3820 if this.name in ("2", "10"): 3821 return self.func(f"LOG{this.name}", expr) 3822 3823 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3824 3825 return self.func("LOG", this, expr) 3826 3827 def use_sql(self, expression: exp.Use) -> str: 3828 kind = self.sql(expression, "kind") 3829 kind = f" {kind}" if kind else "" 3830 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3831 this = f" {this}" if this else "" 3832 return f"USE{kind}{this}" 3833 3834 def binary(self, expression: exp.Binary, op: str) -> str: 3835 sqls: t.List[str] = [] 3836 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3837 binary_type = type(expression) 3838 3839 while stack: 3840 node = stack.pop() 3841 3842 if type(node) is binary_type: 3843 op_func = node.args.get("operator") 3844 if op_func: 3845 op = f"OPERATOR({self.sql(op_func)})" 3846 3847 stack.append(node.right) 3848 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3849 stack.append(node.left) 3850 else: 3851 sqls.append(self.sql(node)) 3852 3853 return "".join(sqls) 3854 3855 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3856 to_clause = self.sql(expression, "to") 3857 if to_clause: 3858 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3859 3860 return self.function_fallback_sql(expression) 3861 3862 def function_fallback_sql(self, expression: exp.Func) -> str: 3863 args = [] 3864 3865 for key in expression.arg_types: 3866 arg_value = expression.args.get(key) 3867 3868 if isinstance(arg_value, list): 3869 for value in arg_value: 3870 args.append(value) 3871 elif arg_value is not None: 3872 args.append(arg_value) 3873 3874 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3875 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3876 else: 3877 name = expression.sql_name() 3878 3879 return self.func(name, *args) 3880 3881 def func( 3882 self, 3883 name: str, 3884 *args: t.Optional[exp.Expression | str], 3885 prefix: str = "(", 3886 suffix: str = ")", 3887 normalize: bool = True, 3888 ) -> str: 3889 name = self.normalize_func(name) if normalize else name 3890 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3891 3892 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3893 arg_sqls = tuple( 3894 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3895 ) 3896 if self.pretty and self.too_wide(arg_sqls): 3897 return self.indent( 3898 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3899 ) 3900 return sep.join(arg_sqls) 3901 3902 def too_wide(self, args: t.Iterable) -> bool: 3903 return sum(len(arg) for arg in args) > self.max_text_width 3904 3905 def format_time( 3906 self, 3907 expression: exp.Expression, 3908 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3909 inverse_time_trie: t.Optional[t.Dict] = None, 3910 ) -> t.Optional[str]: 3911 return format_time( 3912 self.sql(expression, "format"), 3913 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3914 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3915 ) 3916 3917 def expressions( 3918 self, 3919 expression: t.Optional[exp.Expression] = None, 3920 key: t.Optional[str] = None, 3921 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3922 flat: bool = False, 3923 indent: bool = True, 3924 skip_first: bool = False, 3925 skip_last: bool = False, 3926 sep: str = ", ", 3927 prefix: str = "", 3928 dynamic: bool = False, 3929 new_line: bool = False, 3930 ) -> str: 3931 expressions = expression.args.get(key or "expressions") if expression else sqls 3932 3933 if not expressions: 3934 return "" 3935 3936 if flat: 3937 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3938 3939 num_sqls = len(expressions) 3940 result_sqls = [] 3941 3942 for i, e in enumerate(expressions): 3943 sql = self.sql(e, comment=False) 3944 if not sql: 3945 continue 3946 3947 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3948 3949 if self.pretty: 3950 if self.leading_comma: 3951 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3952 else: 3953 result_sqls.append( 3954 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3955 ) 3956 else: 3957 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3958 3959 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3960 if new_line: 3961 result_sqls.insert(0, "") 3962 result_sqls.append("") 3963 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3964 else: 3965 result_sql = "".join(result_sqls) 3966 3967 return ( 3968 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3969 if indent 3970 else result_sql 3971 ) 3972 3973 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3974 flat = flat or isinstance(expression.parent, exp.Properties) 3975 expressions_sql = self.expressions(expression, flat=flat) 3976 if flat: 3977 return f"{op} {expressions_sql}" 3978 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3979 3980 def naked_property(self, expression: exp.Property) -> str: 3981 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3982 if not property_name: 3983 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3984 return f"{property_name} {self.sql(expression, 'this')}" 3985 3986 def tag_sql(self, expression: exp.Tag) -> str: 3987 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3988 3989 def token_sql(self, token_type: TokenType) -> str: 3990 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3991 3992 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3993 this = self.sql(expression, "this") 3994 expressions = self.no_identify(self.expressions, expression) 3995 expressions = ( 3996 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3997 ) 3998 return f"{this}{expressions}" if expressions.strip() != "" else this 3999 4000 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4001 this = self.sql(expression, "this") 4002 expressions = self.expressions(expression, flat=True) 4003 return f"{this}({expressions})" 4004 4005 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4006 return self.binary(expression, "=>") 4007 4008 def when_sql(self, expression: exp.When) -> str: 4009 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4010 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4011 condition = self.sql(expression, "condition") 4012 condition = f" AND {condition}" if condition else "" 4013 4014 then_expression = expression.args.get("then") 4015 if isinstance(then_expression, exp.Insert): 4016 this = self.sql(then_expression, "this") 4017 this = f"INSERT {this}" if this else "INSERT" 4018 then = self.sql(then_expression, "expression") 4019 then = f"{this} VALUES {then}" if then else this 4020 elif isinstance(then_expression, exp.Update): 4021 if isinstance(then_expression.args.get("expressions"), exp.Star): 4022 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4023 else: 4024 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4025 else: 4026 then = self.sql(then_expression) 4027 return f"WHEN {matched}{source}{condition} THEN {then}" 4028 4029 def whens_sql(self, expression: exp.Whens) -> str: 4030 return self.expressions(expression, sep=" ", indent=False) 4031 4032 def merge_sql(self, expression: exp.Merge) -> str: 4033 table = expression.this 4034 table_alias = "" 4035 4036 hints = table.args.get("hints") 4037 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4038 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4039 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4040 4041 this = self.sql(table) 4042 using = f"USING {self.sql(expression, 'using')}" 4043 on = f"ON {self.sql(expression, 'on')}" 4044 whens = self.sql(expression, "whens") 4045 4046 returning = self.sql(expression, "returning") 4047 if returning: 4048 whens = f"{whens}{returning}" 4049 4050 sep = self.sep() 4051 4052 return self.prepend_ctes( 4053 expression, 4054 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4055 ) 4056 4057 @unsupported_args("format") 4058 def tochar_sql(self, expression: exp.ToChar) -> str: 4059 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4060 4061 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4062 if not self.SUPPORTS_TO_NUMBER: 4063 self.unsupported("Unsupported TO_NUMBER function") 4064 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4065 4066 fmt = expression.args.get("format") 4067 if not fmt: 4068 self.unsupported("Conversion format is required for TO_NUMBER") 4069 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4070 4071 return self.func("TO_NUMBER", expression.this, fmt) 4072 4073 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4074 this = self.sql(expression, "this") 4075 kind = self.sql(expression, "kind") 4076 settings_sql = self.expressions(expression, key="settings", sep=" ") 4077 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4078 return f"{this}({kind}{args})" 4079 4080 def dictrange_sql(self, expression: exp.DictRange) -> str: 4081 this = self.sql(expression, "this") 4082 max = self.sql(expression, "max") 4083 min = self.sql(expression, "min") 4084 return f"{this}(MIN {min} MAX {max})" 4085 4086 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4087 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4088 4089 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4090 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4091 4092 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4093 def uniquekeyproperty_sql( 4094 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4095 ) -> str: 4096 return f"{prefix} ({self.expressions(expression, flat=True)})" 4097 4098 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4099 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4100 expressions = self.expressions(expression, flat=True) 4101 expressions = f" {self.wrap(expressions)}" if expressions else "" 4102 buckets = self.sql(expression, "buckets") 4103 kind = self.sql(expression, "kind") 4104 buckets = f" BUCKETS {buckets}" if buckets else "" 4105 order = self.sql(expression, "order") 4106 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4107 4108 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4109 return "" 4110 4111 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4112 expressions = self.expressions(expression, key="expressions", flat=True) 4113 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4114 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4115 buckets = self.sql(expression, "buckets") 4116 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4117 4118 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4119 this = self.sql(expression, "this") 4120 having = self.sql(expression, "having") 4121 4122 if having: 4123 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4124 4125 return self.func("ANY_VALUE", this) 4126 4127 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4128 transform = self.func("TRANSFORM", *expression.expressions) 4129 row_format_before = self.sql(expression, "row_format_before") 4130 row_format_before = f" {row_format_before}" if row_format_before else "" 4131 record_writer = self.sql(expression, "record_writer") 4132 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4133 using = f" USING {self.sql(expression, 'command_script')}" 4134 schema = self.sql(expression, "schema") 4135 schema = f" AS {schema}" if schema else "" 4136 row_format_after = self.sql(expression, "row_format_after") 4137 row_format_after = f" {row_format_after}" if row_format_after else "" 4138 record_reader = self.sql(expression, "record_reader") 4139 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4140 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4141 4142 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4143 key_block_size = self.sql(expression, "key_block_size") 4144 if key_block_size: 4145 return f"KEY_BLOCK_SIZE = {key_block_size}" 4146 4147 using = self.sql(expression, "using") 4148 if using: 4149 return f"USING {using}" 4150 4151 parser = self.sql(expression, "parser") 4152 if parser: 4153 return f"WITH PARSER {parser}" 4154 4155 comment = self.sql(expression, "comment") 4156 if comment: 4157 return f"COMMENT {comment}" 4158 4159 visible = expression.args.get("visible") 4160 if visible is not None: 4161 return "VISIBLE" if visible else "INVISIBLE" 4162 4163 engine_attr = self.sql(expression, "engine_attr") 4164 if engine_attr: 4165 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4166 4167 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4168 if secondary_engine_attr: 4169 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4170 4171 self.unsupported("Unsupported index constraint option.") 4172 return "" 4173 4174 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4175 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4176 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4177 4178 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4179 kind = self.sql(expression, "kind") 4180 kind = f"{kind} INDEX" if kind else "INDEX" 4181 this = self.sql(expression, "this") 4182 this = f" {this}" if this else "" 4183 index_type = self.sql(expression, "index_type") 4184 index_type = f" USING {index_type}" if index_type else "" 4185 expressions = self.expressions(expression, flat=True) 4186 expressions = f" ({expressions})" if expressions else "" 4187 options = self.expressions(expression, key="options", sep=" ") 4188 options = f" {options}" if options else "" 4189 return f"{kind}{this}{index_type}{expressions}{options}" 4190 4191 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4192 if self.NVL2_SUPPORTED: 4193 return self.function_fallback_sql(expression) 4194 4195 case = exp.Case().when( 4196 expression.this.is_(exp.null()).not_(copy=False), 4197 expression.args["true"], 4198 copy=False, 4199 ) 4200 else_cond = expression.args.get("false") 4201 if else_cond: 4202 case.else_(else_cond, copy=False) 4203 4204 return self.sql(case) 4205 4206 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4207 this = self.sql(expression, "this") 4208 expr = self.sql(expression, "expression") 4209 iterator = self.sql(expression, "iterator") 4210 condition = self.sql(expression, "condition") 4211 condition = f" IF {condition}" if condition else "" 4212 return f"{this} FOR {expr} IN {iterator}{condition}" 4213 4214 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4215 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4216 4217 def opclass_sql(self, expression: exp.Opclass) -> str: 4218 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4219 4220 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4221 model = self.sql(expression, "this") 4222 model = f"MODEL {model}" 4223 expr = expression.expression 4224 if expr: 4225 expr_sql = self.sql(expression, "expression") 4226 expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql 4227 else: 4228 expr_sql = None 4229 4230 parameters = self.sql(expression, "params_struct") or None 4231 4232 return self.func(name, model, expr_sql, parameters) 4233 4234 def predict_sql(self, expression: exp.Predict) -> str: 4235 return self._ml_sql(expression, "PREDICT") 4236 4237 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4238 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4239 return self._ml_sql(expression, name) 4240 4241 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4242 return self._ml_sql(expression, "TRANSLATE") 4243 4244 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4245 return self._ml_sql(expression, "FORECAST") 4246 4247 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4248 this_sql = self.sql(expression, "this") 4249 if isinstance(expression.this, exp.Table): 4250 this_sql = f"TABLE {this_sql}" 4251 4252 return self.func( 4253 "FEATURES_AT_TIME", 4254 this_sql, 4255 expression.args.get("time"), 4256 expression.args.get("num_rows"), 4257 expression.args.get("ignore_feature_nulls"), 4258 ) 4259 4260 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4261 this_sql = self.sql(expression, "this") 4262 if isinstance(expression.this, exp.Table): 4263 this_sql = f"TABLE {this_sql}" 4264 4265 query_table = self.sql(expression, "query_table") 4266 if isinstance(expression.args["query_table"], exp.Table): 4267 query_table = f"TABLE {query_table}" 4268 4269 return self.func( 4270 "VECTOR_SEARCH", 4271 this_sql, 4272 expression.args.get("column_to_search"), 4273 query_table, 4274 expression.args.get("query_column_to_search"), 4275 expression.args.get("top_k"), 4276 expression.args.get("distance_type"), 4277 expression.args.get("options"), 4278 ) 4279 4280 def forin_sql(self, expression: exp.ForIn) -> str: 4281 this = self.sql(expression, "this") 4282 expression_sql = self.sql(expression, "expression") 4283 return f"FOR {this} DO {expression_sql}" 4284 4285 def refresh_sql(self, expression: exp.Refresh) -> str: 4286 this = self.sql(expression, "this") 4287 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4288 return f"REFRESH {table}{this}" 4289 4290 def toarray_sql(self, expression: exp.ToArray) -> str: 4291 arg = expression.this 4292 if not arg.type: 4293 from sqlglot.optimizer.annotate_types import annotate_types 4294 4295 arg = annotate_types(arg, dialect=self.dialect) 4296 4297 if arg.is_type(exp.DataType.Type.ARRAY): 4298 return self.sql(arg) 4299 4300 cond_for_null = arg.is_(exp.null()) 4301 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4302 4303 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4304 this = expression.this 4305 time_format = self.format_time(expression) 4306 4307 if time_format: 4308 return self.sql( 4309 exp.cast( 4310 exp.StrToTime(this=this, format=expression.args["format"]), 4311 exp.DataType.Type.TIME, 4312 ) 4313 ) 4314 4315 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4316 return self.sql(this) 4317 4318 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4319 4320 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4321 this = expression.this 4322 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4323 return self.sql(this) 4324 4325 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4326 4327 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4328 this = expression.this 4329 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4330 return self.sql(this) 4331 4332 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4333 4334 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4335 this = expression.this 4336 time_format = self.format_time(expression) 4337 4338 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4339 return self.sql( 4340 exp.cast( 4341 exp.StrToTime(this=this, format=expression.args["format"]), 4342 exp.DataType.Type.DATE, 4343 ) 4344 ) 4345 4346 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4347 return self.sql(this) 4348 4349 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4350 4351 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4352 return self.sql( 4353 exp.func( 4354 "DATEDIFF", 4355 expression.this, 4356 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4357 "day", 4358 ) 4359 ) 4360 4361 def lastday_sql(self, expression: exp.LastDay) -> str: 4362 if self.LAST_DAY_SUPPORTS_DATE_PART: 4363 return self.function_fallback_sql(expression) 4364 4365 unit = expression.text("unit") 4366 if unit and unit != "MONTH": 4367 self.unsupported("Date parts are not supported in LAST_DAY.") 4368 4369 return self.func("LAST_DAY", expression.this) 4370 4371 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4372 from sqlglot.dialects.dialect import unit_to_str 4373 4374 return self.func( 4375 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4376 ) 4377 4378 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4379 if self.CAN_IMPLEMENT_ARRAY_ANY: 4380 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4381 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4382 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4383 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4384 4385 from sqlglot.dialects import Dialect 4386 4387 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4388 if self.dialect.__class__ != Dialect: 4389 self.unsupported("ARRAY_ANY is unsupported") 4390 4391 return self.function_fallback_sql(expression) 4392 4393 def struct_sql(self, expression: exp.Struct) -> str: 4394 expression.set( 4395 "expressions", 4396 [ 4397 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4398 if isinstance(e, exp.PropertyEQ) 4399 else e 4400 for e in expression.expressions 4401 ], 4402 ) 4403 4404 return self.function_fallback_sql(expression) 4405 4406 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4407 low = self.sql(expression, "this") 4408 high = self.sql(expression, "expression") 4409 4410 return f"{low} TO {high}" 4411 4412 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4413 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4414 tables = f" {self.expressions(expression)}" 4415 4416 exists = " IF EXISTS" if expression.args.get("exists") else "" 4417 4418 on_cluster = self.sql(expression, "cluster") 4419 on_cluster = f" {on_cluster}" if on_cluster else "" 4420 4421 identity = self.sql(expression, "identity") 4422 identity = f" {identity} IDENTITY" if identity else "" 4423 4424 option = self.sql(expression, "option") 4425 option = f" {option}" if option else "" 4426 4427 partition = self.sql(expression, "partition") 4428 partition = f" {partition}" if partition else "" 4429 4430 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4431 4432 # This transpiles T-SQL's CONVERT function 4433 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4434 def convert_sql(self, expression: exp.Convert) -> str: 4435 to = expression.this 4436 value = expression.expression 4437 style = expression.args.get("style") 4438 safe = expression.args.get("safe") 4439 strict = expression.args.get("strict") 4440 4441 if not to or not value: 4442 return "" 4443 4444 # Retrieve length of datatype and override to default if not specified 4445 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4446 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4447 4448 transformed: t.Optional[exp.Expression] = None 4449 cast = exp.Cast if strict else exp.TryCast 4450 4451 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4452 if isinstance(style, exp.Literal) and style.is_int: 4453 from sqlglot.dialects.tsql import TSQL 4454 4455 style_value = style.name 4456 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4457 if not converted_style: 4458 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4459 4460 fmt = exp.Literal.string(converted_style) 4461 4462 if to.this == exp.DataType.Type.DATE: 4463 transformed = exp.StrToDate(this=value, format=fmt) 4464 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4465 transformed = exp.StrToTime(this=value, format=fmt) 4466 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4467 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4468 elif to.this == exp.DataType.Type.TEXT: 4469 transformed = exp.TimeToStr(this=value, format=fmt) 4470 4471 if not transformed: 4472 transformed = cast(this=value, to=to, safe=safe) 4473 4474 return self.sql(transformed) 4475 4476 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4477 this = expression.this 4478 if isinstance(this, exp.JSONPathWildcard): 4479 this = self.json_path_part(this) 4480 return f".{this}" if this else "" 4481 4482 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4483 return f".{this}" 4484 4485 this = self.json_path_part(this) 4486 return ( 4487 f"[{this}]" 4488 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4489 else f".{this}" 4490 ) 4491 4492 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4493 this = self.json_path_part(expression.this) 4494 return f"[{this}]" if this else "" 4495 4496 def _simplify_unless_literal(self, expression: E) -> E: 4497 if not isinstance(expression, exp.Literal): 4498 from sqlglot.optimizer.simplify import simplify 4499 4500 expression = simplify(expression, dialect=self.dialect) 4501 4502 return expression 4503 4504 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4505 this = expression.this 4506 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4507 self.unsupported( 4508 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4509 ) 4510 return self.sql(this) 4511 4512 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4513 # The first modifier here will be the one closest to the AggFunc's arg 4514 mods = sorted( 4515 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4516 key=lambda x: 0 4517 if isinstance(x, exp.HavingMax) 4518 else (1 if isinstance(x, exp.Order) else 2), 4519 ) 4520 4521 if mods: 4522 mod = mods[0] 4523 this = expression.__class__(this=mod.this.copy()) 4524 this.meta["inline"] = True 4525 mod.this.replace(this) 4526 return self.sql(expression.this) 4527 4528 agg_func = expression.find(exp.AggFunc) 4529 4530 if agg_func: 4531 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4532 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4533 4534 return f"{self.sql(expression, 'this')} {text}" 4535 4536 def _replace_line_breaks(self, string: str) -> str: 4537 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4538 if self.pretty: 4539 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4540 return string 4541 4542 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4543 option = self.sql(expression, "this") 4544 4545 if expression.expressions: 4546 upper = option.upper() 4547 4548 # Snowflake FILE_FORMAT options are separated by whitespace 4549 sep = " " if upper == "FILE_FORMAT" else ", " 4550 4551 # Databricks copy/format options do not set their list of values with EQ 4552 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4553 values = self.expressions(expression, flat=True, sep=sep) 4554 return f"{option}{op}({values})" 4555 4556 value = self.sql(expression, "expression") 4557 4558 if not value: 4559 return option 4560 4561 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4562 4563 return f"{option}{op}{value}" 4564 4565 def credentials_sql(self, expression: exp.Credentials) -> str: 4566 cred_expr = expression.args.get("credentials") 4567 if isinstance(cred_expr, exp.Literal): 4568 # Redshift case: CREDENTIALS <string> 4569 credentials = self.sql(expression, "credentials") 4570 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4571 else: 4572 # Snowflake case: CREDENTIALS = (...) 4573 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4574 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4575 4576 storage = self.sql(expression, "storage") 4577 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4578 4579 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4580 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4581 4582 iam_role = self.sql(expression, "iam_role") 4583 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4584 4585 region = self.sql(expression, "region") 4586 region = f" REGION {region}" if region else "" 4587 4588 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4589 4590 def copy_sql(self, expression: exp.Copy) -> str: 4591 this = self.sql(expression, "this") 4592 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4593 4594 credentials = self.sql(expression, "credentials") 4595 credentials = self.seg(credentials) if credentials else "" 4596 files = self.expressions(expression, key="files", flat=True) 4597 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4598 4599 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4600 params = self.expressions( 4601 expression, 4602 key="params", 4603 sep=sep, 4604 new_line=True, 4605 skip_last=True, 4606 skip_first=True, 4607 indent=self.COPY_PARAMS_ARE_WRAPPED, 4608 ) 4609 4610 if params: 4611 if self.COPY_PARAMS_ARE_WRAPPED: 4612 params = f" WITH ({params})" 4613 elif not self.pretty and (files or credentials): 4614 params = f" {params}" 4615 4616 return f"COPY{this}{kind} {files}{credentials}{params}" 4617 4618 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4619 return "" 4620 4621 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4622 on_sql = "ON" if expression.args.get("on") else "OFF" 4623 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4624 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4625 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4626 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4627 4628 if filter_col or retention_period: 4629 on_sql = self.func("ON", filter_col, retention_period) 4630 4631 return f"DATA_DELETION={on_sql}" 4632 4633 def maskingpolicycolumnconstraint_sql( 4634 self, expression: exp.MaskingPolicyColumnConstraint 4635 ) -> str: 4636 this = self.sql(expression, "this") 4637 expressions = self.expressions(expression, flat=True) 4638 expressions = f" USING ({expressions})" if expressions else "" 4639 return f"MASKING POLICY {this}{expressions}" 4640 4641 def gapfill_sql(self, expression: exp.GapFill) -> str: 4642 this = self.sql(expression, "this") 4643 this = f"TABLE {this}" 4644 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4645 4646 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4647 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4648 4649 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4650 this = self.sql(expression, "this") 4651 expr = expression.expression 4652 4653 if isinstance(expr, exp.Func): 4654 # T-SQL's CLR functions are case sensitive 4655 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4656 else: 4657 expr = self.sql(expression, "expression") 4658 4659 return self.scope_resolution(expr, this) 4660 4661 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4662 if self.PARSE_JSON_NAME is None: 4663 return self.sql(expression.this) 4664 4665 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4666 4667 def rand_sql(self, expression: exp.Rand) -> str: 4668 lower = self.sql(expression, "lower") 4669 upper = self.sql(expression, "upper") 4670 4671 if lower and upper: 4672 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4673 return self.func("RAND", expression.this) 4674 4675 def changes_sql(self, expression: exp.Changes) -> str: 4676 information = self.sql(expression, "information") 4677 information = f"INFORMATION => {information}" 4678 at_before = self.sql(expression, "at_before") 4679 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4680 end = self.sql(expression, "end") 4681 end = f"{self.seg('')}{end}" if end else "" 4682 4683 return f"CHANGES ({information}){at_before}{end}" 4684 4685 def pad_sql(self, expression: exp.Pad) -> str: 4686 prefix = "L" if expression.args.get("is_left") else "R" 4687 4688 fill_pattern = self.sql(expression, "fill_pattern") or None 4689 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4690 fill_pattern = "' '" 4691 4692 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4693 4694 def summarize_sql(self, expression: exp.Summarize) -> str: 4695 table = " TABLE" if expression.args.get("table") else "" 4696 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4697 4698 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4699 generate_series = exp.GenerateSeries(**expression.args) 4700 4701 parent = expression.parent 4702 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4703 parent = parent.parent 4704 4705 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4706 return self.sql(exp.Unnest(expressions=[generate_series])) 4707 4708 if isinstance(parent, exp.Select): 4709 self.unsupported("GenerateSeries projection unnesting is not supported.") 4710 4711 return self.sql(generate_series) 4712 4713 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4714 exprs = expression.expressions 4715 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4716 if len(exprs) == 0: 4717 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4718 else: 4719 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4720 else: 4721 rhs = self.expressions(expression) # type: ignore 4722 4723 return self.func(name, expression.this, rhs or None) 4724 4725 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4726 if self.SUPPORTS_CONVERT_TIMEZONE: 4727 return self.function_fallback_sql(expression) 4728 4729 source_tz = expression.args.get("source_tz") 4730 target_tz = expression.args.get("target_tz") 4731 timestamp = expression.args.get("timestamp") 4732 4733 if source_tz and timestamp: 4734 timestamp = exp.AtTimeZone( 4735 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4736 ) 4737 4738 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4739 4740 return self.sql(expr) 4741 4742 def json_sql(self, expression: exp.JSON) -> str: 4743 this = self.sql(expression, "this") 4744 this = f" {this}" if this else "" 4745 4746 _with = expression.args.get("with") 4747 4748 if _with is None: 4749 with_sql = "" 4750 elif not _with: 4751 with_sql = " WITHOUT" 4752 else: 4753 with_sql = " WITH" 4754 4755 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4756 4757 return f"JSON{this}{with_sql}{unique_sql}" 4758 4759 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4760 def _generate_on_options(arg: t.Any) -> str: 4761 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4762 4763 path = self.sql(expression, "path") 4764 returning = self.sql(expression, "returning") 4765 returning = f" RETURNING {returning}" if returning else "" 4766 4767 on_condition = self.sql(expression, "on_condition") 4768 on_condition = f" {on_condition}" if on_condition else "" 4769 4770 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4771 4772 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4773 else_ = "ELSE " if expression.args.get("else_") else "" 4774 condition = self.sql(expression, "expression") 4775 condition = f"WHEN {condition} THEN " if condition else else_ 4776 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4777 return f"{condition}{insert}" 4778 4779 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4780 kind = self.sql(expression, "kind") 4781 expressions = self.seg(self.expressions(expression, sep=" ")) 4782 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4783 return res 4784 4785 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4786 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4787 empty = expression.args.get("empty") 4788 empty = ( 4789 f"DEFAULT {empty} ON EMPTY" 4790 if isinstance(empty, exp.Expression) 4791 else self.sql(expression, "empty") 4792 ) 4793 4794 error = expression.args.get("error") 4795 error = ( 4796 f"DEFAULT {error} ON ERROR" 4797 if isinstance(error, exp.Expression) 4798 else self.sql(expression, "error") 4799 ) 4800 4801 if error and empty: 4802 error = ( 4803 f"{empty} {error}" 4804 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4805 else f"{error} {empty}" 4806 ) 4807 empty = "" 4808 4809 null = self.sql(expression, "null") 4810 4811 return f"{empty}{error}{null}" 4812 4813 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4814 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4815 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4816 4817 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4818 this = self.sql(expression, "this") 4819 path = self.sql(expression, "path") 4820 4821 passing = self.expressions(expression, "passing") 4822 passing = f" PASSING {passing}" if passing else "" 4823 4824 on_condition = self.sql(expression, "on_condition") 4825 on_condition = f" {on_condition}" if on_condition else "" 4826 4827 path = f"{path}{passing}{on_condition}" 4828 4829 return self.func("JSON_EXISTS", this, path) 4830 4831 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4832 array_agg = self.function_fallback_sql(expression) 4833 4834 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4835 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4836 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4837 parent = expression.parent 4838 if isinstance(parent, exp.Filter): 4839 parent_cond = parent.expression.this 4840 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4841 else: 4842 this = expression.this 4843 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4844 if this.find(exp.Column): 4845 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4846 this_sql = ( 4847 self.expressions(this) 4848 if isinstance(this, exp.Distinct) 4849 else self.sql(expression, "this") 4850 ) 4851 4852 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4853 4854 return array_agg 4855 4856 def apply_sql(self, expression: exp.Apply) -> str: 4857 this = self.sql(expression, "this") 4858 expr = self.sql(expression, "expression") 4859 4860 return f"{this} APPLY({expr})" 4861 4862 def _grant_or_revoke_sql( 4863 self, 4864 expression: exp.Grant | exp.Revoke, 4865 keyword: str, 4866 preposition: str, 4867 grant_option_prefix: str = "", 4868 grant_option_suffix: str = "", 4869 ) -> str: 4870 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4871 4872 kind = self.sql(expression, "kind") 4873 kind = f" {kind}" if kind else "" 4874 4875 securable = self.sql(expression, "securable") 4876 securable = f" {securable}" if securable else "" 4877 4878 principals = self.expressions(expression, key="principals", flat=True) 4879 4880 if not expression.args.get("grant_option"): 4881 grant_option_prefix = grant_option_suffix = "" 4882 4883 # cascade for revoke only 4884 cascade = self.sql(expression, "cascade") 4885 cascade = f" {cascade}" if cascade else "" 4886 4887 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 4888 4889 def grant_sql(self, expression: exp.Grant) -> str: 4890 return self._grant_or_revoke_sql( 4891 expression, 4892 keyword="GRANT", 4893 preposition="TO", 4894 grant_option_suffix=" WITH GRANT OPTION", 4895 ) 4896 4897 def revoke_sql(self, expression: exp.Revoke) -> str: 4898 return self._grant_or_revoke_sql( 4899 expression, 4900 keyword="REVOKE", 4901 preposition="FROM", 4902 grant_option_prefix="GRANT OPTION FOR ", 4903 ) 4904 4905 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4906 this = self.sql(expression, "this") 4907 columns = self.expressions(expression, flat=True) 4908 columns = f"({columns})" if columns else "" 4909 4910 return f"{this}{columns}" 4911 4912 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4913 this = self.sql(expression, "this") 4914 4915 kind = self.sql(expression, "kind") 4916 kind = f"{kind} " if kind else "" 4917 4918 return f"{kind}{this}" 4919 4920 def columns_sql(self, expression: exp.Columns): 4921 func = self.function_fallback_sql(expression) 4922 if expression.args.get("unpack"): 4923 func = f"*{func}" 4924 4925 return func 4926 4927 def overlay_sql(self, expression: exp.Overlay): 4928 this = self.sql(expression, "this") 4929 expr = self.sql(expression, "expression") 4930 from_sql = self.sql(expression, "from") 4931 for_sql = self.sql(expression, "for") 4932 for_sql = f" FOR {for_sql}" if for_sql else "" 4933 4934 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4935 4936 @unsupported_args("format") 4937 def todouble_sql(self, expression: exp.ToDouble) -> str: 4938 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4939 4940 def string_sql(self, expression: exp.String) -> str: 4941 this = expression.this 4942 zone = expression.args.get("zone") 4943 4944 if zone: 4945 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4946 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4947 # set for source_tz to transpile the time conversion before the STRING cast 4948 this = exp.ConvertTimezone( 4949 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4950 ) 4951 4952 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4953 4954 def median_sql(self, expression: exp.Median): 4955 if not self.SUPPORTS_MEDIAN: 4956 return self.sql( 4957 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4958 ) 4959 4960 return self.function_fallback_sql(expression) 4961 4962 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4963 filler = self.sql(expression, "this") 4964 filler = f" {filler}" if filler else "" 4965 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4966 return f"TRUNCATE{filler} {with_count}" 4967 4968 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4969 if self.SUPPORTS_UNIX_SECONDS: 4970 return self.function_fallback_sql(expression) 4971 4972 start_ts = exp.cast( 4973 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4974 ) 4975 4976 return self.sql( 4977 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4978 ) 4979 4980 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4981 dim = expression.expression 4982 4983 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4984 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4985 if not (dim.is_int and dim.name == "1"): 4986 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4987 dim = None 4988 4989 # If dimension is required but not specified, default initialize it 4990 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4991 dim = exp.Literal.number(1) 4992 4993 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4994 4995 def attach_sql(self, expression: exp.Attach) -> str: 4996 this = self.sql(expression, "this") 4997 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4998 expressions = self.expressions(expression) 4999 expressions = f" ({expressions})" if expressions else "" 5000 5001 return f"ATTACH{exists_sql} {this}{expressions}" 5002 5003 def detach_sql(self, expression: exp.Detach) -> str: 5004 this = self.sql(expression, "this") 5005 # the DATABASE keyword is required if IF EXISTS is set 5006 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5007 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5008 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5009 5010 return f"DETACH{exists_sql} {this}" 5011 5012 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5013 this = self.sql(expression, "this") 5014 value = self.sql(expression, "expression") 5015 value = f" {value}" if value else "" 5016 return f"{this}{value}" 5017 5018 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5019 return ( 5020 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5021 ) 5022 5023 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5024 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5025 encode = f"{encode} {self.sql(expression, 'this')}" 5026 5027 properties = expression.args.get("properties") 5028 if properties: 5029 encode = f"{encode} {self.properties(properties)}" 5030 5031 return encode 5032 5033 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5034 this = self.sql(expression, "this") 5035 include = f"INCLUDE {this}" 5036 5037 column_def = self.sql(expression, "column_def") 5038 if column_def: 5039 include = f"{include} {column_def}" 5040 5041 alias = self.sql(expression, "alias") 5042 if alias: 5043 include = f"{include} AS {alias}" 5044 5045 return include 5046 5047 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5048 name = f"NAME {self.sql(expression, 'this')}" 5049 return self.func("XMLELEMENT", name, *expression.expressions) 5050 5051 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5052 this = self.sql(expression, "this") 5053 expr = self.sql(expression, "expression") 5054 expr = f"({expr})" if expr else "" 5055 return f"{this}{expr}" 5056 5057 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5058 partitions = self.expressions(expression, "partition_expressions") 5059 create = self.expressions(expression, "create_expressions") 5060 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5061 5062 def partitionbyrangepropertydynamic_sql( 5063 self, expression: exp.PartitionByRangePropertyDynamic 5064 ) -> str: 5065 start = self.sql(expression, "start") 5066 end = self.sql(expression, "end") 5067 5068 every = expression.args["every"] 5069 if isinstance(every, exp.Interval) and every.this.is_string: 5070 every.this.replace(exp.Literal.number(every.name)) 5071 5072 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5073 5074 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5075 name = self.sql(expression, "this") 5076 values = self.expressions(expression, flat=True) 5077 5078 return f"NAME {name} VALUE {values}" 5079 5080 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5081 kind = self.sql(expression, "kind") 5082 sample = self.sql(expression, "sample") 5083 return f"SAMPLE {sample} {kind}" 5084 5085 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5086 kind = self.sql(expression, "kind") 5087 option = self.sql(expression, "option") 5088 option = f" {option}" if option else "" 5089 this = self.sql(expression, "this") 5090 this = f" {this}" if this else "" 5091 columns = self.expressions(expression) 5092 columns = f" {columns}" if columns else "" 5093 return f"{kind}{option} STATISTICS{this}{columns}" 5094 5095 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5096 this = self.sql(expression, "this") 5097 columns = self.expressions(expression) 5098 inner_expression = self.sql(expression, "expression") 5099 inner_expression = f" {inner_expression}" if inner_expression else "" 5100 update_options = self.sql(expression, "update_options") 5101 update_options = f" {update_options} UPDATE" if update_options else "" 5102 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5103 5104 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5105 kind = self.sql(expression, "kind") 5106 kind = f" {kind}" if kind else "" 5107 return f"DELETE{kind} STATISTICS" 5108 5109 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5110 inner_expression = self.sql(expression, "expression") 5111 return f"LIST CHAINED ROWS{inner_expression}" 5112 5113 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5114 kind = self.sql(expression, "kind") 5115 this = self.sql(expression, "this") 5116 this = f" {this}" if this else "" 5117 inner_expression = self.sql(expression, "expression") 5118 return f"VALIDATE {kind}{this}{inner_expression}" 5119 5120 def analyze_sql(self, expression: exp.Analyze) -> str: 5121 options = self.expressions(expression, key="options", sep=" ") 5122 options = f" {options}" if options else "" 5123 kind = self.sql(expression, "kind") 5124 kind = f" {kind}" if kind else "" 5125 this = self.sql(expression, "this") 5126 this = f" {this}" if this else "" 5127 mode = self.sql(expression, "mode") 5128 mode = f" {mode}" if mode else "" 5129 properties = self.sql(expression, "properties") 5130 properties = f" {properties}" if properties else "" 5131 partition = self.sql(expression, "partition") 5132 partition = f" {partition}" if partition else "" 5133 inner_expression = self.sql(expression, "expression") 5134 inner_expression = f" {inner_expression}" if inner_expression else "" 5135 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5136 5137 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5138 this = self.sql(expression, "this") 5139 namespaces = self.expressions(expression, key="namespaces") 5140 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5141 passing = self.expressions(expression, key="passing") 5142 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5143 columns = self.expressions(expression, key="columns") 5144 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5145 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5146 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5147 5148 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5149 this = self.sql(expression, "this") 5150 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5151 5152 def export_sql(self, expression: exp.Export) -> str: 5153 this = self.sql(expression, "this") 5154 connection = self.sql(expression, "connection") 5155 connection = f"WITH CONNECTION {connection} " if connection else "" 5156 options = self.sql(expression, "options") 5157 return f"EXPORT DATA {connection}{options} AS {this}" 5158 5159 def declare_sql(self, expression: exp.Declare) -> str: 5160 return f"DECLARE {self.expressions(expression, flat=True)}" 5161 5162 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5163 variable = self.sql(expression, "this") 5164 default = self.sql(expression, "default") 5165 default = f" = {default}" if default else "" 5166 5167 kind = self.sql(expression, "kind") 5168 if isinstance(expression.args.get("kind"), exp.Schema): 5169 kind = f"TABLE {kind}" 5170 5171 return f"{variable} AS {kind}{default}" 5172 5173 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5174 kind = self.sql(expression, "kind") 5175 this = self.sql(expression, "this") 5176 set = self.sql(expression, "expression") 5177 using = self.sql(expression, "using") 5178 using = f" USING {using}" if using else "" 5179 5180 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5181 5182 return f"{kind_sql} {this} SET {set}{using}" 5183 5184 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5185 params = self.expressions(expression, key="params", flat=True) 5186 return self.func(expression.name, *expression.expressions) + f"({params})" 5187 5188 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5189 return self.func(expression.name, *expression.expressions) 5190 5191 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5192 return self.anonymousaggfunc_sql(expression) 5193 5194 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5195 return self.parameterizedagg_sql(expression) 5196 5197 def show_sql(self, expression: exp.Show) -> str: 5198 self.unsupported("Unsupported SHOW statement") 5199 return "" 5200 5201 def install_sql(self, expression: exp.Install) -> str: 5202 self.unsupported("Unsupported INSTALL statement") 5203 return "" 5204 5205 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5206 # Snowflake GET/PUT statements: 5207 # PUT <file> <internalStage> <properties> 5208 # GET <internalStage> <file> <properties> 5209 props = expression.args.get("properties") 5210 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5211 this = self.sql(expression, "this") 5212 target = self.sql(expression, "target") 5213 5214 if isinstance(expression, exp.Put): 5215 return f"PUT {this} {target}{props_sql}" 5216 else: 5217 return f"GET {target} {this}{props_sql}" 5218 5219 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5220 this = self.sql(expression, "this") 5221 expr = self.sql(expression, "expression") 5222 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5223 return f"TRANSLATE({this} USING {expr}{with_error})" 5224 5225 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5226 if self.SUPPORTS_DECODE_CASE: 5227 return self.func("DECODE", *expression.expressions) 5228 5229 expression, *expressions = expression.expressions 5230 5231 ifs = [] 5232 for search, result in zip(expressions[::2], expressions[1::2]): 5233 if isinstance(search, exp.Literal): 5234 ifs.append(exp.If(this=expression.eq(search), true=result)) 5235 elif isinstance(search, exp.Null): 5236 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5237 else: 5238 if isinstance(search, exp.Binary): 5239 search = exp.paren(search) 5240 5241 cond = exp.or_( 5242 expression.eq(search), 5243 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5244 copy=False, 5245 ) 5246 ifs.append(exp.If(this=cond, true=result)) 5247 5248 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5249 return self.sql(case) 5250 5251 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5252 this = self.sql(expression, "this") 5253 this = self.seg(this, sep="") 5254 dimensions = self.expressions( 5255 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5256 ) 5257 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5258 metrics = self.expressions( 5259 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5260 ) 5261 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5262 where = self.sql(expression, "where") 5263 where = self.seg(f"WHERE {where}") if where else "" 5264 body = self.indent(this + metrics + dimensions + where, skip_first=True) 5265 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5266 5267 def getextract_sql(self, expression: exp.GetExtract) -> str: 5268 this = expression.this 5269 expr = expression.expression 5270 5271 if not this.type or not expression.type: 5272 from sqlglot.optimizer.annotate_types import annotate_types 5273 5274 this = annotate_types(this, dialect=self.dialect) 5275 5276 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5277 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5278 5279 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5280 5281 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5282 return self.sql( 5283 exp.DateAdd( 5284 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5285 expression=expression.this, 5286 unit=exp.var("DAY"), 5287 ) 5288 ) 5289 5290 def space_sql(self: Generator, expression: exp.Space) -> str: 5291 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5292 5293 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5294 return f"BUILD {self.sql(expression, 'this')}" 5295 5296 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5297 method = self.sql(expression, "method") 5298 kind = expression.args.get("kind") 5299 if not kind: 5300 return f"REFRESH {method}" 5301 5302 every = self.sql(expression, "every") 5303 unit = self.sql(expression, "unit") 5304 every = f" EVERY {every} {unit}" if every else "" 5305 starts = self.sql(expression, "starts") 5306 starts = f" STARTS {starts}" if starts else "" 5307 5308 return f"REFRESH {method} ON {kind}{every}{starts}" 5309 5310 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5311 self.unsupported("The model!attribute syntax is not supported") 5312 return ""
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.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, "?|"), 165 exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, "?&"), 166 exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, "#-"), 167 exp.LanguageProperty: lambda self, e: self.naked_property(e), 168 exp.LocationProperty: lambda self, e: self.naked_property(e), 169 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 170 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 171 exp.NonClusteredColumnConstraint: lambda self, 172 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 173 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 174 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 175 exp.OnCommitProperty: lambda _, 176 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 177 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 178 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 179 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 180 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 181 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 182 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 183 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 184 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 185 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 186 exp.ProjectionPolicyColumnConstraint: lambda self, 187 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 188 exp.Put: lambda self, e: self.get_put_sql(e), 189 exp.RemoteWithConnectionModelProperty: lambda self, 190 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 191 exp.ReturnsProperty: lambda self, e: ( 192 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 193 ), 194 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 195 exp.SecureProperty: lambda *_: "SECURE", 196 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 197 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 198 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 199 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 200 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 201 exp.SqlReadWriteProperty: lambda _, e: e.name, 202 exp.SqlSecurityProperty: lambda _, 203 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 204 exp.StabilityProperty: lambda _, e: e.name, 205 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 206 exp.StreamingTableProperty: lambda *_: "STREAMING", 207 exp.StrictProperty: lambda *_: "STRICT", 208 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 209 exp.TableColumn: lambda self, e: self.sql(e.this), 210 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 211 exp.TemporaryProperty: lambda *_: "TEMPORARY", 212 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 213 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 214 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 215 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 216 exp.TransientProperty: lambda *_: "TRANSIENT", 217 exp.Union: lambda self, e: self.set_operations(e), 218 exp.UnloggedProperty: lambda *_: "UNLOGGED", 219 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 220 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 221 exp.Uuid: lambda *_: "UUID()", 222 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 223 exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string("UTC"))), 224 exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string("UTC"))), 225 exp.UtcTimestamp: lambda self, e: self.sql( 226 exp.CurrentTimestamp(this=exp.Literal.string("UTC")) 227 ), 228 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 229 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 230 exp.VolatileProperty: lambda *_: "VOLATILE", 231 exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})", 232 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 233 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 234 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 235 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 236 exp.ForceProperty: lambda *_: "FORCE", 237 } 238 239 # Whether null ordering is supported in order by 240 # True: Full Support, None: No support, False: No support for certain cases 241 # such as window specifications, aggregate functions etc 242 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 243 244 # Whether ignore nulls is inside the agg or outside. 245 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 246 IGNORE_NULLS_IN_FUNC = False 247 248 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 249 LOCKING_READS_SUPPORTED = False 250 251 # Whether the EXCEPT and INTERSECT operations can return duplicates 252 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 253 254 # Wrap derived values in parens, usually standard but spark doesn't support it 255 WRAP_DERIVED_VALUES = True 256 257 # Whether create function uses an AS before the RETURN 258 CREATE_FUNCTION_RETURN_AS = True 259 260 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 261 MATCHED_BY_SOURCE = True 262 263 # Whether the INTERVAL expression works only with values like '1 day' 264 SINGLE_STRING_INTERVAL = False 265 266 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 267 INTERVAL_ALLOWS_PLURAL_FORM = True 268 269 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 270 LIMIT_FETCH = "ALL" 271 272 # Whether limit and fetch allows expresions or just limits 273 LIMIT_ONLY_LITERALS = False 274 275 # Whether a table is allowed to be renamed with a db 276 RENAME_TABLE_WITH_DB = True 277 278 # The separator for grouping sets and rollups 279 GROUPINGS_SEP = "," 280 281 # The string used for creating an index on a table 282 INDEX_ON = "ON" 283 284 # Whether join hints should be generated 285 JOIN_HINTS = True 286 287 # Whether table hints should be generated 288 TABLE_HINTS = True 289 290 # Whether query hints should be generated 291 QUERY_HINTS = True 292 293 # What kind of separator to use for query hints 294 QUERY_HINT_SEP = ", " 295 296 # Whether comparing against booleans (e.g. x IS TRUE) is supported 297 IS_BOOL_ALLOWED = True 298 299 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 300 DUPLICATE_KEY_UPDATE_WITH_SET = True 301 302 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 303 LIMIT_IS_TOP = False 304 305 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 306 RETURNING_END = True 307 308 # Whether to generate an unquoted value for EXTRACT's date part argument 309 EXTRACT_ALLOWS_QUOTES = True 310 311 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 312 TZ_TO_WITH_TIME_ZONE = False 313 314 # Whether the NVL2 function is supported 315 NVL2_SUPPORTED = True 316 317 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 318 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 319 320 # Whether VALUES statements can be used as derived tables. 321 # MySQL 5 and Redshift do not allow this, so when False, it will convert 322 # SELECT * VALUES into SELECT UNION 323 VALUES_AS_TABLE = True 324 325 # Whether the word COLUMN is included when adding a column with ALTER TABLE 326 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 327 328 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 329 UNNEST_WITH_ORDINALITY = True 330 331 # Whether FILTER (WHERE cond) can be used for conditional aggregation 332 AGGREGATE_FILTER_SUPPORTED = True 333 334 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 335 SEMI_ANTI_JOIN_WITH_SIDE = True 336 337 # Whether to include the type of a computed column in the CREATE DDL 338 COMPUTED_COLUMN_WITH_TYPE = True 339 340 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 341 SUPPORTS_TABLE_COPY = True 342 343 # Whether parentheses are required around the table sample's expression 344 TABLESAMPLE_REQUIRES_PARENS = True 345 346 # Whether a table sample clause's size needs to be followed by the ROWS keyword 347 TABLESAMPLE_SIZE_IS_ROWS = True 348 349 # The keyword(s) to use when generating a sample clause 350 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 351 352 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 353 TABLESAMPLE_WITH_METHOD = True 354 355 # The keyword to use when specifying the seed of a sample clause 356 TABLESAMPLE_SEED_KEYWORD = "SEED" 357 358 # Whether COLLATE is a function instead of a binary operator 359 COLLATE_IS_FUNC = False 360 361 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 362 DATA_TYPE_SPECIFIERS_ALLOWED = False 363 364 # Whether conditions require booleans WHERE x = 0 vs WHERE x 365 ENSURE_BOOLS = False 366 367 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 368 CTE_RECURSIVE_KEYWORD_REQUIRED = True 369 370 # Whether CONCAT requires >1 arguments 371 SUPPORTS_SINGLE_ARG_CONCAT = True 372 373 # Whether LAST_DAY function supports a date part argument 374 LAST_DAY_SUPPORTS_DATE_PART = True 375 376 # Whether named columns are allowed in table aliases 377 SUPPORTS_TABLE_ALIAS_COLUMNS = True 378 379 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 380 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 381 382 # What delimiter to use for separating JSON key/value pairs 383 JSON_KEY_VALUE_PAIR_SEP = ":" 384 385 # INSERT OVERWRITE TABLE x override 386 INSERT_OVERWRITE = " OVERWRITE TABLE" 387 388 # Whether the SELECT .. INTO syntax is used instead of CTAS 389 SUPPORTS_SELECT_INTO = False 390 391 # Whether UNLOGGED tables can be created 392 SUPPORTS_UNLOGGED_TABLES = False 393 394 # Whether the CREATE TABLE LIKE statement is supported 395 SUPPORTS_CREATE_TABLE_LIKE = True 396 397 # Whether the LikeProperty needs to be specified inside of the schema clause 398 LIKE_PROPERTY_INSIDE_SCHEMA = False 399 400 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 401 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 402 MULTI_ARG_DISTINCT = True 403 404 # Whether the JSON extraction operators expect a value of type JSON 405 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 406 407 # Whether bracketed keys like ["foo"] are supported in JSON paths 408 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 409 410 # Whether to escape keys using single quotes in JSON paths 411 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 412 413 # The JSONPathPart expressions supported by this dialect 414 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 415 416 # Whether any(f(x) for x in array) can be implemented by this dialect 417 CAN_IMPLEMENT_ARRAY_ANY = False 418 419 # Whether the function TO_NUMBER is supported 420 SUPPORTS_TO_NUMBER = True 421 422 # Whether EXCLUDE in window specification is supported 423 SUPPORTS_WINDOW_EXCLUDE = False 424 425 # Whether or not set op modifiers apply to the outer set op or select. 426 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 427 # True means limit 1 happens after the set op, False means it it happens on y. 428 SET_OP_MODIFIERS = True 429 430 # Whether parameters from COPY statement are wrapped in parentheses 431 COPY_PARAMS_ARE_WRAPPED = True 432 433 # Whether values of params are set with "=" token or empty space 434 COPY_PARAMS_EQ_REQUIRED = False 435 436 # Whether COPY statement has INTO keyword 437 COPY_HAS_INTO_KEYWORD = True 438 439 # Whether the conditional TRY(expression) function is supported 440 TRY_SUPPORTED = True 441 442 # Whether the UESCAPE syntax in unicode strings is supported 443 SUPPORTS_UESCAPE = True 444 445 # Function used to replace escaped unicode codes in unicode strings 446 UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None 447 448 # The keyword to use when generating a star projection with excluded columns 449 STAR_EXCEPT = "EXCEPT" 450 451 # The HEX function name 452 HEX_FUNC = "HEX" 453 454 # The keywords to use when prefixing & separating WITH based properties 455 WITH_PROPERTIES_PREFIX = "WITH" 456 457 # Whether to quote the generated expression of exp.JsonPath 458 QUOTE_JSON_PATH = True 459 460 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 461 PAD_FILL_PATTERN_IS_REQUIRED = False 462 463 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 464 SUPPORTS_EXPLODING_PROJECTIONS = True 465 466 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 467 ARRAY_CONCAT_IS_VAR_LEN = True 468 469 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 470 SUPPORTS_CONVERT_TIMEZONE = False 471 472 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 473 SUPPORTS_MEDIAN = True 474 475 # Whether UNIX_SECONDS(timestamp) is supported 476 SUPPORTS_UNIX_SECONDS = False 477 478 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 479 ALTER_SET_WRAPPED = False 480 481 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 482 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 483 # TODO: The normalization should be done by default once we've tested it across all dialects. 484 NORMALIZE_EXTRACT_DATE_PARTS = False 485 486 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 487 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 488 489 # The function name of the exp.ArraySize expression 490 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 491 492 # The syntax to use when altering the type of a column 493 ALTER_SET_TYPE = "SET DATA TYPE" 494 495 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 496 # None -> Doesn't support it at all 497 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 498 # True (Postgres) -> Explicitly requires it 499 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 500 501 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 502 SUPPORTS_DECODE_CASE = True 503 504 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 505 SUPPORTS_BETWEEN_FLAGS = False 506 507 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 508 SUPPORTS_LIKE_QUANTIFIERS = True 509 510 # Prefix which is appended to exp.Table expressions in MATCH AGAINST 511 MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None 512 513 TYPE_MAPPING = { 514 exp.DataType.Type.DATETIME2: "TIMESTAMP", 515 exp.DataType.Type.NCHAR: "CHAR", 516 exp.DataType.Type.NVARCHAR: "VARCHAR", 517 exp.DataType.Type.MEDIUMTEXT: "TEXT", 518 exp.DataType.Type.LONGTEXT: "TEXT", 519 exp.DataType.Type.TINYTEXT: "TEXT", 520 exp.DataType.Type.BLOB: "VARBINARY", 521 exp.DataType.Type.MEDIUMBLOB: "BLOB", 522 exp.DataType.Type.LONGBLOB: "BLOB", 523 exp.DataType.Type.TINYBLOB: "BLOB", 524 exp.DataType.Type.INET: "INET", 525 exp.DataType.Type.ROWVERSION: "VARBINARY", 526 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 527 } 528 529 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 530 531 TIME_PART_SINGULARS = { 532 "MICROSECONDS": "MICROSECOND", 533 "SECONDS": "SECOND", 534 "MINUTES": "MINUTE", 535 "HOURS": "HOUR", 536 "DAYS": "DAY", 537 "WEEKS": "WEEK", 538 "MONTHS": "MONTH", 539 "QUARTERS": "QUARTER", 540 "YEARS": "YEAR", 541 } 542 543 AFTER_HAVING_MODIFIER_TRANSFORMS = { 544 "cluster": lambda self, e: self.sql(e, "cluster"), 545 "distribute": lambda self, e: self.sql(e, "distribute"), 546 "sort": lambda self, e: self.sql(e, "sort"), 547 "windows": lambda self, e: ( 548 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 549 if e.args.get("windows") 550 else "" 551 ), 552 "qualify": lambda self, e: self.sql(e, "qualify"), 553 } 554 555 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 556 557 STRUCT_DELIMITER = ("<", ">") 558 559 PARAMETER_TOKEN = "@" 560 NAMED_PLACEHOLDER_TOKEN = ":" 561 562 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 563 564 PROPERTIES_LOCATION = { 565 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 567 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 571 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 573 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 576 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 579 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 580 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 582 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 583 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 585 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 589 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 593 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 594 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 595 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 596 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 597 exp.HeapProperty: exp.Properties.Location.POST_WITH, 598 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 600 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 603 exp.JournalProperty: exp.Properties.Location.POST_NAME, 604 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 609 exp.LogProperty: exp.Properties.Location.POST_NAME, 610 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 611 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 612 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 613 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 615 exp.Order: exp.Properties.Location.POST_SCHEMA, 616 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 618 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 620 exp.Property: exp.Properties.Location.POST_WITH, 621 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 629 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 630 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 631 exp.Set: exp.Properties.Location.POST_SCHEMA, 632 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.SetProperty: exp.Properties.Location.POST_CREATE, 634 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 636 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 637 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 640 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 641 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 642 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 643 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 644 exp.Tags: exp.Properties.Location.POST_WITH, 645 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 646 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 647 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 648 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 649 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 650 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 651 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 652 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 653 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 654 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 655 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 656 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 657 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 658 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 659 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 660 } 661 662 # Keywords that can't be used as unquoted identifier names 663 RESERVED_KEYWORDS: t.Set[str] = set() 664 665 # Expressions whose comments are separated from them for better formatting 666 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 667 exp.Command, 668 exp.Create, 669 exp.Describe, 670 exp.Delete, 671 exp.Drop, 672 exp.From, 673 exp.Insert, 674 exp.Join, 675 exp.MultitableInserts, 676 exp.Order, 677 exp.Group, 678 exp.Having, 679 exp.Select, 680 exp.SetOperation, 681 exp.Update, 682 exp.Where, 683 exp.With, 684 ) 685 686 # Expressions that should not have their comments generated in maybe_comment 687 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 688 exp.Binary, 689 exp.SetOperation, 690 ) 691 692 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 693 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 694 exp.Column, 695 exp.Literal, 696 exp.Neg, 697 exp.Paren, 698 ) 699 700 PARAMETERIZABLE_TEXT_TYPES = { 701 exp.DataType.Type.NVARCHAR, 702 exp.DataType.Type.VARCHAR, 703 exp.DataType.Type.CHAR, 704 exp.DataType.Type.NCHAR, 705 } 706 707 # Expressions that need to have all CTEs under them bubbled up to them 708 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 709 710 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 711 712 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 713 714 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 715 716 __slots__ = ( 717 "pretty", 718 "identify", 719 "normalize", 720 "pad", 721 "_indent", 722 "normalize_functions", 723 "unsupported_level", 724 "max_unsupported", 725 "leading_comma", 726 "max_text_width", 727 "comments", 728 "dialect", 729 "unsupported_messages", 730 "_escaped_quote_end", 731 "_escaped_identifier_end", 732 "_next_name", 733 "_identifier_start", 734 "_identifier_end", 735 "_quote_json_path_key_using_brackets", 736 ) 737 738 def __init__( 739 self, 740 pretty: t.Optional[bool] = None, 741 identify: str | bool = False, 742 normalize: bool = False, 743 pad: int = 2, 744 indent: int = 2, 745 normalize_functions: t.Optional[str | bool] = None, 746 unsupported_level: ErrorLevel = ErrorLevel.WARN, 747 max_unsupported: int = 3, 748 leading_comma: bool = False, 749 max_text_width: int = 80, 750 comments: bool = True, 751 dialect: DialectType = None, 752 ): 753 import sqlglot 754 from sqlglot.dialects import Dialect 755 756 self.pretty = pretty if pretty is not None else sqlglot.pretty 757 self.identify = identify 758 self.normalize = normalize 759 self.pad = pad 760 self._indent = indent 761 self.unsupported_level = unsupported_level 762 self.max_unsupported = max_unsupported 763 self.leading_comma = leading_comma 764 self.max_text_width = max_text_width 765 self.comments = comments 766 self.dialect = Dialect.get_or_raise(dialect) 767 768 # This is both a Dialect property and a Generator argument, so we prioritize the latter 769 self.normalize_functions = ( 770 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 771 ) 772 773 self.unsupported_messages: t.List[str] = [] 774 self._escaped_quote_end: str = ( 775 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 776 ) 777 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 778 779 self._next_name = name_sequence("_t") 780 781 self._identifier_start = self.dialect.IDENTIFIER_START 782 self._identifier_end = self.dialect.IDENTIFIER_END 783 784 self._quote_json_path_key_using_brackets = True 785 786 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 787 """ 788 Generates the SQL string corresponding to the given syntax tree. 789 790 Args: 791 expression: The syntax tree. 792 copy: Whether to copy the expression. The generator performs mutations so 793 it is safer to copy. 794 795 Returns: 796 The SQL string corresponding to `expression`. 797 """ 798 if copy: 799 expression = expression.copy() 800 801 expression = self.preprocess(expression) 802 803 self.unsupported_messages = [] 804 sql = self.sql(expression).strip() 805 806 if self.pretty: 807 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 808 809 if self.unsupported_level == ErrorLevel.IGNORE: 810 return sql 811 812 if self.unsupported_level == ErrorLevel.WARN: 813 for msg in self.unsupported_messages: 814 logger.warning(msg) 815 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 816 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 817 818 return sql 819 820 def preprocess(self, expression: exp.Expression) -> exp.Expression: 821 """Apply generic preprocessing transformations to a given expression.""" 822 expression = self._move_ctes_to_top_level(expression) 823 824 if self.ENSURE_BOOLS: 825 from sqlglot.transforms import ensure_bools 826 827 expression = ensure_bools(expression) 828 829 return expression 830 831 def _move_ctes_to_top_level(self, expression: E) -> E: 832 if ( 833 not expression.parent 834 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 835 and any(node.parent is not expression for node in expression.find_all(exp.With)) 836 ): 837 from sqlglot.transforms import move_ctes_to_top_level 838 839 expression = move_ctes_to_top_level(expression) 840 return expression 841 842 def unsupported(self, message: str) -> None: 843 if self.unsupported_level == ErrorLevel.IMMEDIATE: 844 raise UnsupportedError(message) 845 self.unsupported_messages.append(message) 846 847 def sep(self, sep: str = " ") -> str: 848 return f"{sep.strip()}\n" if self.pretty else sep 849 850 def seg(self, sql: str, sep: str = " ") -> str: 851 return f"{self.sep(sep)}{sql}" 852 853 def sanitize_comment(self, comment: str) -> str: 854 comment = " " + comment if comment[0].strip() else comment 855 comment = comment + " " if comment[-1].strip() else comment 856 857 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 858 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 859 comment = comment.replace("*/", "* /") 860 861 return comment 862 863 def maybe_comment( 864 self, 865 sql: str, 866 expression: t.Optional[exp.Expression] = None, 867 comments: t.Optional[t.List[str]] = None, 868 separated: bool = False, 869 ) -> str: 870 comments = ( 871 ((expression and expression.comments) if comments is None else comments) # type: ignore 872 if self.comments 873 else None 874 ) 875 876 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 877 return sql 878 879 comments_sql = " ".join( 880 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 881 ) 882 883 if not comments_sql: 884 return sql 885 886 comments_sql = self._replace_line_breaks(comments_sql) 887 888 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 889 return ( 890 f"{self.sep()}{comments_sql}{sql}" 891 if not sql or sql[0].isspace() 892 else f"{comments_sql}{self.sep()}{sql}" 893 ) 894 895 return f"{sql} {comments_sql}" 896 897 def wrap(self, expression: exp.Expression | str) -> str: 898 this_sql = ( 899 self.sql(expression) 900 if isinstance(expression, exp.UNWRAPPED_QUERIES) 901 else self.sql(expression, "this") 902 ) 903 if not this_sql: 904 return "()" 905 906 this_sql = self.indent(this_sql, level=1, pad=0) 907 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 908 909 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 910 original = self.identify 911 self.identify = False 912 result = func(*args, **kwargs) 913 self.identify = original 914 return result 915 916 def normalize_func(self, name: str) -> str: 917 if self.normalize_functions == "upper" or self.normalize_functions is True: 918 return name.upper() 919 if self.normalize_functions == "lower": 920 return name.lower() 921 return name 922 923 def indent( 924 self, 925 sql: str, 926 level: int = 0, 927 pad: t.Optional[int] = None, 928 skip_first: bool = False, 929 skip_last: bool = False, 930 ) -> str: 931 if not self.pretty or not sql: 932 return sql 933 934 pad = self.pad if pad is None else pad 935 lines = sql.split("\n") 936 937 return "\n".join( 938 ( 939 line 940 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 941 else f"{' ' * (level * self._indent + pad)}{line}" 942 ) 943 for i, line in enumerate(lines) 944 ) 945 946 def sql( 947 self, 948 expression: t.Optional[str | exp.Expression], 949 key: t.Optional[str] = None, 950 comment: bool = True, 951 ) -> str: 952 if not expression: 953 return "" 954 955 if isinstance(expression, str): 956 return expression 957 958 if key: 959 value = expression.args.get(key) 960 if value: 961 return self.sql(value) 962 return "" 963 964 transform = self.TRANSFORMS.get(expression.__class__) 965 966 if callable(transform): 967 sql = transform(self, expression) 968 elif isinstance(expression, exp.Expression): 969 exp_handler_name = f"{expression.key}_sql" 970 971 if hasattr(self, exp_handler_name): 972 sql = getattr(self, exp_handler_name)(expression) 973 elif isinstance(expression, exp.Func): 974 sql = self.function_fallback_sql(expression) 975 elif isinstance(expression, exp.Property): 976 sql = self.property_sql(expression) 977 else: 978 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 979 else: 980 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 981 982 return self.maybe_comment(sql, expression) if self.comments and comment else sql 983 984 def uncache_sql(self, expression: exp.Uncache) -> str: 985 table = self.sql(expression, "this") 986 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 987 return f"UNCACHE TABLE{exists_sql} {table}" 988 989 def cache_sql(self, expression: exp.Cache) -> str: 990 lazy = " LAZY" if expression.args.get("lazy") else "" 991 table = self.sql(expression, "this") 992 options = expression.args.get("options") 993 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 994 sql = self.sql(expression, "expression") 995 sql = f" AS{self.sep()}{sql}" if sql else "" 996 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 997 return self.prepend_ctes(expression, sql) 998 999 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1000 if isinstance(expression.parent, exp.Cast): 1001 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1002 default = "DEFAULT " if expression.args.get("default") else "" 1003 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 1004 1005 def column_parts(self, expression: exp.Column) -> str: 1006 return ".".join( 1007 self.sql(part) 1008 for part in ( 1009 expression.args.get("catalog"), 1010 expression.args.get("db"), 1011 expression.args.get("table"), 1012 expression.args.get("this"), 1013 ) 1014 if part 1015 ) 1016 1017 def column_sql(self, expression: exp.Column) -> str: 1018 join_mark = " (+)" if expression.args.get("join_mark") else "" 1019 1020 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1021 join_mark = "" 1022 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1023 1024 return f"{self.column_parts(expression)}{join_mark}" 1025 1026 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1027 this = self.sql(expression, "this") 1028 this = f" {this}" if this else "" 1029 position = self.sql(expression, "position") 1030 return f"{position}{this}" 1031 1032 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1033 column = self.sql(expression, "this") 1034 kind = self.sql(expression, "kind") 1035 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1036 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1037 kind = f"{sep}{kind}" if kind else "" 1038 constraints = f" {constraints}" if constraints else "" 1039 position = self.sql(expression, "position") 1040 position = f" {position}" if position else "" 1041 1042 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1043 kind = "" 1044 1045 return f"{exists}{column}{kind}{constraints}{position}" 1046 1047 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1048 this = self.sql(expression, "this") 1049 kind_sql = self.sql(expression, "kind").strip() 1050 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1051 1052 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1053 this = self.sql(expression, "this") 1054 if expression.args.get("not_null"): 1055 persisted = " PERSISTED NOT NULL" 1056 elif expression.args.get("persisted"): 1057 persisted = " PERSISTED" 1058 else: 1059 persisted = "" 1060 1061 return f"AS {this}{persisted}" 1062 1063 def autoincrementcolumnconstraint_sql(self, _) -> str: 1064 return self.token_sql(TokenType.AUTO_INCREMENT) 1065 1066 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1067 if isinstance(expression.this, list): 1068 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1069 else: 1070 this = self.sql(expression, "this") 1071 1072 return f"COMPRESS {this}" 1073 1074 def generatedasidentitycolumnconstraint_sql( 1075 self, expression: exp.GeneratedAsIdentityColumnConstraint 1076 ) -> str: 1077 this = "" 1078 if expression.this is not None: 1079 on_null = " ON NULL" if expression.args.get("on_null") else "" 1080 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1081 1082 start = expression.args.get("start") 1083 start = f"START WITH {start}" if start else "" 1084 increment = expression.args.get("increment") 1085 increment = f" INCREMENT BY {increment}" if increment else "" 1086 minvalue = expression.args.get("minvalue") 1087 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1088 maxvalue = expression.args.get("maxvalue") 1089 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1090 cycle = expression.args.get("cycle") 1091 cycle_sql = "" 1092 1093 if cycle is not None: 1094 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1095 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1096 1097 sequence_opts = "" 1098 if start or increment or cycle_sql: 1099 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1100 sequence_opts = f" ({sequence_opts.strip()})" 1101 1102 expr = self.sql(expression, "expression") 1103 expr = f"({expr})" if expr else "IDENTITY" 1104 1105 return f"GENERATED{this} AS {expr}{sequence_opts}" 1106 1107 def generatedasrowcolumnconstraint_sql( 1108 self, expression: exp.GeneratedAsRowColumnConstraint 1109 ) -> str: 1110 start = "START" if expression.args.get("start") else "END" 1111 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1112 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1113 1114 def periodforsystemtimeconstraint_sql( 1115 self, expression: exp.PeriodForSystemTimeConstraint 1116 ) -> str: 1117 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1118 1119 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1120 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1121 1122 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1123 desc = expression.args.get("desc") 1124 if desc is not None: 1125 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1126 options = self.expressions(expression, key="options", flat=True, sep=" ") 1127 options = f" {options}" if options else "" 1128 return f"PRIMARY KEY{options}" 1129 1130 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1131 this = self.sql(expression, "this") 1132 this = f" {this}" if this else "" 1133 index_type = expression.args.get("index_type") 1134 index_type = f" USING {index_type}" if index_type else "" 1135 on_conflict = self.sql(expression, "on_conflict") 1136 on_conflict = f" {on_conflict}" if on_conflict else "" 1137 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1138 options = self.expressions(expression, key="options", flat=True, sep=" ") 1139 options = f" {options}" if options else "" 1140 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1141 1142 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1143 return self.sql(expression, "this") 1144 1145 def create_sql(self, expression: exp.Create) -> str: 1146 kind = self.sql(expression, "kind") 1147 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1148 properties = expression.args.get("properties") 1149 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1150 1151 this = self.createable_sql(expression, properties_locs) 1152 1153 properties_sql = "" 1154 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1155 exp.Properties.Location.POST_WITH 1156 ): 1157 props_ast = exp.Properties( 1158 expressions=[ 1159 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1160 *properties_locs[exp.Properties.Location.POST_WITH], 1161 ] 1162 ) 1163 props_ast.parent = expression 1164 properties_sql = self.sql(props_ast) 1165 1166 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1167 properties_sql = self.sep() + properties_sql 1168 elif not self.pretty: 1169 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1170 properties_sql = f" {properties_sql}" 1171 1172 begin = " BEGIN" if expression.args.get("begin") else "" 1173 end = " END" if expression.args.get("end") else "" 1174 1175 expression_sql = self.sql(expression, "expression") 1176 if expression_sql: 1177 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1178 1179 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1180 postalias_props_sql = "" 1181 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1182 postalias_props_sql = self.properties( 1183 exp.Properties( 1184 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1185 ), 1186 wrapped=False, 1187 ) 1188 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1189 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1190 1191 postindex_props_sql = "" 1192 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1193 postindex_props_sql = self.properties( 1194 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1195 wrapped=False, 1196 prefix=" ", 1197 ) 1198 1199 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1200 indexes = f" {indexes}" if indexes else "" 1201 index_sql = indexes + postindex_props_sql 1202 1203 replace = " OR REPLACE" if expression.args.get("replace") else "" 1204 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1205 unique = " UNIQUE" if expression.args.get("unique") else "" 1206 1207 clustered = expression.args.get("clustered") 1208 if clustered is None: 1209 clustered_sql = "" 1210 elif clustered: 1211 clustered_sql = " CLUSTERED COLUMNSTORE" 1212 else: 1213 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1214 1215 postcreate_props_sql = "" 1216 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1217 postcreate_props_sql = self.properties( 1218 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1219 sep=" ", 1220 prefix=" ", 1221 wrapped=False, 1222 ) 1223 1224 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1225 1226 postexpression_props_sql = "" 1227 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1228 postexpression_props_sql = self.properties( 1229 exp.Properties( 1230 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1231 ), 1232 sep=" ", 1233 prefix=" ", 1234 wrapped=False, 1235 ) 1236 1237 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1238 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1239 no_schema_binding = ( 1240 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1241 ) 1242 1243 clone = self.sql(expression, "clone") 1244 clone = f" {clone}" if clone else "" 1245 1246 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1247 properties_expression = f"{expression_sql}{properties_sql}" 1248 else: 1249 properties_expression = f"{properties_sql}{expression_sql}" 1250 1251 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1252 return self.prepend_ctes(expression, expression_sql) 1253 1254 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1255 start = self.sql(expression, "start") 1256 start = f"START WITH {start}" if start else "" 1257 increment = self.sql(expression, "increment") 1258 increment = f" INCREMENT BY {increment}" if increment else "" 1259 minvalue = self.sql(expression, "minvalue") 1260 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1261 maxvalue = self.sql(expression, "maxvalue") 1262 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1263 owned = self.sql(expression, "owned") 1264 owned = f" OWNED BY {owned}" if owned else "" 1265 1266 cache = expression.args.get("cache") 1267 if cache is None: 1268 cache_str = "" 1269 elif cache is True: 1270 cache_str = " CACHE" 1271 else: 1272 cache_str = f" CACHE {cache}" 1273 1274 options = self.expressions(expression, key="options", flat=True, sep=" ") 1275 options = f" {options}" if options else "" 1276 1277 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1278 1279 def clone_sql(self, expression: exp.Clone) -> str: 1280 this = self.sql(expression, "this") 1281 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1282 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1283 return f"{shallow}{keyword} {this}" 1284 1285 def describe_sql(self, expression: exp.Describe) -> str: 1286 style = expression.args.get("style") 1287 style = f" {style}" if style else "" 1288 partition = self.sql(expression, "partition") 1289 partition = f" {partition}" if partition else "" 1290 format = self.sql(expression, "format") 1291 format = f" {format}" if format else "" 1292 1293 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1294 1295 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1296 tag = self.sql(expression, "tag") 1297 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1298 1299 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1300 with_ = self.sql(expression, "with") 1301 if with_: 1302 sql = f"{with_}{self.sep()}{sql}" 1303 return sql 1304 1305 def with_sql(self, expression: exp.With) -> str: 1306 sql = self.expressions(expression, flat=True) 1307 recursive = ( 1308 "RECURSIVE " 1309 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1310 else "" 1311 ) 1312 search = self.sql(expression, "search") 1313 search = f" {search}" if search else "" 1314 1315 return f"WITH {recursive}{sql}{search}" 1316 1317 def cte_sql(self, expression: exp.CTE) -> str: 1318 alias = expression.args.get("alias") 1319 if alias: 1320 alias.add_comments(expression.pop_comments()) 1321 1322 alias_sql = self.sql(expression, "alias") 1323 1324 materialized = expression.args.get("materialized") 1325 if materialized is False: 1326 materialized = "NOT MATERIALIZED " 1327 elif materialized: 1328 materialized = "MATERIALIZED " 1329 1330 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1331 1332 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1333 alias = self.sql(expression, "this") 1334 columns = self.expressions(expression, key="columns", flat=True) 1335 columns = f"({columns})" if columns else "" 1336 1337 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1338 columns = "" 1339 self.unsupported("Named columns are not supported in table alias.") 1340 1341 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1342 alias = self._next_name() 1343 1344 return f"{alias}{columns}" 1345 1346 def bitstring_sql(self, expression: exp.BitString) -> str: 1347 this = self.sql(expression, "this") 1348 if self.dialect.BIT_START: 1349 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1350 return f"{int(this, 2)}" 1351 1352 def hexstring_sql( 1353 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1354 ) -> str: 1355 this = self.sql(expression, "this") 1356 is_integer_type = expression.args.get("is_integer") 1357 1358 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1359 not self.dialect.HEX_START and not binary_function_repr 1360 ): 1361 # Integer representation will be returned if: 1362 # - The read dialect treats the hex value as integer literal but not the write 1363 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1364 return f"{int(this, 16)}" 1365 1366 if not is_integer_type: 1367 # Read dialect treats the hex value as BINARY/BLOB 1368 if binary_function_repr: 1369 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1370 return self.func(binary_function_repr, exp.Literal.string(this)) 1371 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1372 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1373 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1374 1375 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1376 1377 def bytestring_sql(self, expression: exp.ByteString) -> str: 1378 this = self.sql(expression, "this") 1379 if self.dialect.BYTE_START: 1380 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1381 return this 1382 1383 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1384 this = self.sql(expression, "this") 1385 escape = expression.args.get("escape") 1386 1387 if self.dialect.UNICODE_START: 1388 escape_substitute = r"\\\1" 1389 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1390 else: 1391 escape_substitute = r"\\u\1" 1392 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1393 1394 if escape: 1395 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1396 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1397 else: 1398 escape_pattern = ESCAPED_UNICODE_RE 1399 escape_sql = "" 1400 1401 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1402 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1403 1404 return f"{left_quote}{this}{right_quote}{escape_sql}" 1405 1406 def rawstring_sql(self, expression: exp.RawString) -> str: 1407 string = expression.this 1408 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1409 string = string.replace("\\", "\\\\") 1410 1411 string = self.escape_str(string, escape_backslash=False) 1412 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1413 1414 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1415 this = self.sql(expression, "this") 1416 specifier = self.sql(expression, "expression") 1417 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1418 return f"{this}{specifier}" 1419 1420 def datatype_sql(self, expression: exp.DataType) -> str: 1421 nested = "" 1422 values = "" 1423 interior = self.expressions(expression, flat=True) 1424 1425 type_value = expression.this 1426 if type_value in self.UNSUPPORTED_TYPES: 1427 self.unsupported( 1428 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1429 ) 1430 1431 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1432 type_sql = self.sql(expression, "kind") 1433 else: 1434 type_sql = ( 1435 self.TYPE_MAPPING.get(type_value, type_value.value) 1436 if isinstance(type_value, exp.DataType.Type) 1437 else type_value 1438 ) 1439 1440 if interior: 1441 if expression.args.get("nested"): 1442 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1443 if expression.args.get("values") is not None: 1444 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1445 values = self.expressions(expression, key="values", flat=True) 1446 values = f"{delimiters[0]}{values}{delimiters[1]}" 1447 elif type_value == exp.DataType.Type.INTERVAL: 1448 nested = f" {interior}" 1449 else: 1450 nested = f"({interior})" 1451 1452 type_sql = f"{type_sql}{nested}{values}" 1453 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1454 exp.DataType.Type.TIMETZ, 1455 exp.DataType.Type.TIMESTAMPTZ, 1456 ): 1457 type_sql = f"{type_sql} WITH TIME ZONE" 1458 1459 return type_sql 1460 1461 def directory_sql(self, expression: exp.Directory) -> str: 1462 local = "LOCAL " if expression.args.get("local") else "" 1463 row_format = self.sql(expression, "row_format") 1464 row_format = f" {row_format}" if row_format else "" 1465 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1466 1467 def delete_sql(self, expression: exp.Delete) -> str: 1468 this = self.sql(expression, "this") 1469 this = f" FROM {this}" if this else "" 1470 using = self.sql(expression, "using") 1471 using = f" USING {using}" if using else "" 1472 cluster = self.sql(expression, "cluster") 1473 cluster = f" {cluster}" if cluster else "" 1474 where = self.sql(expression, "where") 1475 returning = self.sql(expression, "returning") 1476 limit = self.sql(expression, "limit") 1477 tables = self.expressions(expression, key="tables") 1478 tables = f" {tables}" if tables else "" 1479 if self.RETURNING_END: 1480 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1481 else: 1482 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1483 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1484 1485 def drop_sql(self, expression: exp.Drop) -> str: 1486 this = self.sql(expression, "this") 1487 expressions = self.expressions(expression, flat=True) 1488 expressions = f" ({expressions})" if expressions else "" 1489 kind = expression.args["kind"] 1490 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1491 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1492 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1493 on_cluster = self.sql(expression, "cluster") 1494 on_cluster = f" {on_cluster}" if on_cluster else "" 1495 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1496 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1497 cascade = " CASCADE" if expression.args.get("cascade") else "" 1498 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1499 purge = " PURGE" if expression.args.get("purge") else "" 1500 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1501 1502 def set_operation(self, expression: exp.SetOperation) -> str: 1503 op_type = type(expression) 1504 op_name = op_type.key.upper() 1505 1506 distinct = expression.args.get("distinct") 1507 if ( 1508 distinct is False 1509 and op_type in (exp.Except, exp.Intersect) 1510 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1511 ): 1512 self.unsupported(f"{op_name} ALL is not supported") 1513 1514 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1515 1516 if distinct is None: 1517 distinct = default_distinct 1518 if distinct is None: 1519 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1520 1521 if distinct is default_distinct: 1522 distinct_or_all = "" 1523 else: 1524 distinct_or_all = " DISTINCT" if distinct else " ALL" 1525 1526 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1527 side_kind = f"{side_kind} " if side_kind else "" 1528 1529 by_name = " BY NAME" if expression.args.get("by_name") else "" 1530 on = self.expressions(expression, key="on", flat=True) 1531 on = f" ON ({on})" if on else "" 1532 1533 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1534 1535 def set_operations(self, expression: exp.SetOperation) -> str: 1536 if not self.SET_OP_MODIFIERS: 1537 limit = expression.args.get("limit") 1538 order = expression.args.get("order") 1539 1540 if limit or order: 1541 select = self._move_ctes_to_top_level( 1542 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1543 ) 1544 1545 if limit: 1546 select = select.limit(limit.pop(), copy=False) 1547 if order: 1548 select = select.order_by(order.pop(), copy=False) 1549 return self.sql(select) 1550 1551 sqls: t.List[str] = [] 1552 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1553 1554 while stack: 1555 node = stack.pop() 1556 1557 if isinstance(node, exp.SetOperation): 1558 stack.append(node.expression) 1559 stack.append( 1560 self.maybe_comment( 1561 self.set_operation(node), comments=node.comments, separated=True 1562 ) 1563 ) 1564 stack.append(node.this) 1565 else: 1566 sqls.append(self.sql(node)) 1567 1568 this = self.sep().join(sqls) 1569 this = self.query_modifiers(expression, this) 1570 return self.prepend_ctes(expression, this) 1571 1572 def fetch_sql(self, expression: exp.Fetch) -> str: 1573 direction = expression.args.get("direction") 1574 direction = f" {direction}" if direction else "" 1575 count = self.sql(expression, "count") 1576 count = f" {count}" if count else "" 1577 limit_options = self.sql(expression, "limit_options") 1578 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1579 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1580 1581 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1582 percent = " PERCENT" if expression.args.get("percent") else "" 1583 rows = " ROWS" if expression.args.get("rows") else "" 1584 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1585 if not with_ties and rows: 1586 with_ties = " ONLY" 1587 return f"{percent}{rows}{with_ties}" 1588 1589 def filter_sql(self, expression: exp.Filter) -> str: 1590 if self.AGGREGATE_FILTER_SUPPORTED: 1591 this = self.sql(expression, "this") 1592 where = self.sql(expression, "expression").strip() 1593 return f"{this} FILTER({where})" 1594 1595 agg = expression.this 1596 agg_arg = agg.this 1597 cond = expression.expression.this 1598 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1599 return self.sql(agg) 1600 1601 def hint_sql(self, expression: exp.Hint) -> str: 1602 if not self.QUERY_HINTS: 1603 self.unsupported("Hints are not supported") 1604 return "" 1605 1606 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1607 1608 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1609 using = self.sql(expression, "using") 1610 using = f" USING {using}" if using else "" 1611 columns = self.expressions(expression, key="columns", flat=True) 1612 columns = f"({columns})" if columns else "" 1613 partition_by = self.expressions(expression, key="partition_by", flat=True) 1614 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1615 where = self.sql(expression, "where") 1616 include = self.expressions(expression, key="include", flat=True) 1617 if include: 1618 include = f" INCLUDE ({include})" 1619 with_storage = self.expressions(expression, key="with_storage", flat=True) 1620 with_storage = f" WITH ({with_storage})" if with_storage else "" 1621 tablespace = self.sql(expression, "tablespace") 1622 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1623 on = self.sql(expression, "on") 1624 on = f" ON {on}" if on else "" 1625 1626 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1627 1628 def index_sql(self, expression: exp.Index) -> str: 1629 unique = "UNIQUE " if expression.args.get("unique") else "" 1630 primary = "PRIMARY " if expression.args.get("primary") else "" 1631 amp = "AMP " if expression.args.get("amp") else "" 1632 name = self.sql(expression, "this") 1633 name = f"{name} " if name else "" 1634 table = self.sql(expression, "table") 1635 table = f"{self.INDEX_ON} {table}" if table else "" 1636 1637 index = "INDEX " if not table else "" 1638 1639 params = self.sql(expression, "params") 1640 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1641 1642 def identifier_sql(self, expression: exp.Identifier) -> str: 1643 text = expression.name 1644 lower = text.lower() 1645 text = lower if self.normalize and not expression.quoted else text 1646 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1647 if ( 1648 expression.quoted 1649 or self.dialect.can_identify(text, self.identify) 1650 or lower in self.RESERVED_KEYWORDS 1651 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1652 ): 1653 text = f"{self._identifier_start}{text}{self._identifier_end}" 1654 return text 1655 1656 def hex_sql(self, expression: exp.Hex) -> str: 1657 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1658 if self.dialect.HEX_LOWERCASE: 1659 text = self.func("LOWER", text) 1660 1661 return text 1662 1663 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1664 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1665 if not self.dialect.HEX_LOWERCASE: 1666 text = self.func("LOWER", text) 1667 return text 1668 1669 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1670 input_format = self.sql(expression, "input_format") 1671 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1672 output_format = self.sql(expression, "output_format") 1673 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1674 return self.sep().join((input_format, output_format)) 1675 1676 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1677 string = self.sql(exp.Literal.string(expression.name)) 1678 return f"{prefix}{string}" 1679 1680 def partition_sql(self, expression: exp.Partition) -> str: 1681 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1682 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1683 1684 def properties_sql(self, expression: exp.Properties) -> str: 1685 root_properties = [] 1686 with_properties = [] 1687 1688 for p in expression.expressions: 1689 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1690 if p_loc == exp.Properties.Location.POST_WITH: 1691 with_properties.append(p) 1692 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1693 root_properties.append(p) 1694 1695 root_props_ast = exp.Properties(expressions=root_properties) 1696 root_props_ast.parent = expression.parent 1697 1698 with_props_ast = exp.Properties(expressions=with_properties) 1699 with_props_ast.parent = expression.parent 1700 1701 root_props = self.root_properties(root_props_ast) 1702 with_props = self.with_properties(with_props_ast) 1703 1704 if root_props and with_props and not self.pretty: 1705 with_props = " " + with_props 1706 1707 return root_props + with_props 1708 1709 def root_properties(self, properties: exp.Properties) -> str: 1710 if properties.expressions: 1711 return self.expressions(properties, indent=False, sep=" ") 1712 return "" 1713 1714 def properties( 1715 self, 1716 properties: exp.Properties, 1717 prefix: str = "", 1718 sep: str = ", ", 1719 suffix: str = "", 1720 wrapped: bool = True, 1721 ) -> str: 1722 if properties.expressions: 1723 expressions = self.expressions(properties, sep=sep, indent=False) 1724 if expressions: 1725 expressions = self.wrap(expressions) if wrapped else expressions 1726 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1727 return "" 1728 1729 def with_properties(self, properties: exp.Properties) -> str: 1730 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1731 1732 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1733 properties_locs = defaultdict(list) 1734 for p in properties.expressions: 1735 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1736 if p_loc != exp.Properties.Location.UNSUPPORTED: 1737 properties_locs[p_loc].append(p) 1738 else: 1739 self.unsupported(f"Unsupported property {p.key}") 1740 1741 return properties_locs 1742 1743 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1744 if isinstance(expression.this, exp.Dot): 1745 return self.sql(expression, "this") 1746 return f"'{expression.name}'" if string_key else expression.name 1747 1748 def property_sql(self, expression: exp.Property) -> str: 1749 property_cls = expression.__class__ 1750 if property_cls == exp.Property: 1751 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1752 1753 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1754 if not property_name: 1755 self.unsupported(f"Unsupported property {expression.key}") 1756 1757 return f"{property_name}={self.sql(expression, 'this')}" 1758 1759 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1760 if self.SUPPORTS_CREATE_TABLE_LIKE: 1761 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1762 options = f" {options}" if options else "" 1763 1764 like = f"LIKE {self.sql(expression, 'this')}{options}" 1765 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1766 like = f"({like})" 1767 1768 return like 1769 1770 if expression.expressions: 1771 self.unsupported("Transpilation of LIKE property options is unsupported") 1772 1773 select = exp.select("*").from_(expression.this).limit(0) 1774 return f"AS {self.sql(select)}" 1775 1776 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1777 no = "NO " if expression.args.get("no") else "" 1778 protection = " PROTECTION" if expression.args.get("protection") else "" 1779 return f"{no}FALLBACK{protection}" 1780 1781 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1782 no = "NO " if expression.args.get("no") else "" 1783 local = expression.args.get("local") 1784 local = f"{local} " if local else "" 1785 dual = "DUAL " if expression.args.get("dual") else "" 1786 before = "BEFORE " if expression.args.get("before") else "" 1787 after = "AFTER " if expression.args.get("after") else "" 1788 return f"{no}{local}{dual}{before}{after}JOURNAL" 1789 1790 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1791 freespace = self.sql(expression, "this") 1792 percent = " PERCENT" if expression.args.get("percent") else "" 1793 return f"FREESPACE={freespace}{percent}" 1794 1795 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1796 if expression.args.get("default"): 1797 property = "DEFAULT" 1798 elif expression.args.get("on"): 1799 property = "ON" 1800 else: 1801 property = "OFF" 1802 return f"CHECKSUM={property}" 1803 1804 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1805 if expression.args.get("no"): 1806 return "NO MERGEBLOCKRATIO" 1807 if expression.args.get("default"): 1808 return "DEFAULT MERGEBLOCKRATIO" 1809 1810 percent = " PERCENT" if expression.args.get("percent") else "" 1811 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1812 1813 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1814 default = expression.args.get("default") 1815 minimum = expression.args.get("minimum") 1816 maximum = expression.args.get("maximum") 1817 if default or minimum or maximum: 1818 if default: 1819 prop = "DEFAULT" 1820 elif minimum: 1821 prop = "MINIMUM" 1822 else: 1823 prop = "MAXIMUM" 1824 return f"{prop} DATABLOCKSIZE" 1825 units = expression.args.get("units") 1826 units = f" {units}" if units else "" 1827 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1828 1829 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1830 autotemp = expression.args.get("autotemp") 1831 always = expression.args.get("always") 1832 default = expression.args.get("default") 1833 manual = expression.args.get("manual") 1834 never = expression.args.get("never") 1835 1836 if autotemp is not None: 1837 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1838 elif always: 1839 prop = "ALWAYS" 1840 elif default: 1841 prop = "DEFAULT" 1842 elif manual: 1843 prop = "MANUAL" 1844 elif never: 1845 prop = "NEVER" 1846 return f"BLOCKCOMPRESSION={prop}" 1847 1848 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1849 no = expression.args.get("no") 1850 no = " NO" if no else "" 1851 concurrent = expression.args.get("concurrent") 1852 concurrent = " CONCURRENT" if concurrent else "" 1853 target = self.sql(expression, "target") 1854 target = f" {target}" if target else "" 1855 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1856 1857 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1858 if isinstance(expression.this, list): 1859 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1860 if expression.this: 1861 modulus = self.sql(expression, "this") 1862 remainder = self.sql(expression, "expression") 1863 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1864 1865 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1866 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1867 return f"FROM ({from_expressions}) TO ({to_expressions})" 1868 1869 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1870 this = self.sql(expression, "this") 1871 1872 for_values_or_default = expression.expression 1873 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1874 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1875 else: 1876 for_values_or_default = " DEFAULT" 1877 1878 return f"PARTITION OF {this}{for_values_or_default}" 1879 1880 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1881 kind = expression.args.get("kind") 1882 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1883 for_or_in = expression.args.get("for_or_in") 1884 for_or_in = f" {for_or_in}" if for_or_in else "" 1885 lock_type = expression.args.get("lock_type") 1886 override = " OVERRIDE" if expression.args.get("override") else "" 1887 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1888 1889 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1890 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1891 statistics = expression.args.get("statistics") 1892 statistics_sql = "" 1893 if statistics is not None: 1894 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1895 return f"{data_sql}{statistics_sql}" 1896 1897 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1898 this = self.sql(expression, "this") 1899 this = f"HISTORY_TABLE={this}" if this else "" 1900 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1901 data_consistency = ( 1902 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1903 ) 1904 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1905 retention_period = ( 1906 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1907 ) 1908 1909 if this: 1910 on_sql = self.func("ON", this, data_consistency, retention_period) 1911 else: 1912 on_sql = "ON" if expression.args.get("on") else "OFF" 1913 1914 sql = f"SYSTEM_VERSIONING={on_sql}" 1915 1916 return f"WITH({sql})" if expression.args.get("with") else sql 1917 1918 def insert_sql(self, expression: exp.Insert) -> str: 1919 hint = self.sql(expression, "hint") 1920 overwrite = expression.args.get("overwrite") 1921 1922 if isinstance(expression.this, exp.Directory): 1923 this = " OVERWRITE" if overwrite else " INTO" 1924 else: 1925 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1926 1927 stored = self.sql(expression, "stored") 1928 stored = f" {stored}" if stored else "" 1929 alternative = expression.args.get("alternative") 1930 alternative = f" OR {alternative}" if alternative else "" 1931 ignore = " IGNORE" if expression.args.get("ignore") else "" 1932 is_function = expression.args.get("is_function") 1933 if is_function: 1934 this = f"{this} FUNCTION" 1935 this = f"{this} {self.sql(expression, 'this')}" 1936 1937 exists = " IF EXISTS" if expression.args.get("exists") else "" 1938 where = self.sql(expression, "where") 1939 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1940 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1941 on_conflict = self.sql(expression, "conflict") 1942 on_conflict = f" {on_conflict}" if on_conflict else "" 1943 by_name = " BY NAME" if expression.args.get("by_name") else "" 1944 returning = self.sql(expression, "returning") 1945 1946 if self.RETURNING_END: 1947 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1948 else: 1949 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1950 1951 partition_by = self.sql(expression, "partition") 1952 partition_by = f" {partition_by}" if partition_by else "" 1953 settings = self.sql(expression, "settings") 1954 settings = f" {settings}" if settings else "" 1955 1956 source = self.sql(expression, "source") 1957 source = f"TABLE {source}" if source else "" 1958 1959 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1960 return self.prepend_ctes(expression, sql) 1961 1962 def introducer_sql(self, expression: exp.Introducer) -> str: 1963 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1964 1965 def kill_sql(self, expression: exp.Kill) -> str: 1966 kind = self.sql(expression, "kind") 1967 kind = f" {kind}" if kind else "" 1968 this = self.sql(expression, "this") 1969 this = f" {this}" if this else "" 1970 return f"KILL{kind}{this}" 1971 1972 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1973 return expression.name 1974 1975 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1976 return expression.name 1977 1978 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1979 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1980 1981 constraint = self.sql(expression, "constraint") 1982 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1983 1984 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1985 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1986 action = self.sql(expression, "action") 1987 1988 expressions = self.expressions(expression, flat=True) 1989 if expressions: 1990 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1991 expressions = f" {set_keyword}{expressions}" 1992 1993 where = self.sql(expression, "where") 1994 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1995 1996 def returning_sql(self, expression: exp.Returning) -> str: 1997 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1998 1999 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2000 fields = self.sql(expression, "fields") 2001 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2002 escaped = self.sql(expression, "escaped") 2003 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2004 items = self.sql(expression, "collection_items") 2005 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2006 keys = self.sql(expression, "map_keys") 2007 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2008 lines = self.sql(expression, "lines") 2009 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2010 null = self.sql(expression, "null") 2011 null = f" NULL DEFINED AS {null}" if null else "" 2012 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 2013 2014 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2015 return f"WITH ({self.expressions(expression, flat=True)})" 2016 2017 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2018 this = f"{self.sql(expression, 'this')} INDEX" 2019 target = self.sql(expression, "target") 2020 target = f" FOR {target}" if target else "" 2021 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2022 2023 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2024 this = self.sql(expression, "this") 2025 kind = self.sql(expression, "kind") 2026 expr = self.sql(expression, "expression") 2027 return f"{this} ({kind} => {expr})" 2028 2029 def table_parts(self, expression: exp.Table) -> str: 2030 return ".".join( 2031 self.sql(part) 2032 for part in ( 2033 expression.args.get("catalog"), 2034 expression.args.get("db"), 2035 expression.args.get("this"), 2036 ) 2037 if part is not None 2038 ) 2039 2040 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2041 table = self.table_parts(expression) 2042 only = "ONLY " if expression.args.get("only") else "" 2043 partition = self.sql(expression, "partition") 2044 partition = f" {partition}" if partition else "" 2045 version = self.sql(expression, "version") 2046 version = f" {version}" if version else "" 2047 alias = self.sql(expression, "alias") 2048 alias = f"{sep}{alias}" if alias else "" 2049 2050 sample = self.sql(expression, "sample") 2051 if self.dialect.ALIAS_POST_TABLESAMPLE: 2052 sample_pre_alias = sample 2053 sample_post_alias = "" 2054 else: 2055 sample_pre_alias = "" 2056 sample_post_alias = sample 2057 2058 hints = self.expressions(expression, key="hints", sep=" ") 2059 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2060 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2061 joins = self.indent( 2062 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2063 ) 2064 laterals = self.expressions(expression, key="laterals", sep="") 2065 2066 file_format = self.sql(expression, "format") 2067 if file_format: 2068 pattern = self.sql(expression, "pattern") 2069 pattern = f", PATTERN => {pattern}" if pattern else "" 2070 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2071 2072 ordinality = expression.args.get("ordinality") or "" 2073 if ordinality: 2074 ordinality = f" WITH ORDINALITY{alias}" 2075 alias = "" 2076 2077 when = self.sql(expression, "when") 2078 if when: 2079 table = f"{table} {when}" 2080 2081 changes = self.sql(expression, "changes") 2082 changes = f" {changes}" if changes else "" 2083 2084 rows_from = self.expressions(expression, key="rows_from") 2085 if rows_from: 2086 table = f"ROWS FROM {self.wrap(rows_from)}" 2087 2088 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2089 2090 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2091 table = self.func("TABLE", expression.this) 2092 alias = self.sql(expression, "alias") 2093 alias = f" AS {alias}" if alias else "" 2094 sample = self.sql(expression, "sample") 2095 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2096 joins = self.indent( 2097 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2098 ) 2099 return f"{table}{alias}{pivots}{sample}{joins}" 2100 2101 def tablesample_sql( 2102 self, 2103 expression: exp.TableSample, 2104 tablesample_keyword: t.Optional[str] = None, 2105 ) -> str: 2106 method = self.sql(expression, "method") 2107 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2108 numerator = self.sql(expression, "bucket_numerator") 2109 denominator = self.sql(expression, "bucket_denominator") 2110 field = self.sql(expression, "bucket_field") 2111 field = f" ON {field}" if field else "" 2112 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2113 seed = self.sql(expression, "seed") 2114 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2115 2116 size = self.sql(expression, "size") 2117 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2118 size = f"{size} ROWS" 2119 2120 percent = self.sql(expression, "percent") 2121 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2122 percent = f"{percent} PERCENT" 2123 2124 expr = f"{bucket}{percent}{size}" 2125 if self.TABLESAMPLE_REQUIRES_PARENS: 2126 expr = f"({expr})" 2127 2128 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2129 2130 def pivot_sql(self, expression: exp.Pivot) -> str: 2131 expressions = self.expressions(expression, flat=True) 2132 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2133 2134 group = self.sql(expression, "group") 2135 2136 if expression.this: 2137 this = self.sql(expression, "this") 2138 if not expressions: 2139 return f"UNPIVOT {this}" 2140 2141 on = f"{self.seg('ON')} {expressions}" 2142 into = self.sql(expression, "into") 2143 into = f"{self.seg('INTO')} {into}" if into else "" 2144 using = self.expressions(expression, key="using", flat=True) 2145 using = f"{self.seg('USING')} {using}" if using else "" 2146 return f"{direction} {this}{on}{into}{using}{group}" 2147 2148 alias = self.sql(expression, "alias") 2149 alias = f" AS {alias}" if alias else "" 2150 2151 fields = self.expressions( 2152 expression, 2153 "fields", 2154 sep=" ", 2155 dynamic=True, 2156 new_line=True, 2157 skip_first=True, 2158 skip_last=True, 2159 ) 2160 2161 include_nulls = expression.args.get("include_nulls") 2162 if include_nulls is not None: 2163 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2164 else: 2165 nulls = "" 2166 2167 default_on_null = self.sql(expression, "default_on_null") 2168 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2169 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2170 2171 def version_sql(self, expression: exp.Version) -> str: 2172 this = f"FOR {expression.name}" 2173 kind = expression.text("kind") 2174 expr = self.sql(expression, "expression") 2175 return f"{this} {kind} {expr}" 2176 2177 def tuple_sql(self, expression: exp.Tuple) -> str: 2178 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2179 2180 def update_sql(self, expression: exp.Update) -> str: 2181 this = self.sql(expression, "this") 2182 set_sql = self.expressions(expression, flat=True) 2183 from_sql = self.sql(expression, "from") 2184 where_sql = self.sql(expression, "where") 2185 returning = self.sql(expression, "returning") 2186 order = self.sql(expression, "order") 2187 limit = self.sql(expression, "limit") 2188 if self.RETURNING_END: 2189 expression_sql = f"{from_sql}{where_sql}{returning}" 2190 else: 2191 expression_sql = f"{returning}{from_sql}{where_sql}" 2192 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2193 return self.prepend_ctes(expression, sql) 2194 2195 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2196 values_as_table = values_as_table and self.VALUES_AS_TABLE 2197 2198 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2199 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2200 args = self.expressions(expression) 2201 alias = self.sql(expression, "alias") 2202 values = f"VALUES{self.seg('')}{args}" 2203 values = ( 2204 f"({values})" 2205 if self.WRAP_DERIVED_VALUES 2206 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2207 else values 2208 ) 2209 return f"{values} AS {alias}" if alias else values 2210 2211 # Converts `VALUES...` expression into a series of select unions. 2212 alias_node = expression.args.get("alias") 2213 column_names = alias_node and alias_node.columns 2214 2215 selects: t.List[exp.Query] = [] 2216 2217 for i, tup in enumerate(expression.expressions): 2218 row = tup.expressions 2219 2220 if i == 0 and column_names: 2221 row = [ 2222 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2223 ] 2224 2225 selects.append(exp.Select(expressions=row)) 2226 2227 if self.pretty: 2228 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2229 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2230 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2231 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2232 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2233 2234 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2235 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2236 return f"({unions}){alias}" 2237 2238 def var_sql(self, expression: exp.Var) -> str: 2239 return self.sql(expression, "this") 2240 2241 @unsupported_args("expressions") 2242 def into_sql(self, expression: exp.Into) -> str: 2243 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2244 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2245 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2246 2247 def from_sql(self, expression: exp.From) -> str: 2248 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2249 2250 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2251 grouping_sets = self.expressions(expression, indent=False) 2252 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2253 2254 def rollup_sql(self, expression: exp.Rollup) -> str: 2255 expressions = self.expressions(expression, indent=False) 2256 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2257 2258 def cube_sql(self, expression: exp.Cube) -> str: 2259 expressions = self.expressions(expression, indent=False) 2260 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2261 2262 def group_sql(self, expression: exp.Group) -> str: 2263 group_by_all = expression.args.get("all") 2264 if group_by_all is True: 2265 modifier = " ALL" 2266 elif group_by_all is False: 2267 modifier = " DISTINCT" 2268 else: 2269 modifier = "" 2270 2271 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2272 2273 grouping_sets = self.expressions(expression, key="grouping_sets") 2274 cube = self.expressions(expression, key="cube") 2275 rollup = self.expressions(expression, key="rollup") 2276 2277 groupings = csv( 2278 self.seg(grouping_sets) if grouping_sets else "", 2279 self.seg(cube) if cube else "", 2280 self.seg(rollup) if rollup else "", 2281 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2282 sep=self.GROUPINGS_SEP, 2283 ) 2284 2285 if ( 2286 expression.expressions 2287 and groupings 2288 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2289 ): 2290 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2291 2292 return f"{group_by}{groupings}" 2293 2294 def having_sql(self, expression: exp.Having) -> str: 2295 this = self.indent(self.sql(expression, "this")) 2296 return f"{self.seg('HAVING')}{self.sep()}{this}" 2297 2298 def connect_sql(self, expression: exp.Connect) -> str: 2299 start = self.sql(expression, "start") 2300 start = self.seg(f"START WITH {start}") if start else "" 2301 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2302 connect = self.sql(expression, "connect") 2303 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2304 return start + connect 2305 2306 def prior_sql(self, expression: exp.Prior) -> str: 2307 return f"PRIOR {self.sql(expression, 'this')}" 2308 2309 def join_sql(self, expression: exp.Join) -> str: 2310 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2311 side = None 2312 else: 2313 side = expression.side 2314 2315 op_sql = " ".join( 2316 op 2317 for op in ( 2318 expression.method, 2319 "GLOBAL" if expression.args.get("global") else None, 2320 side, 2321 expression.kind, 2322 expression.hint if self.JOIN_HINTS else None, 2323 ) 2324 if op 2325 ) 2326 match_cond = self.sql(expression, "match_condition") 2327 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2328 on_sql = self.sql(expression, "on") 2329 using = expression.args.get("using") 2330 2331 if not on_sql and using: 2332 on_sql = csv(*(self.sql(column) for column in using)) 2333 2334 this = expression.this 2335 this_sql = self.sql(this) 2336 2337 exprs = self.expressions(expression) 2338 if exprs: 2339 this_sql = f"{this_sql},{self.seg(exprs)}" 2340 2341 if on_sql: 2342 on_sql = self.indent(on_sql, skip_first=True) 2343 space = self.seg(" " * self.pad) if self.pretty else " " 2344 if using: 2345 on_sql = f"{space}USING ({on_sql})" 2346 else: 2347 on_sql = f"{space}ON {on_sql}" 2348 elif not op_sql: 2349 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2350 return f" {this_sql}" 2351 2352 return f", {this_sql}" 2353 2354 if op_sql != "STRAIGHT_JOIN": 2355 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2356 2357 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2358 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2359 2360 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2361 args = self.expressions(expression, flat=True) 2362 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2363 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2364 2365 def lateral_op(self, expression: exp.Lateral) -> str: 2366 cross_apply = expression.args.get("cross_apply") 2367 2368 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2369 if cross_apply is True: 2370 op = "INNER JOIN " 2371 elif cross_apply is False: 2372 op = "LEFT JOIN " 2373 else: 2374 op = "" 2375 2376 return f"{op}LATERAL" 2377 2378 def lateral_sql(self, expression: exp.Lateral) -> str: 2379 this = self.sql(expression, "this") 2380 2381 if expression.args.get("view"): 2382 alias = expression.args["alias"] 2383 columns = self.expressions(alias, key="columns", flat=True) 2384 table = f" {alias.name}" if alias.name else "" 2385 columns = f" AS {columns}" if columns else "" 2386 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2387 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2388 2389 alias = self.sql(expression, "alias") 2390 alias = f" AS {alias}" if alias else "" 2391 2392 ordinality = expression.args.get("ordinality") or "" 2393 if ordinality: 2394 ordinality = f" WITH ORDINALITY{alias}" 2395 alias = "" 2396 2397 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2398 2399 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2400 this = self.sql(expression, "this") 2401 2402 args = [ 2403 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2404 for e in (expression.args.get(k) for k in ("offset", "expression")) 2405 if e 2406 ] 2407 2408 args_sql = ", ".join(self.sql(e) for e in args) 2409 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2410 expressions = self.expressions(expression, flat=True) 2411 limit_options = self.sql(expression, "limit_options") 2412 expressions = f" BY {expressions}" if expressions else "" 2413 2414 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2415 2416 def offset_sql(self, expression: exp.Offset) -> str: 2417 this = self.sql(expression, "this") 2418 value = expression.expression 2419 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2420 expressions = self.expressions(expression, flat=True) 2421 expressions = f" BY {expressions}" if expressions else "" 2422 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2423 2424 def setitem_sql(self, expression: exp.SetItem) -> str: 2425 kind = self.sql(expression, "kind") 2426 kind = f"{kind} " if kind else "" 2427 this = self.sql(expression, "this") 2428 expressions = self.expressions(expression) 2429 collate = self.sql(expression, "collate") 2430 collate = f" COLLATE {collate}" if collate else "" 2431 global_ = "GLOBAL " if expression.args.get("global") else "" 2432 return f"{global_}{kind}{this}{expressions}{collate}" 2433 2434 def set_sql(self, expression: exp.Set) -> str: 2435 expressions = f" {self.expressions(expression, flat=True)}" 2436 tag = " TAG" if expression.args.get("tag") else "" 2437 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2438 2439 def queryband_sql(self, expression: exp.QueryBand) -> str: 2440 this = self.sql(expression, "this") 2441 update = " UPDATE" if expression.args.get("update") else "" 2442 scope = self.sql(expression, "scope") 2443 scope = f" FOR {scope}" if scope else "" 2444 2445 return f"QUERY_BAND = {this}{update}{scope}" 2446 2447 def pragma_sql(self, expression: exp.Pragma) -> str: 2448 return f"PRAGMA {self.sql(expression, 'this')}" 2449 2450 def lock_sql(self, expression: exp.Lock) -> str: 2451 if not self.LOCKING_READS_SUPPORTED: 2452 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2453 return "" 2454 2455 update = expression.args["update"] 2456 key = expression.args.get("key") 2457 if update: 2458 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2459 else: 2460 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2461 expressions = self.expressions(expression, flat=True) 2462 expressions = f" OF {expressions}" if expressions else "" 2463 wait = expression.args.get("wait") 2464 2465 if wait is not None: 2466 if isinstance(wait, exp.Literal): 2467 wait = f" WAIT {self.sql(wait)}" 2468 else: 2469 wait = " NOWAIT" if wait else " SKIP LOCKED" 2470 2471 return f"{lock_type}{expressions}{wait or ''}" 2472 2473 def literal_sql(self, expression: exp.Literal) -> str: 2474 text = expression.this or "" 2475 if expression.is_string: 2476 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2477 return text 2478 2479 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2480 if self.dialect.ESCAPED_SEQUENCES: 2481 to_escaped = self.dialect.ESCAPED_SEQUENCES 2482 text = "".join( 2483 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2484 ) 2485 2486 return self._replace_line_breaks(text).replace( 2487 self.dialect.QUOTE_END, self._escaped_quote_end 2488 ) 2489 2490 def loaddata_sql(self, expression: exp.LoadData) -> str: 2491 local = " LOCAL" if expression.args.get("local") else "" 2492 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2493 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2494 this = f" INTO TABLE {self.sql(expression, 'this')}" 2495 partition = self.sql(expression, "partition") 2496 partition = f" {partition}" if partition else "" 2497 input_format = self.sql(expression, "input_format") 2498 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2499 serde = self.sql(expression, "serde") 2500 serde = f" SERDE {serde}" if serde else "" 2501 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2502 2503 def null_sql(self, *_) -> str: 2504 return "NULL" 2505 2506 def boolean_sql(self, expression: exp.Boolean) -> str: 2507 return "TRUE" if expression.this else "FALSE" 2508 2509 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2510 this = self.sql(expression, "this") 2511 this = f"{this} " if this else this 2512 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2513 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2514 2515 def withfill_sql(self, expression: exp.WithFill) -> str: 2516 from_sql = self.sql(expression, "from") 2517 from_sql = f" FROM {from_sql}" if from_sql else "" 2518 to_sql = self.sql(expression, "to") 2519 to_sql = f" TO {to_sql}" if to_sql else "" 2520 step_sql = self.sql(expression, "step") 2521 step_sql = f" STEP {step_sql}" if step_sql else "" 2522 interpolated_values = [ 2523 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2524 if isinstance(e, exp.Alias) 2525 else self.sql(e, "this") 2526 for e in expression.args.get("interpolate") or [] 2527 ] 2528 interpolate = ( 2529 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2530 ) 2531 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2532 2533 def cluster_sql(self, expression: exp.Cluster) -> str: 2534 return self.op_expressions("CLUSTER BY", expression) 2535 2536 def distribute_sql(self, expression: exp.Distribute) -> str: 2537 return self.op_expressions("DISTRIBUTE BY", expression) 2538 2539 def sort_sql(self, expression: exp.Sort) -> str: 2540 return self.op_expressions("SORT BY", expression) 2541 2542 def ordered_sql(self, expression: exp.Ordered) -> str: 2543 desc = expression.args.get("desc") 2544 asc = not desc 2545 2546 nulls_first = expression.args.get("nulls_first") 2547 nulls_last = not nulls_first 2548 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2549 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2550 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2551 2552 this = self.sql(expression, "this") 2553 2554 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2555 nulls_sort_change = "" 2556 if nulls_first and ( 2557 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2558 ): 2559 nulls_sort_change = " NULLS FIRST" 2560 elif ( 2561 nulls_last 2562 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2563 and not nulls_are_last 2564 ): 2565 nulls_sort_change = " NULLS LAST" 2566 2567 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2568 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2569 window = expression.find_ancestor(exp.Window, exp.Select) 2570 if isinstance(window, exp.Window) and window.args.get("spec"): 2571 self.unsupported( 2572 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2573 ) 2574 nulls_sort_change = "" 2575 elif self.NULL_ORDERING_SUPPORTED is False and ( 2576 (asc and nulls_sort_change == " NULLS LAST") 2577 or (desc and nulls_sort_change == " NULLS FIRST") 2578 ): 2579 # BigQuery does not allow these ordering/nulls combinations when used under 2580 # an aggregation func or under a window containing one 2581 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2582 2583 if isinstance(ancestor, exp.Window): 2584 ancestor = ancestor.this 2585 if isinstance(ancestor, exp.AggFunc): 2586 self.unsupported( 2587 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2588 ) 2589 nulls_sort_change = "" 2590 elif self.NULL_ORDERING_SUPPORTED is None: 2591 if expression.this.is_int: 2592 self.unsupported( 2593 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2594 ) 2595 elif not isinstance(expression.this, exp.Rand): 2596 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2597 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2598 nulls_sort_change = "" 2599 2600 with_fill = self.sql(expression, "with_fill") 2601 with_fill = f" {with_fill}" if with_fill else "" 2602 2603 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2604 2605 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2606 window_frame = self.sql(expression, "window_frame") 2607 window_frame = f"{window_frame} " if window_frame else "" 2608 2609 this = self.sql(expression, "this") 2610 2611 return f"{window_frame}{this}" 2612 2613 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2614 partition = self.partition_by_sql(expression) 2615 order = self.sql(expression, "order") 2616 measures = self.expressions(expression, key="measures") 2617 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2618 rows = self.sql(expression, "rows") 2619 rows = self.seg(rows) if rows else "" 2620 after = self.sql(expression, "after") 2621 after = self.seg(after) if after else "" 2622 pattern = self.sql(expression, "pattern") 2623 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2624 definition_sqls = [ 2625 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2626 for definition in expression.args.get("define", []) 2627 ] 2628 definitions = self.expressions(sqls=definition_sqls) 2629 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2630 body = "".join( 2631 ( 2632 partition, 2633 order, 2634 measures, 2635 rows, 2636 after, 2637 pattern, 2638 define, 2639 ) 2640 ) 2641 alias = self.sql(expression, "alias") 2642 alias = f" {alias}" if alias else "" 2643 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2644 2645 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2646 limit = expression.args.get("limit") 2647 2648 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2649 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2650 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2651 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2652 2653 return csv( 2654 *sqls, 2655 *[self.sql(join) for join in expression.args.get("joins") or []], 2656 self.sql(expression, "match"), 2657 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2658 self.sql(expression, "prewhere"), 2659 self.sql(expression, "where"), 2660 self.sql(expression, "connect"), 2661 self.sql(expression, "group"), 2662 self.sql(expression, "having"), 2663 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2664 self.sql(expression, "order"), 2665 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2666 *self.after_limit_modifiers(expression), 2667 self.options_modifier(expression), 2668 self.for_modifiers(expression), 2669 sep="", 2670 ) 2671 2672 def options_modifier(self, expression: exp.Expression) -> str: 2673 options = self.expressions(expression, key="options") 2674 return f" {options}" if options else "" 2675 2676 def for_modifiers(self, expression: exp.Expression) -> str: 2677 for_modifiers = self.expressions(expression, key="for") 2678 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2679 2680 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2681 self.unsupported("Unsupported query option.") 2682 return "" 2683 2684 def offset_limit_modifiers( 2685 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2686 ) -> t.List[str]: 2687 return [ 2688 self.sql(expression, "offset") if fetch else self.sql(limit), 2689 self.sql(limit) if fetch else self.sql(expression, "offset"), 2690 ] 2691 2692 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2693 locks = self.expressions(expression, key="locks", sep=" ") 2694 locks = f" {locks}" if locks else "" 2695 return [locks, self.sql(expression, "sample")] 2696 2697 def select_sql(self, expression: exp.Select) -> str: 2698 into = expression.args.get("into") 2699 if not self.SUPPORTS_SELECT_INTO and into: 2700 into.pop() 2701 2702 hint = self.sql(expression, "hint") 2703 distinct = self.sql(expression, "distinct") 2704 distinct = f" {distinct}" if distinct else "" 2705 kind = self.sql(expression, "kind") 2706 2707 limit = expression.args.get("limit") 2708 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2709 top = self.limit_sql(limit, top=True) 2710 limit.pop() 2711 else: 2712 top = "" 2713 2714 expressions = self.expressions(expression) 2715 2716 if kind: 2717 if kind in self.SELECT_KINDS: 2718 kind = f" AS {kind}" 2719 else: 2720 if kind == "STRUCT": 2721 expressions = self.expressions( 2722 sqls=[ 2723 self.sql( 2724 exp.Struct( 2725 expressions=[ 2726 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2727 if isinstance(e, exp.Alias) 2728 else e 2729 for e in expression.expressions 2730 ] 2731 ) 2732 ) 2733 ] 2734 ) 2735 kind = "" 2736 2737 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2738 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2739 2740 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2741 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2742 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2743 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2744 sql = self.query_modifiers( 2745 expression, 2746 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2747 self.sql(expression, "into", comment=False), 2748 self.sql(expression, "from", comment=False), 2749 ) 2750 2751 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2752 if expression.args.get("with"): 2753 sql = self.maybe_comment(sql, expression) 2754 expression.pop_comments() 2755 2756 sql = self.prepend_ctes(expression, sql) 2757 2758 if not self.SUPPORTS_SELECT_INTO and into: 2759 if into.args.get("temporary"): 2760 table_kind = " TEMPORARY" 2761 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2762 table_kind = " UNLOGGED" 2763 else: 2764 table_kind = "" 2765 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2766 2767 return sql 2768 2769 def schema_sql(self, expression: exp.Schema) -> str: 2770 this = self.sql(expression, "this") 2771 sql = self.schema_columns_sql(expression) 2772 return f"{this} {sql}" if this and sql else this or sql 2773 2774 def schema_columns_sql(self, expression: exp.Schema) -> str: 2775 if expression.expressions: 2776 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2777 return "" 2778 2779 def star_sql(self, expression: exp.Star) -> str: 2780 except_ = self.expressions(expression, key="except", flat=True) 2781 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2782 replace = self.expressions(expression, key="replace", flat=True) 2783 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2784 rename = self.expressions(expression, key="rename", flat=True) 2785 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2786 return f"*{except_}{replace}{rename}" 2787 2788 def parameter_sql(self, expression: exp.Parameter) -> str: 2789 this = self.sql(expression, "this") 2790 return f"{self.PARAMETER_TOKEN}{this}" 2791 2792 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2793 this = self.sql(expression, "this") 2794 kind = expression.text("kind") 2795 if kind: 2796 kind = f"{kind}." 2797 return f"@@{kind}{this}" 2798 2799 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2800 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2801 2802 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2803 alias = self.sql(expression, "alias") 2804 alias = f"{sep}{alias}" if alias else "" 2805 sample = self.sql(expression, "sample") 2806 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2807 alias = f"{sample}{alias}" 2808 2809 # Set to None so it's not generated again by self.query_modifiers() 2810 expression.set("sample", None) 2811 2812 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2813 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2814 return self.prepend_ctes(expression, sql) 2815 2816 def qualify_sql(self, expression: exp.Qualify) -> str: 2817 this = self.indent(self.sql(expression, "this")) 2818 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2819 2820 def unnest_sql(self, expression: exp.Unnest) -> str: 2821 args = self.expressions(expression, flat=True) 2822 2823 alias = expression.args.get("alias") 2824 offset = expression.args.get("offset") 2825 2826 if self.UNNEST_WITH_ORDINALITY: 2827 if alias and isinstance(offset, exp.Expression): 2828 alias.append("columns", offset) 2829 2830 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2831 columns = alias.columns 2832 alias = self.sql(columns[0]) if columns else "" 2833 else: 2834 alias = self.sql(alias) 2835 2836 alias = f" AS {alias}" if alias else alias 2837 if self.UNNEST_WITH_ORDINALITY: 2838 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2839 else: 2840 if isinstance(offset, exp.Expression): 2841 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2842 elif offset: 2843 suffix = f"{alias} WITH OFFSET" 2844 else: 2845 suffix = alias 2846 2847 return f"UNNEST({args}){suffix}" 2848 2849 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2850 return "" 2851 2852 def where_sql(self, expression: exp.Where) -> str: 2853 this = self.indent(self.sql(expression, "this")) 2854 return f"{self.seg('WHERE')}{self.sep()}{this}" 2855 2856 def window_sql(self, expression: exp.Window) -> str: 2857 this = self.sql(expression, "this") 2858 partition = self.partition_by_sql(expression) 2859 order = expression.args.get("order") 2860 order = self.order_sql(order, flat=True) if order else "" 2861 spec = self.sql(expression, "spec") 2862 alias = self.sql(expression, "alias") 2863 over = self.sql(expression, "over") or "OVER" 2864 2865 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2866 2867 first = expression.args.get("first") 2868 if first is None: 2869 first = "" 2870 else: 2871 first = "FIRST" if first else "LAST" 2872 2873 if not partition and not order and not spec and alias: 2874 return f"{this} {alias}" 2875 2876 args = self.format_args( 2877 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2878 ) 2879 return f"{this} ({args})" 2880 2881 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2882 partition = self.expressions(expression, key="partition_by", flat=True) 2883 return f"PARTITION BY {partition}" if partition else "" 2884 2885 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2886 kind = self.sql(expression, "kind") 2887 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2888 end = ( 2889 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2890 or "CURRENT ROW" 2891 ) 2892 2893 window_spec = f"{kind} BETWEEN {start} AND {end}" 2894 2895 exclude = self.sql(expression, "exclude") 2896 if exclude: 2897 if self.SUPPORTS_WINDOW_EXCLUDE: 2898 window_spec += f" EXCLUDE {exclude}" 2899 else: 2900 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2901 2902 return window_spec 2903 2904 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2905 this = self.sql(expression, "this") 2906 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2907 return f"{this} WITHIN GROUP ({expression_sql})" 2908 2909 def between_sql(self, expression: exp.Between) -> str: 2910 this = self.sql(expression, "this") 2911 low = self.sql(expression, "low") 2912 high = self.sql(expression, "high") 2913 symmetric = expression.args.get("symmetric") 2914 2915 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2916 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2917 2918 flag = ( 2919 " SYMMETRIC" 2920 if symmetric 2921 else " ASYMMETRIC" 2922 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2923 else "" # silently drop ASYMMETRIC – semantics identical 2924 ) 2925 return f"{this} BETWEEN{flag} {low} AND {high}" 2926 2927 def bracket_offset_expressions( 2928 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2929 ) -> t.List[exp.Expression]: 2930 return apply_index_offset( 2931 expression.this, 2932 expression.expressions, 2933 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2934 dialect=self.dialect, 2935 ) 2936 2937 def bracket_sql(self, expression: exp.Bracket) -> str: 2938 expressions = self.bracket_offset_expressions(expression) 2939 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2940 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2941 2942 def all_sql(self, expression: exp.All) -> str: 2943 this = self.sql(expression, "this") 2944 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2945 this = self.wrap(this) 2946 return f"ALL {this}" 2947 2948 def any_sql(self, expression: exp.Any) -> str: 2949 this = self.sql(expression, "this") 2950 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2951 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2952 this = self.wrap(this) 2953 return f"ANY{this}" 2954 return f"ANY {this}" 2955 2956 def exists_sql(self, expression: exp.Exists) -> str: 2957 return f"EXISTS{self.wrap(expression)}" 2958 2959 def case_sql(self, expression: exp.Case) -> str: 2960 this = self.sql(expression, "this") 2961 statements = [f"CASE {this}" if this else "CASE"] 2962 2963 for e in expression.args["ifs"]: 2964 statements.append(f"WHEN {self.sql(e, 'this')}") 2965 statements.append(f"THEN {self.sql(e, 'true')}") 2966 2967 default = self.sql(expression, "default") 2968 2969 if default: 2970 statements.append(f"ELSE {default}") 2971 2972 statements.append("END") 2973 2974 if self.pretty and self.too_wide(statements): 2975 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2976 2977 return " ".join(statements) 2978 2979 def constraint_sql(self, expression: exp.Constraint) -> str: 2980 this = self.sql(expression, "this") 2981 expressions = self.expressions(expression, flat=True) 2982 return f"CONSTRAINT {this} {expressions}" 2983 2984 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2985 order = expression.args.get("order") 2986 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2987 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2988 2989 def extract_sql(self, expression: exp.Extract) -> str: 2990 from sqlglot.dialects.dialect import map_date_part 2991 2992 this = ( 2993 map_date_part(expression.this, self.dialect) 2994 if self.NORMALIZE_EXTRACT_DATE_PARTS 2995 else expression.this 2996 ) 2997 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2998 expression_sql = self.sql(expression, "expression") 2999 3000 return f"EXTRACT({this_sql} FROM {expression_sql})" 3001 3002 def trim_sql(self, expression: exp.Trim) -> str: 3003 trim_type = self.sql(expression, "position") 3004 3005 if trim_type == "LEADING": 3006 func_name = "LTRIM" 3007 elif trim_type == "TRAILING": 3008 func_name = "RTRIM" 3009 else: 3010 func_name = "TRIM" 3011 3012 return self.func(func_name, expression.this, expression.expression) 3013 3014 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3015 args = expression.expressions 3016 if isinstance(expression, exp.ConcatWs): 3017 args = args[1:] # Skip the delimiter 3018 3019 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3020 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3021 3022 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3023 3024 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3025 if not e.type: 3026 from sqlglot.optimizer.annotate_types import annotate_types 3027 3028 e = annotate_types(e, dialect=self.dialect) 3029 3030 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3031 return e 3032 3033 return exp.func("coalesce", e, exp.Literal.string("")) 3034 3035 args = [_wrap_with_coalesce(e) for e in args] 3036 3037 return args 3038 3039 def concat_sql(self, expression: exp.Concat) -> str: 3040 expressions = self.convert_concat_args(expression) 3041 3042 # Some dialects don't allow a single-argument CONCAT call 3043 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3044 return self.sql(expressions[0]) 3045 3046 return self.func("CONCAT", *expressions) 3047 3048 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3049 return self.func( 3050 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3051 ) 3052 3053 def check_sql(self, expression: exp.Check) -> str: 3054 this = self.sql(expression, key="this") 3055 return f"CHECK ({this})" 3056 3057 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3058 expressions = self.expressions(expression, flat=True) 3059 expressions = f" ({expressions})" if expressions else "" 3060 reference = self.sql(expression, "reference") 3061 reference = f" {reference}" if reference else "" 3062 delete = self.sql(expression, "delete") 3063 delete = f" ON DELETE {delete}" if delete else "" 3064 update = self.sql(expression, "update") 3065 update = f" ON UPDATE {update}" if update else "" 3066 options = self.expressions(expression, key="options", flat=True, sep=" ") 3067 options = f" {options}" if options else "" 3068 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3069 3070 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3071 expressions = self.expressions(expression, flat=True) 3072 include = self.sql(expression, "include") 3073 options = self.expressions(expression, key="options", flat=True, sep=" ") 3074 options = f" {options}" if options else "" 3075 return f"PRIMARY KEY ({expressions}){include}{options}" 3076 3077 def if_sql(self, expression: exp.If) -> str: 3078 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3079 3080 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3081 if self.MATCH_AGAINST_TABLE_PREFIX: 3082 expressions = [] 3083 for expr in expression.expressions: 3084 if isinstance(expr, exp.Table): 3085 expressions.append(f"TABLE {self.sql(expr)}") 3086 else: 3087 expressions.append(expr) 3088 else: 3089 expressions = expression.expressions 3090 3091 modifier = expression.args.get("modifier") 3092 modifier = f" {modifier}" if modifier else "" 3093 return ( 3094 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3095 ) 3096 3097 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3098 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3099 3100 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3101 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3102 3103 if expression.args.get("escape"): 3104 path = self.escape_str(path) 3105 3106 if self.QUOTE_JSON_PATH: 3107 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3108 3109 return path 3110 3111 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3112 if isinstance(expression, exp.JSONPathPart): 3113 transform = self.TRANSFORMS.get(expression.__class__) 3114 if not callable(transform): 3115 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3116 return "" 3117 3118 return transform(self, expression) 3119 3120 if isinstance(expression, int): 3121 return str(expression) 3122 3123 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3124 escaped = expression.replace("'", "\\'") 3125 escaped = f"\\'{expression}\\'" 3126 else: 3127 escaped = expression.replace('"', '\\"') 3128 escaped = f'"{escaped}"' 3129 3130 return escaped 3131 3132 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3133 return f"{self.sql(expression, 'this')} FORMAT JSON" 3134 3135 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3136 # Output the Teradata column FORMAT override. 3137 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3138 this = self.sql(expression, "this") 3139 fmt = self.sql(expression, "format") 3140 return f"{this} (FORMAT {fmt})" 3141 3142 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3143 null_handling = expression.args.get("null_handling") 3144 null_handling = f" {null_handling}" if null_handling else "" 3145 3146 unique_keys = expression.args.get("unique_keys") 3147 if unique_keys is not None: 3148 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3149 else: 3150 unique_keys = "" 3151 3152 return_type = self.sql(expression, "return_type") 3153 return_type = f" RETURNING {return_type}" if return_type else "" 3154 encoding = self.sql(expression, "encoding") 3155 encoding = f" ENCODING {encoding}" if encoding else "" 3156 3157 return self.func( 3158 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3159 *expression.expressions, 3160 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3161 ) 3162 3163 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3164 return self.jsonobject_sql(expression) 3165 3166 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3167 null_handling = expression.args.get("null_handling") 3168 null_handling = f" {null_handling}" if null_handling else "" 3169 return_type = self.sql(expression, "return_type") 3170 return_type = f" RETURNING {return_type}" if return_type else "" 3171 strict = " STRICT" if expression.args.get("strict") else "" 3172 return self.func( 3173 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3174 ) 3175 3176 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3177 this = self.sql(expression, "this") 3178 order = self.sql(expression, "order") 3179 null_handling = expression.args.get("null_handling") 3180 null_handling = f" {null_handling}" if null_handling else "" 3181 return_type = self.sql(expression, "return_type") 3182 return_type = f" RETURNING {return_type}" if return_type else "" 3183 strict = " STRICT" if expression.args.get("strict") else "" 3184 return self.func( 3185 "JSON_ARRAYAGG", 3186 this, 3187 suffix=f"{order}{null_handling}{return_type}{strict})", 3188 ) 3189 3190 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3191 path = self.sql(expression, "path") 3192 path = f" PATH {path}" if path else "" 3193 nested_schema = self.sql(expression, "nested_schema") 3194 3195 if nested_schema: 3196 return f"NESTED{path} {nested_schema}" 3197 3198 this = self.sql(expression, "this") 3199 kind = self.sql(expression, "kind") 3200 kind = f" {kind}" if kind else "" 3201 return f"{this}{kind}{path}" 3202 3203 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3204 return self.func("COLUMNS", *expression.expressions) 3205 3206 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3207 this = self.sql(expression, "this") 3208 path = self.sql(expression, "path") 3209 path = f", {path}" if path else "" 3210 error_handling = expression.args.get("error_handling") 3211 error_handling = f" {error_handling}" if error_handling else "" 3212 empty_handling = expression.args.get("empty_handling") 3213 empty_handling = f" {empty_handling}" if empty_handling else "" 3214 schema = self.sql(expression, "schema") 3215 return self.func( 3216 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3217 ) 3218 3219 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3220 this = self.sql(expression, "this") 3221 kind = self.sql(expression, "kind") 3222 path = self.sql(expression, "path") 3223 path = f" {path}" if path else "" 3224 as_json = " AS JSON" if expression.args.get("as_json") else "" 3225 return f"{this} {kind}{path}{as_json}" 3226 3227 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3228 this = self.sql(expression, "this") 3229 path = self.sql(expression, "path") 3230 path = f", {path}" if path else "" 3231 expressions = self.expressions(expression) 3232 with_ = ( 3233 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3234 if expressions 3235 else "" 3236 ) 3237 return f"OPENJSON({this}{path}){with_}" 3238 3239 def in_sql(self, expression: exp.In) -> str: 3240 query = expression.args.get("query") 3241 unnest = expression.args.get("unnest") 3242 field = expression.args.get("field") 3243 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3244 3245 if query: 3246 in_sql = self.sql(query) 3247 elif unnest: 3248 in_sql = self.in_unnest_op(unnest) 3249 elif field: 3250 in_sql = self.sql(field) 3251 else: 3252 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3253 3254 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3255 3256 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3257 return f"(SELECT {self.sql(unnest)})" 3258 3259 def interval_sql(self, expression: exp.Interval) -> str: 3260 unit = self.sql(expression, "unit") 3261 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3262 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3263 unit = f" {unit}" if unit else "" 3264 3265 if self.SINGLE_STRING_INTERVAL: 3266 this = expression.this.name if expression.this else "" 3267 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3268 3269 this = self.sql(expression, "this") 3270 if this: 3271 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3272 this = f" {this}" if unwrapped else f" ({this})" 3273 3274 return f"INTERVAL{this}{unit}" 3275 3276 def return_sql(self, expression: exp.Return) -> str: 3277 return f"RETURN {self.sql(expression, 'this')}" 3278 3279 def reference_sql(self, expression: exp.Reference) -> str: 3280 this = self.sql(expression, "this") 3281 expressions = self.expressions(expression, flat=True) 3282 expressions = f"({expressions})" if expressions else "" 3283 options = self.expressions(expression, key="options", flat=True, sep=" ") 3284 options = f" {options}" if options else "" 3285 return f"REFERENCES {this}{expressions}{options}" 3286 3287 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3288 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3289 parent = expression.parent 3290 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3291 return self.func( 3292 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3293 ) 3294 3295 def paren_sql(self, expression: exp.Paren) -> str: 3296 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3297 return f"({sql}{self.seg(')', sep='')}" 3298 3299 def neg_sql(self, expression: exp.Neg) -> str: 3300 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3301 this_sql = self.sql(expression, "this") 3302 sep = " " if this_sql[0] == "-" else "" 3303 return f"-{sep}{this_sql}" 3304 3305 def not_sql(self, expression: exp.Not) -> str: 3306 return f"NOT {self.sql(expression, 'this')}" 3307 3308 def alias_sql(self, expression: exp.Alias) -> str: 3309 alias = self.sql(expression, "alias") 3310 alias = f" AS {alias}" if alias else "" 3311 return f"{self.sql(expression, 'this')}{alias}" 3312 3313 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3314 alias = expression.args["alias"] 3315 3316 parent = expression.parent 3317 pivot = parent and parent.parent 3318 3319 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3320 identifier_alias = isinstance(alias, exp.Identifier) 3321 literal_alias = isinstance(alias, exp.Literal) 3322 3323 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3324 alias.replace(exp.Literal.string(alias.output_name)) 3325 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3326 alias.replace(exp.to_identifier(alias.output_name)) 3327 3328 return self.alias_sql(expression) 3329 3330 def aliases_sql(self, expression: exp.Aliases) -> str: 3331 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3332 3333 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3334 this = self.sql(expression, "this") 3335 index = self.sql(expression, "expression") 3336 return f"{this} AT {index}" 3337 3338 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3339 this = self.sql(expression, "this") 3340 zone = self.sql(expression, "zone") 3341 return f"{this} AT TIME ZONE {zone}" 3342 3343 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3344 this = self.sql(expression, "this") 3345 zone = self.sql(expression, "zone") 3346 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3347 3348 def add_sql(self, expression: exp.Add) -> str: 3349 return self.binary(expression, "+") 3350 3351 def and_sql( 3352 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3353 ) -> str: 3354 return self.connector_sql(expression, "AND", stack) 3355 3356 def or_sql( 3357 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3358 ) -> str: 3359 return self.connector_sql(expression, "OR", stack) 3360 3361 def xor_sql( 3362 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3363 ) -> str: 3364 return self.connector_sql(expression, "XOR", stack) 3365 3366 def connector_sql( 3367 self, 3368 expression: exp.Connector, 3369 op: str, 3370 stack: t.Optional[t.List[str | exp.Expression]] = None, 3371 ) -> str: 3372 if stack is not None: 3373 if expression.expressions: 3374 stack.append(self.expressions(expression, sep=f" {op} ")) 3375 else: 3376 stack.append(expression.right) 3377 if expression.comments and self.comments: 3378 for comment in expression.comments: 3379 if comment: 3380 op += f" /*{self.sanitize_comment(comment)}*/" 3381 stack.extend((op, expression.left)) 3382 return op 3383 3384 stack = [expression] 3385 sqls: t.List[str] = [] 3386 ops = set() 3387 3388 while stack: 3389 node = stack.pop() 3390 if isinstance(node, exp.Connector): 3391 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3392 else: 3393 sql = self.sql(node) 3394 if sqls and sqls[-1] in ops: 3395 sqls[-1] += f" {sql}" 3396 else: 3397 sqls.append(sql) 3398 3399 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3400 return sep.join(sqls) 3401 3402 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3403 return self.binary(expression, "&") 3404 3405 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3406 return self.binary(expression, "<<") 3407 3408 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3409 return f"~{self.sql(expression, 'this')}" 3410 3411 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3412 return self.binary(expression, "|") 3413 3414 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3415 return self.binary(expression, ">>") 3416 3417 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3418 return self.binary(expression, "^") 3419 3420 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3421 format_sql = self.sql(expression, "format") 3422 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3423 to_sql = self.sql(expression, "to") 3424 to_sql = f" {to_sql}" if to_sql else "" 3425 action = self.sql(expression, "action") 3426 action = f" {action}" if action else "" 3427 default = self.sql(expression, "default") 3428 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3429 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3430 3431 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3432 zone = self.sql(expression, "this") 3433 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3434 3435 def collate_sql(self, expression: exp.Collate) -> str: 3436 if self.COLLATE_IS_FUNC: 3437 return self.function_fallback_sql(expression) 3438 return self.binary(expression, "COLLATE") 3439 3440 def command_sql(self, expression: exp.Command) -> str: 3441 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3442 3443 def comment_sql(self, expression: exp.Comment) -> str: 3444 this = self.sql(expression, "this") 3445 kind = expression.args["kind"] 3446 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3447 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3448 expression_sql = self.sql(expression, "expression") 3449 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3450 3451 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3452 this = self.sql(expression, "this") 3453 delete = " DELETE" if expression.args.get("delete") else "" 3454 recompress = self.sql(expression, "recompress") 3455 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3456 to_disk = self.sql(expression, "to_disk") 3457 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3458 to_volume = self.sql(expression, "to_volume") 3459 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3460 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3461 3462 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3463 where = self.sql(expression, "where") 3464 group = self.sql(expression, "group") 3465 aggregates = self.expressions(expression, key="aggregates") 3466 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3467 3468 if not (where or group or aggregates) and len(expression.expressions) == 1: 3469 return f"TTL {self.expressions(expression, flat=True)}" 3470 3471 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3472 3473 def transaction_sql(self, expression: exp.Transaction) -> str: 3474 modes = self.expressions(expression, key="modes") 3475 modes = f" {modes}" if modes else "" 3476 return f"BEGIN{modes}" 3477 3478 def commit_sql(self, expression: exp.Commit) -> str: 3479 chain = expression.args.get("chain") 3480 if chain is not None: 3481 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3482 3483 return f"COMMIT{chain or ''}" 3484 3485 def rollback_sql(self, expression: exp.Rollback) -> str: 3486 savepoint = expression.args.get("savepoint") 3487 savepoint = f" TO {savepoint}" if savepoint else "" 3488 return f"ROLLBACK{savepoint}" 3489 3490 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3491 this = self.sql(expression, "this") 3492 3493 dtype = self.sql(expression, "dtype") 3494 if dtype: 3495 collate = self.sql(expression, "collate") 3496 collate = f" COLLATE {collate}" if collate else "" 3497 using = self.sql(expression, "using") 3498 using = f" USING {using}" if using else "" 3499 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3500 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3501 3502 default = self.sql(expression, "default") 3503 if default: 3504 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3505 3506 comment = self.sql(expression, "comment") 3507 if comment: 3508 return f"ALTER COLUMN {this} COMMENT {comment}" 3509 3510 visible = expression.args.get("visible") 3511 if visible: 3512 return f"ALTER COLUMN {this} SET {visible}" 3513 3514 allow_null = expression.args.get("allow_null") 3515 drop = expression.args.get("drop") 3516 3517 if not drop and not allow_null: 3518 self.unsupported("Unsupported ALTER COLUMN syntax") 3519 3520 if allow_null is not None: 3521 keyword = "DROP" if drop else "SET" 3522 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3523 3524 return f"ALTER COLUMN {this} DROP DEFAULT" 3525 3526 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3527 this = self.sql(expression, "this") 3528 3529 visible = expression.args.get("visible") 3530 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3531 3532 return f"ALTER INDEX {this} {visible_sql}" 3533 3534 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3535 this = self.sql(expression, "this") 3536 if not isinstance(expression.this, exp.Var): 3537 this = f"KEY DISTKEY {this}" 3538 return f"ALTER DISTSTYLE {this}" 3539 3540 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3541 compound = " COMPOUND" if expression.args.get("compound") else "" 3542 this = self.sql(expression, "this") 3543 expressions = self.expressions(expression, flat=True) 3544 expressions = f"({expressions})" if expressions else "" 3545 return f"ALTER{compound} SORTKEY {this or expressions}" 3546 3547 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3548 if not self.RENAME_TABLE_WITH_DB: 3549 # Remove db from tables 3550 expression = expression.transform( 3551 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3552 ).assert_is(exp.AlterRename) 3553 this = self.sql(expression, "this") 3554 to_kw = " TO" if include_to else "" 3555 return f"RENAME{to_kw} {this}" 3556 3557 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3558 exists = " IF EXISTS" if expression.args.get("exists") else "" 3559 old_column = self.sql(expression, "this") 3560 new_column = self.sql(expression, "to") 3561 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3562 3563 def alterset_sql(self, expression: exp.AlterSet) -> str: 3564 exprs = self.expressions(expression, flat=True) 3565 if self.ALTER_SET_WRAPPED: 3566 exprs = f"({exprs})" 3567 3568 return f"SET {exprs}" 3569 3570 def alter_sql(self, expression: exp.Alter) -> str: 3571 actions = expression.args["actions"] 3572 3573 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3574 actions[0], exp.ColumnDef 3575 ): 3576 actions_sql = self.expressions(expression, key="actions", flat=True) 3577 actions_sql = f"ADD {actions_sql}" 3578 else: 3579 actions_list = [] 3580 for action in actions: 3581 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3582 action_sql = self.add_column_sql(action) 3583 else: 3584 action_sql = self.sql(action) 3585 if isinstance(action, exp.Query): 3586 action_sql = f"AS {action_sql}" 3587 3588 actions_list.append(action_sql) 3589 3590 actions_sql = self.format_args(*actions_list).lstrip("\n") 3591 3592 exists = " IF EXISTS" if expression.args.get("exists") else "" 3593 on_cluster = self.sql(expression, "cluster") 3594 on_cluster = f" {on_cluster}" if on_cluster else "" 3595 only = " ONLY" if expression.args.get("only") else "" 3596 options = self.expressions(expression, key="options") 3597 options = f", {options}" if options else "" 3598 kind = self.sql(expression, "kind") 3599 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3600 check = " WITH CHECK" if expression.args.get("check") else "" 3601 this = self.sql(expression, "this") 3602 this = f" {this}" if this else "" 3603 3604 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}" 3605 3606 def altersession_sql(self, expression: exp.AlterSession) -> str: 3607 items_sql = self.expressions(expression, flat=True) 3608 keyword = "UNSET" if expression.args.get("unset") else "SET" 3609 return f"{keyword} {items_sql}" 3610 3611 def add_column_sql(self, expression: exp.Expression) -> str: 3612 sql = self.sql(expression) 3613 if isinstance(expression, exp.Schema): 3614 column_text = " COLUMNS" 3615 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3616 column_text = " COLUMN" 3617 else: 3618 column_text = "" 3619 3620 return f"ADD{column_text} {sql}" 3621 3622 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3623 expressions = self.expressions(expression) 3624 exists = " IF EXISTS " if expression.args.get("exists") else " " 3625 return f"DROP{exists}{expressions}" 3626 3627 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3628 return f"ADD {self.expressions(expression, indent=False)}" 3629 3630 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3631 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3632 location = self.sql(expression, "location") 3633 location = f" {location}" if location else "" 3634 return f"ADD {exists}{self.sql(expression.this)}{location}" 3635 3636 def distinct_sql(self, expression: exp.Distinct) -> str: 3637 this = self.expressions(expression, flat=True) 3638 3639 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3640 case = exp.case() 3641 for arg in expression.expressions: 3642 case = case.when(arg.is_(exp.null()), exp.null()) 3643 this = self.sql(case.else_(f"({this})")) 3644 3645 this = f" {this}" if this else "" 3646 3647 on = self.sql(expression, "on") 3648 on = f" ON {on}" if on else "" 3649 return f"DISTINCT{this}{on}" 3650 3651 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3652 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3653 3654 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3655 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3656 3657 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3658 this_sql = self.sql(expression, "this") 3659 expression_sql = self.sql(expression, "expression") 3660 kind = "MAX" if expression.args.get("max") else "MIN" 3661 return f"{this_sql} HAVING {kind} {expression_sql}" 3662 3663 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3664 return self.sql( 3665 exp.Cast( 3666 this=exp.Div(this=expression.this, expression=expression.expression), 3667 to=exp.DataType(this=exp.DataType.Type.INT), 3668 ) 3669 ) 3670 3671 def dpipe_sql(self, expression: exp.DPipe) -> str: 3672 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3673 return self.func( 3674 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3675 ) 3676 return self.binary(expression, "||") 3677 3678 def div_sql(self, expression: exp.Div) -> str: 3679 l, r = expression.left, expression.right 3680 3681 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3682 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3683 3684 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3685 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3686 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3687 3688 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3689 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3690 return self.sql( 3691 exp.cast( 3692 l / r, 3693 to=exp.DataType.Type.BIGINT, 3694 ) 3695 ) 3696 3697 return self.binary(expression, "/") 3698 3699 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3700 n = exp._wrap(expression.this, exp.Binary) 3701 d = exp._wrap(expression.expression, exp.Binary) 3702 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3703 3704 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3705 return self.binary(expression, "OVERLAPS") 3706 3707 def distance_sql(self, expression: exp.Distance) -> str: 3708 return self.binary(expression, "<->") 3709 3710 def dot_sql(self, expression: exp.Dot) -> str: 3711 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3712 3713 def eq_sql(self, expression: exp.EQ) -> str: 3714 return self.binary(expression, "=") 3715 3716 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3717 return self.binary(expression, ":=") 3718 3719 def escape_sql(self, expression: exp.Escape) -> str: 3720 return self.binary(expression, "ESCAPE") 3721 3722 def glob_sql(self, expression: exp.Glob) -> str: 3723 return self.binary(expression, "GLOB") 3724 3725 def gt_sql(self, expression: exp.GT) -> str: 3726 return self.binary(expression, ">") 3727 3728 def gte_sql(self, expression: exp.GTE) -> str: 3729 return self.binary(expression, ">=") 3730 3731 def is_sql(self, expression: exp.Is) -> str: 3732 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3733 return self.sql( 3734 expression.this if expression.expression.this else exp.not_(expression.this) 3735 ) 3736 return self.binary(expression, "IS") 3737 3738 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3739 this = expression.this 3740 rhs = expression.expression 3741 3742 if isinstance(expression, exp.Like): 3743 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3744 op = "LIKE" 3745 else: 3746 exp_class = exp.ILike 3747 op = "ILIKE" 3748 3749 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3750 exprs = rhs.this.unnest() 3751 3752 if isinstance(exprs, exp.Tuple): 3753 exprs = exprs.expressions 3754 3755 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3756 3757 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3758 for expr in exprs[1:]: 3759 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3760 3761 return self.sql(like_expr) 3762 3763 return self.binary(expression, op) 3764 3765 def like_sql(self, expression: exp.Like) -> str: 3766 return self._like_sql(expression) 3767 3768 def ilike_sql(self, expression: exp.ILike) -> str: 3769 return self._like_sql(expression) 3770 3771 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3772 return self.binary(expression, "SIMILAR TO") 3773 3774 def lt_sql(self, expression: exp.LT) -> str: 3775 return self.binary(expression, "<") 3776 3777 def lte_sql(self, expression: exp.LTE) -> str: 3778 return self.binary(expression, "<=") 3779 3780 def mod_sql(self, expression: exp.Mod) -> str: 3781 return self.binary(expression, "%") 3782 3783 def mul_sql(self, expression: exp.Mul) -> str: 3784 return self.binary(expression, "*") 3785 3786 def neq_sql(self, expression: exp.NEQ) -> str: 3787 return self.binary(expression, "<>") 3788 3789 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3790 return self.binary(expression, "IS NOT DISTINCT FROM") 3791 3792 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3793 return self.binary(expression, "IS DISTINCT FROM") 3794 3795 def slice_sql(self, expression: exp.Slice) -> str: 3796 return self.binary(expression, ":") 3797 3798 def sub_sql(self, expression: exp.Sub) -> str: 3799 return self.binary(expression, "-") 3800 3801 def trycast_sql(self, expression: exp.TryCast) -> str: 3802 return self.cast_sql(expression, safe_prefix="TRY_") 3803 3804 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3805 return self.cast_sql(expression) 3806 3807 def try_sql(self, expression: exp.Try) -> str: 3808 if not self.TRY_SUPPORTED: 3809 self.unsupported("Unsupported TRY function") 3810 return self.sql(expression, "this") 3811 3812 return self.func("TRY", expression.this) 3813 3814 def log_sql(self, expression: exp.Log) -> str: 3815 this = expression.this 3816 expr = expression.expression 3817 3818 if self.dialect.LOG_BASE_FIRST is False: 3819 this, expr = expr, this 3820 elif self.dialect.LOG_BASE_FIRST is None and expr: 3821 if this.name in ("2", "10"): 3822 return self.func(f"LOG{this.name}", expr) 3823 3824 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3825 3826 return self.func("LOG", this, expr) 3827 3828 def use_sql(self, expression: exp.Use) -> str: 3829 kind = self.sql(expression, "kind") 3830 kind = f" {kind}" if kind else "" 3831 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3832 this = f" {this}" if this else "" 3833 return f"USE{kind}{this}" 3834 3835 def binary(self, expression: exp.Binary, op: str) -> str: 3836 sqls: t.List[str] = [] 3837 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3838 binary_type = type(expression) 3839 3840 while stack: 3841 node = stack.pop() 3842 3843 if type(node) is binary_type: 3844 op_func = node.args.get("operator") 3845 if op_func: 3846 op = f"OPERATOR({self.sql(op_func)})" 3847 3848 stack.append(node.right) 3849 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3850 stack.append(node.left) 3851 else: 3852 sqls.append(self.sql(node)) 3853 3854 return "".join(sqls) 3855 3856 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3857 to_clause = self.sql(expression, "to") 3858 if to_clause: 3859 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3860 3861 return self.function_fallback_sql(expression) 3862 3863 def function_fallback_sql(self, expression: exp.Func) -> str: 3864 args = [] 3865 3866 for key in expression.arg_types: 3867 arg_value = expression.args.get(key) 3868 3869 if isinstance(arg_value, list): 3870 for value in arg_value: 3871 args.append(value) 3872 elif arg_value is not None: 3873 args.append(arg_value) 3874 3875 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3876 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3877 else: 3878 name = expression.sql_name() 3879 3880 return self.func(name, *args) 3881 3882 def func( 3883 self, 3884 name: str, 3885 *args: t.Optional[exp.Expression | str], 3886 prefix: str = "(", 3887 suffix: str = ")", 3888 normalize: bool = True, 3889 ) -> str: 3890 name = self.normalize_func(name) if normalize else name 3891 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3892 3893 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3894 arg_sqls = tuple( 3895 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3896 ) 3897 if self.pretty and self.too_wide(arg_sqls): 3898 return self.indent( 3899 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3900 ) 3901 return sep.join(arg_sqls) 3902 3903 def too_wide(self, args: t.Iterable) -> bool: 3904 return sum(len(arg) for arg in args) > self.max_text_width 3905 3906 def format_time( 3907 self, 3908 expression: exp.Expression, 3909 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3910 inverse_time_trie: t.Optional[t.Dict] = None, 3911 ) -> t.Optional[str]: 3912 return format_time( 3913 self.sql(expression, "format"), 3914 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3915 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3916 ) 3917 3918 def expressions( 3919 self, 3920 expression: t.Optional[exp.Expression] = None, 3921 key: t.Optional[str] = None, 3922 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3923 flat: bool = False, 3924 indent: bool = True, 3925 skip_first: bool = False, 3926 skip_last: bool = False, 3927 sep: str = ", ", 3928 prefix: str = "", 3929 dynamic: bool = False, 3930 new_line: bool = False, 3931 ) -> str: 3932 expressions = expression.args.get(key or "expressions") if expression else sqls 3933 3934 if not expressions: 3935 return "" 3936 3937 if flat: 3938 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3939 3940 num_sqls = len(expressions) 3941 result_sqls = [] 3942 3943 for i, e in enumerate(expressions): 3944 sql = self.sql(e, comment=False) 3945 if not sql: 3946 continue 3947 3948 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3949 3950 if self.pretty: 3951 if self.leading_comma: 3952 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3953 else: 3954 result_sqls.append( 3955 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3956 ) 3957 else: 3958 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3959 3960 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3961 if new_line: 3962 result_sqls.insert(0, "") 3963 result_sqls.append("") 3964 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3965 else: 3966 result_sql = "".join(result_sqls) 3967 3968 return ( 3969 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3970 if indent 3971 else result_sql 3972 ) 3973 3974 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3975 flat = flat or isinstance(expression.parent, exp.Properties) 3976 expressions_sql = self.expressions(expression, flat=flat) 3977 if flat: 3978 return f"{op} {expressions_sql}" 3979 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3980 3981 def naked_property(self, expression: exp.Property) -> str: 3982 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3983 if not property_name: 3984 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3985 return f"{property_name} {self.sql(expression, 'this')}" 3986 3987 def tag_sql(self, expression: exp.Tag) -> str: 3988 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3989 3990 def token_sql(self, token_type: TokenType) -> str: 3991 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3992 3993 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3994 this = self.sql(expression, "this") 3995 expressions = self.no_identify(self.expressions, expression) 3996 expressions = ( 3997 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3998 ) 3999 return f"{this}{expressions}" if expressions.strip() != "" else this 4000 4001 def joinhint_sql(self, expression: exp.JoinHint) -> str: 4002 this = self.sql(expression, "this") 4003 expressions = self.expressions(expression, flat=True) 4004 return f"{this}({expressions})" 4005 4006 def kwarg_sql(self, expression: exp.Kwarg) -> str: 4007 return self.binary(expression, "=>") 4008 4009 def when_sql(self, expression: exp.When) -> str: 4010 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4011 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4012 condition = self.sql(expression, "condition") 4013 condition = f" AND {condition}" if condition else "" 4014 4015 then_expression = expression.args.get("then") 4016 if isinstance(then_expression, exp.Insert): 4017 this = self.sql(then_expression, "this") 4018 this = f"INSERT {this}" if this else "INSERT" 4019 then = self.sql(then_expression, "expression") 4020 then = f"{this} VALUES {then}" if then else this 4021 elif isinstance(then_expression, exp.Update): 4022 if isinstance(then_expression.args.get("expressions"), exp.Star): 4023 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4024 else: 4025 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4026 else: 4027 then = self.sql(then_expression) 4028 return f"WHEN {matched}{source}{condition} THEN {then}" 4029 4030 def whens_sql(self, expression: exp.Whens) -> str: 4031 return self.expressions(expression, sep=" ", indent=False) 4032 4033 def merge_sql(self, expression: exp.Merge) -> str: 4034 table = expression.this 4035 table_alias = "" 4036 4037 hints = table.args.get("hints") 4038 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4039 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4040 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4041 4042 this = self.sql(table) 4043 using = f"USING {self.sql(expression, 'using')}" 4044 on = f"ON {self.sql(expression, 'on')}" 4045 whens = self.sql(expression, "whens") 4046 4047 returning = self.sql(expression, "returning") 4048 if returning: 4049 whens = f"{whens}{returning}" 4050 4051 sep = self.sep() 4052 4053 return self.prepend_ctes( 4054 expression, 4055 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4056 ) 4057 4058 @unsupported_args("format") 4059 def tochar_sql(self, expression: exp.ToChar) -> str: 4060 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4061 4062 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4063 if not self.SUPPORTS_TO_NUMBER: 4064 self.unsupported("Unsupported TO_NUMBER function") 4065 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4066 4067 fmt = expression.args.get("format") 4068 if not fmt: 4069 self.unsupported("Conversion format is required for TO_NUMBER") 4070 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4071 4072 return self.func("TO_NUMBER", expression.this, fmt) 4073 4074 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4075 this = self.sql(expression, "this") 4076 kind = self.sql(expression, "kind") 4077 settings_sql = self.expressions(expression, key="settings", sep=" ") 4078 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4079 return f"{this}({kind}{args})" 4080 4081 def dictrange_sql(self, expression: exp.DictRange) -> str: 4082 this = self.sql(expression, "this") 4083 max = self.sql(expression, "max") 4084 min = self.sql(expression, "min") 4085 return f"{this}(MIN {min} MAX {max})" 4086 4087 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4088 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4089 4090 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4091 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4092 4093 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4094 def uniquekeyproperty_sql( 4095 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4096 ) -> str: 4097 return f"{prefix} ({self.expressions(expression, flat=True)})" 4098 4099 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4100 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4101 expressions = self.expressions(expression, flat=True) 4102 expressions = f" {self.wrap(expressions)}" if expressions else "" 4103 buckets = self.sql(expression, "buckets") 4104 kind = self.sql(expression, "kind") 4105 buckets = f" BUCKETS {buckets}" if buckets else "" 4106 order = self.sql(expression, "order") 4107 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4108 4109 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4110 return "" 4111 4112 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4113 expressions = self.expressions(expression, key="expressions", flat=True) 4114 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4115 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4116 buckets = self.sql(expression, "buckets") 4117 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4118 4119 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4120 this = self.sql(expression, "this") 4121 having = self.sql(expression, "having") 4122 4123 if having: 4124 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4125 4126 return self.func("ANY_VALUE", this) 4127 4128 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4129 transform = self.func("TRANSFORM", *expression.expressions) 4130 row_format_before = self.sql(expression, "row_format_before") 4131 row_format_before = f" {row_format_before}" if row_format_before else "" 4132 record_writer = self.sql(expression, "record_writer") 4133 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4134 using = f" USING {self.sql(expression, 'command_script')}" 4135 schema = self.sql(expression, "schema") 4136 schema = f" AS {schema}" if schema else "" 4137 row_format_after = self.sql(expression, "row_format_after") 4138 row_format_after = f" {row_format_after}" if row_format_after else "" 4139 record_reader = self.sql(expression, "record_reader") 4140 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4141 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4142 4143 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4144 key_block_size = self.sql(expression, "key_block_size") 4145 if key_block_size: 4146 return f"KEY_BLOCK_SIZE = {key_block_size}" 4147 4148 using = self.sql(expression, "using") 4149 if using: 4150 return f"USING {using}" 4151 4152 parser = self.sql(expression, "parser") 4153 if parser: 4154 return f"WITH PARSER {parser}" 4155 4156 comment = self.sql(expression, "comment") 4157 if comment: 4158 return f"COMMENT {comment}" 4159 4160 visible = expression.args.get("visible") 4161 if visible is not None: 4162 return "VISIBLE" if visible else "INVISIBLE" 4163 4164 engine_attr = self.sql(expression, "engine_attr") 4165 if engine_attr: 4166 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4167 4168 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4169 if secondary_engine_attr: 4170 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4171 4172 self.unsupported("Unsupported index constraint option.") 4173 return "" 4174 4175 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4176 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4177 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4178 4179 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4180 kind = self.sql(expression, "kind") 4181 kind = f"{kind} INDEX" if kind else "INDEX" 4182 this = self.sql(expression, "this") 4183 this = f" {this}" if this else "" 4184 index_type = self.sql(expression, "index_type") 4185 index_type = f" USING {index_type}" if index_type else "" 4186 expressions = self.expressions(expression, flat=True) 4187 expressions = f" ({expressions})" if expressions else "" 4188 options = self.expressions(expression, key="options", sep=" ") 4189 options = f" {options}" if options else "" 4190 return f"{kind}{this}{index_type}{expressions}{options}" 4191 4192 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4193 if self.NVL2_SUPPORTED: 4194 return self.function_fallback_sql(expression) 4195 4196 case = exp.Case().when( 4197 expression.this.is_(exp.null()).not_(copy=False), 4198 expression.args["true"], 4199 copy=False, 4200 ) 4201 else_cond = expression.args.get("false") 4202 if else_cond: 4203 case.else_(else_cond, copy=False) 4204 4205 return self.sql(case) 4206 4207 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4208 this = self.sql(expression, "this") 4209 expr = self.sql(expression, "expression") 4210 iterator = self.sql(expression, "iterator") 4211 condition = self.sql(expression, "condition") 4212 condition = f" IF {condition}" if condition else "" 4213 return f"{this} FOR {expr} IN {iterator}{condition}" 4214 4215 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4216 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4217 4218 def opclass_sql(self, expression: exp.Opclass) -> str: 4219 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4220 4221 def _ml_sql(self, expression: exp.Func, name: str) -> str: 4222 model = self.sql(expression, "this") 4223 model = f"MODEL {model}" 4224 expr = expression.expression 4225 if expr: 4226 expr_sql = self.sql(expression, "expression") 4227 expr_sql = f"TABLE {expr_sql}" if not isinstance(expr, exp.Subquery) else expr_sql 4228 else: 4229 expr_sql = None 4230 4231 parameters = self.sql(expression, "params_struct") or None 4232 4233 return self.func(name, model, expr_sql, parameters) 4234 4235 def predict_sql(self, expression: exp.Predict) -> str: 4236 return self._ml_sql(expression, "PREDICT") 4237 4238 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4239 name = "GENERATE_TEXT_EMBEDDING" if expression.args.get("is_text") else "GENERATE_EMBEDDING" 4240 return self._ml_sql(expression, name) 4241 4242 def mltranslate_sql(self, expression: exp.MLTranslate) -> str: 4243 return self._ml_sql(expression, "TRANSLATE") 4244 4245 def mlforecast_sql(self, expression: exp.MLForecast) -> str: 4246 return self._ml_sql(expression, "FORECAST") 4247 4248 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4249 this_sql = self.sql(expression, "this") 4250 if isinstance(expression.this, exp.Table): 4251 this_sql = f"TABLE {this_sql}" 4252 4253 return self.func( 4254 "FEATURES_AT_TIME", 4255 this_sql, 4256 expression.args.get("time"), 4257 expression.args.get("num_rows"), 4258 expression.args.get("ignore_feature_nulls"), 4259 ) 4260 4261 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4262 this_sql = self.sql(expression, "this") 4263 if isinstance(expression.this, exp.Table): 4264 this_sql = f"TABLE {this_sql}" 4265 4266 query_table = self.sql(expression, "query_table") 4267 if isinstance(expression.args["query_table"], exp.Table): 4268 query_table = f"TABLE {query_table}" 4269 4270 return self.func( 4271 "VECTOR_SEARCH", 4272 this_sql, 4273 expression.args.get("column_to_search"), 4274 query_table, 4275 expression.args.get("query_column_to_search"), 4276 expression.args.get("top_k"), 4277 expression.args.get("distance_type"), 4278 expression.args.get("options"), 4279 ) 4280 4281 def forin_sql(self, expression: exp.ForIn) -> str: 4282 this = self.sql(expression, "this") 4283 expression_sql = self.sql(expression, "expression") 4284 return f"FOR {this} DO {expression_sql}" 4285 4286 def refresh_sql(self, expression: exp.Refresh) -> str: 4287 this = self.sql(expression, "this") 4288 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4289 return f"REFRESH {table}{this}" 4290 4291 def toarray_sql(self, expression: exp.ToArray) -> str: 4292 arg = expression.this 4293 if not arg.type: 4294 from sqlglot.optimizer.annotate_types import annotate_types 4295 4296 arg = annotate_types(arg, dialect=self.dialect) 4297 4298 if arg.is_type(exp.DataType.Type.ARRAY): 4299 return self.sql(arg) 4300 4301 cond_for_null = arg.is_(exp.null()) 4302 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4303 4304 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4305 this = expression.this 4306 time_format = self.format_time(expression) 4307 4308 if time_format: 4309 return self.sql( 4310 exp.cast( 4311 exp.StrToTime(this=this, format=expression.args["format"]), 4312 exp.DataType.Type.TIME, 4313 ) 4314 ) 4315 4316 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4317 return self.sql(this) 4318 4319 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4320 4321 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4322 this = expression.this 4323 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4324 return self.sql(this) 4325 4326 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4327 4328 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4329 this = expression.this 4330 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4331 return self.sql(this) 4332 4333 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4334 4335 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4336 this = expression.this 4337 time_format = self.format_time(expression) 4338 4339 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4340 return self.sql( 4341 exp.cast( 4342 exp.StrToTime(this=this, format=expression.args["format"]), 4343 exp.DataType.Type.DATE, 4344 ) 4345 ) 4346 4347 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4348 return self.sql(this) 4349 4350 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4351 4352 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4353 return self.sql( 4354 exp.func( 4355 "DATEDIFF", 4356 expression.this, 4357 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4358 "day", 4359 ) 4360 ) 4361 4362 def lastday_sql(self, expression: exp.LastDay) -> str: 4363 if self.LAST_DAY_SUPPORTS_DATE_PART: 4364 return self.function_fallback_sql(expression) 4365 4366 unit = expression.text("unit") 4367 if unit and unit != "MONTH": 4368 self.unsupported("Date parts are not supported in LAST_DAY.") 4369 4370 return self.func("LAST_DAY", expression.this) 4371 4372 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4373 from sqlglot.dialects.dialect import unit_to_str 4374 4375 return self.func( 4376 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4377 ) 4378 4379 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4380 if self.CAN_IMPLEMENT_ARRAY_ANY: 4381 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4382 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4383 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4384 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4385 4386 from sqlglot.dialects import Dialect 4387 4388 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4389 if self.dialect.__class__ != Dialect: 4390 self.unsupported("ARRAY_ANY is unsupported") 4391 4392 return self.function_fallback_sql(expression) 4393 4394 def struct_sql(self, expression: exp.Struct) -> str: 4395 expression.set( 4396 "expressions", 4397 [ 4398 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4399 if isinstance(e, exp.PropertyEQ) 4400 else e 4401 for e in expression.expressions 4402 ], 4403 ) 4404 4405 return self.function_fallback_sql(expression) 4406 4407 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4408 low = self.sql(expression, "this") 4409 high = self.sql(expression, "expression") 4410 4411 return f"{low} TO {high}" 4412 4413 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4414 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4415 tables = f" {self.expressions(expression)}" 4416 4417 exists = " IF EXISTS" if expression.args.get("exists") else "" 4418 4419 on_cluster = self.sql(expression, "cluster") 4420 on_cluster = f" {on_cluster}" if on_cluster else "" 4421 4422 identity = self.sql(expression, "identity") 4423 identity = f" {identity} IDENTITY" if identity else "" 4424 4425 option = self.sql(expression, "option") 4426 option = f" {option}" if option else "" 4427 4428 partition = self.sql(expression, "partition") 4429 partition = f" {partition}" if partition else "" 4430 4431 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4432 4433 # This transpiles T-SQL's CONVERT function 4434 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4435 def convert_sql(self, expression: exp.Convert) -> str: 4436 to = expression.this 4437 value = expression.expression 4438 style = expression.args.get("style") 4439 safe = expression.args.get("safe") 4440 strict = expression.args.get("strict") 4441 4442 if not to or not value: 4443 return "" 4444 4445 # Retrieve length of datatype and override to default if not specified 4446 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4447 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4448 4449 transformed: t.Optional[exp.Expression] = None 4450 cast = exp.Cast if strict else exp.TryCast 4451 4452 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4453 if isinstance(style, exp.Literal) and style.is_int: 4454 from sqlglot.dialects.tsql import TSQL 4455 4456 style_value = style.name 4457 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4458 if not converted_style: 4459 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4460 4461 fmt = exp.Literal.string(converted_style) 4462 4463 if to.this == exp.DataType.Type.DATE: 4464 transformed = exp.StrToDate(this=value, format=fmt) 4465 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4466 transformed = exp.StrToTime(this=value, format=fmt) 4467 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4468 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4469 elif to.this == exp.DataType.Type.TEXT: 4470 transformed = exp.TimeToStr(this=value, format=fmt) 4471 4472 if not transformed: 4473 transformed = cast(this=value, to=to, safe=safe) 4474 4475 return self.sql(transformed) 4476 4477 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4478 this = expression.this 4479 if isinstance(this, exp.JSONPathWildcard): 4480 this = self.json_path_part(this) 4481 return f".{this}" if this else "" 4482 4483 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4484 return f".{this}" 4485 4486 this = self.json_path_part(this) 4487 return ( 4488 f"[{this}]" 4489 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4490 else f".{this}" 4491 ) 4492 4493 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4494 this = self.json_path_part(expression.this) 4495 return f"[{this}]" if this else "" 4496 4497 def _simplify_unless_literal(self, expression: E) -> E: 4498 if not isinstance(expression, exp.Literal): 4499 from sqlglot.optimizer.simplify import simplify 4500 4501 expression = simplify(expression, dialect=self.dialect) 4502 4503 return expression 4504 4505 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4506 this = expression.this 4507 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4508 self.unsupported( 4509 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4510 ) 4511 return self.sql(this) 4512 4513 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4514 # The first modifier here will be the one closest to the AggFunc's arg 4515 mods = sorted( 4516 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4517 key=lambda x: 0 4518 if isinstance(x, exp.HavingMax) 4519 else (1 if isinstance(x, exp.Order) else 2), 4520 ) 4521 4522 if mods: 4523 mod = mods[0] 4524 this = expression.__class__(this=mod.this.copy()) 4525 this.meta["inline"] = True 4526 mod.this.replace(this) 4527 return self.sql(expression.this) 4528 4529 agg_func = expression.find(exp.AggFunc) 4530 4531 if agg_func: 4532 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4533 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4534 4535 return f"{self.sql(expression, 'this')} {text}" 4536 4537 def _replace_line_breaks(self, string: str) -> str: 4538 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4539 if self.pretty: 4540 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4541 return string 4542 4543 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4544 option = self.sql(expression, "this") 4545 4546 if expression.expressions: 4547 upper = option.upper() 4548 4549 # Snowflake FILE_FORMAT options are separated by whitespace 4550 sep = " " if upper == "FILE_FORMAT" else ", " 4551 4552 # Databricks copy/format options do not set their list of values with EQ 4553 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4554 values = self.expressions(expression, flat=True, sep=sep) 4555 return f"{option}{op}({values})" 4556 4557 value = self.sql(expression, "expression") 4558 4559 if not value: 4560 return option 4561 4562 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4563 4564 return f"{option}{op}{value}" 4565 4566 def credentials_sql(self, expression: exp.Credentials) -> str: 4567 cred_expr = expression.args.get("credentials") 4568 if isinstance(cred_expr, exp.Literal): 4569 # Redshift case: CREDENTIALS <string> 4570 credentials = self.sql(expression, "credentials") 4571 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4572 else: 4573 # Snowflake case: CREDENTIALS = (...) 4574 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4575 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4576 4577 storage = self.sql(expression, "storage") 4578 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4579 4580 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4581 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4582 4583 iam_role = self.sql(expression, "iam_role") 4584 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4585 4586 region = self.sql(expression, "region") 4587 region = f" REGION {region}" if region else "" 4588 4589 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4590 4591 def copy_sql(self, expression: exp.Copy) -> str: 4592 this = self.sql(expression, "this") 4593 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4594 4595 credentials = self.sql(expression, "credentials") 4596 credentials = self.seg(credentials) if credentials else "" 4597 files = self.expressions(expression, key="files", flat=True) 4598 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4599 4600 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4601 params = self.expressions( 4602 expression, 4603 key="params", 4604 sep=sep, 4605 new_line=True, 4606 skip_last=True, 4607 skip_first=True, 4608 indent=self.COPY_PARAMS_ARE_WRAPPED, 4609 ) 4610 4611 if params: 4612 if self.COPY_PARAMS_ARE_WRAPPED: 4613 params = f" WITH ({params})" 4614 elif not self.pretty and (files or credentials): 4615 params = f" {params}" 4616 4617 return f"COPY{this}{kind} {files}{credentials}{params}" 4618 4619 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4620 return "" 4621 4622 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4623 on_sql = "ON" if expression.args.get("on") else "OFF" 4624 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4625 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4626 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4627 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4628 4629 if filter_col or retention_period: 4630 on_sql = self.func("ON", filter_col, retention_period) 4631 4632 return f"DATA_DELETION={on_sql}" 4633 4634 def maskingpolicycolumnconstraint_sql( 4635 self, expression: exp.MaskingPolicyColumnConstraint 4636 ) -> str: 4637 this = self.sql(expression, "this") 4638 expressions = self.expressions(expression, flat=True) 4639 expressions = f" USING ({expressions})" if expressions else "" 4640 return f"MASKING POLICY {this}{expressions}" 4641 4642 def gapfill_sql(self, expression: exp.GapFill) -> str: 4643 this = self.sql(expression, "this") 4644 this = f"TABLE {this}" 4645 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4646 4647 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4648 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4649 4650 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4651 this = self.sql(expression, "this") 4652 expr = expression.expression 4653 4654 if isinstance(expr, exp.Func): 4655 # T-SQL's CLR functions are case sensitive 4656 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4657 else: 4658 expr = self.sql(expression, "expression") 4659 4660 return self.scope_resolution(expr, this) 4661 4662 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4663 if self.PARSE_JSON_NAME is None: 4664 return self.sql(expression.this) 4665 4666 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4667 4668 def rand_sql(self, expression: exp.Rand) -> str: 4669 lower = self.sql(expression, "lower") 4670 upper = self.sql(expression, "upper") 4671 4672 if lower and upper: 4673 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4674 return self.func("RAND", expression.this) 4675 4676 def changes_sql(self, expression: exp.Changes) -> str: 4677 information = self.sql(expression, "information") 4678 information = f"INFORMATION => {information}" 4679 at_before = self.sql(expression, "at_before") 4680 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4681 end = self.sql(expression, "end") 4682 end = f"{self.seg('')}{end}" if end else "" 4683 4684 return f"CHANGES ({information}){at_before}{end}" 4685 4686 def pad_sql(self, expression: exp.Pad) -> str: 4687 prefix = "L" if expression.args.get("is_left") else "R" 4688 4689 fill_pattern = self.sql(expression, "fill_pattern") or None 4690 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4691 fill_pattern = "' '" 4692 4693 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4694 4695 def summarize_sql(self, expression: exp.Summarize) -> str: 4696 table = " TABLE" if expression.args.get("table") else "" 4697 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4698 4699 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4700 generate_series = exp.GenerateSeries(**expression.args) 4701 4702 parent = expression.parent 4703 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4704 parent = parent.parent 4705 4706 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4707 return self.sql(exp.Unnest(expressions=[generate_series])) 4708 4709 if isinstance(parent, exp.Select): 4710 self.unsupported("GenerateSeries projection unnesting is not supported.") 4711 4712 return self.sql(generate_series) 4713 4714 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4715 exprs = expression.expressions 4716 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4717 if len(exprs) == 0: 4718 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4719 else: 4720 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4721 else: 4722 rhs = self.expressions(expression) # type: ignore 4723 4724 return self.func(name, expression.this, rhs or None) 4725 4726 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4727 if self.SUPPORTS_CONVERT_TIMEZONE: 4728 return self.function_fallback_sql(expression) 4729 4730 source_tz = expression.args.get("source_tz") 4731 target_tz = expression.args.get("target_tz") 4732 timestamp = expression.args.get("timestamp") 4733 4734 if source_tz and timestamp: 4735 timestamp = exp.AtTimeZone( 4736 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4737 ) 4738 4739 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4740 4741 return self.sql(expr) 4742 4743 def json_sql(self, expression: exp.JSON) -> str: 4744 this = self.sql(expression, "this") 4745 this = f" {this}" if this else "" 4746 4747 _with = expression.args.get("with") 4748 4749 if _with is None: 4750 with_sql = "" 4751 elif not _with: 4752 with_sql = " WITHOUT" 4753 else: 4754 with_sql = " WITH" 4755 4756 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4757 4758 return f"JSON{this}{with_sql}{unique_sql}" 4759 4760 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4761 def _generate_on_options(arg: t.Any) -> str: 4762 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4763 4764 path = self.sql(expression, "path") 4765 returning = self.sql(expression, "returning") 4766 returning = f" RETURNING {returning}" if returning else "" 4767 4768 on_condition = self.sql(expression, "on_condition") 4769 on_condition = f" {on_condition}" if on_condition else "" 4770 4771 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4772 4773 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4774 else_ = "ELSE " if expression.args.get("else_") else "" 4775 condition = self.sql(expression, "expression") 4776 condition = f"WHEN {condition} THEN " if condition else else_ 4777 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4778 return f"{condition}{insert}" 4779 4780 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4781 kind = self.sql(expression, "kind") 4782 expressions = self.seg(self.expressions(expression, sep=" ")) 4783 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4784 return res 4785 4786 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4787 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4788 empty = expression.args.get("empty") 4789 empty = ( 4790 f"DEFAULT {empty} ON EMPTY" 4791 if isinstance(empty, exp.Expression) 4792 else self.sql(expression, "empty") 4793 ) 4794 4795 error = expression.args.get("error") 4796 error = ( 4797 f"DEFAULT {error} ON ERROR" 4798 if isinstance(error, exp.Expression) 4799 else self.sql(expression, "error") 4800 ) 4801 4802 if error and empty: 4803 error = ( 4804 f"{empty} {error}" 4805 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4806 else f"{error} {empty}" 4807 ) 4808 empty = "" 4809 4810 null = self.sql(expression, "null") 4811 4812 return f"{empty}{error}{null}" 4813 4814 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4815 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4816 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4817 4818 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4819 this = self.sql(expression, "this") 4820 path = self.sql(expression, "path") 4821 4822 passing = self.expressions(expression, "passing") 4823 passing = f" PASSING {passing}" if passing else "" 4824 4825 on_condition = self.sql(expression, "on_condition") 4826 on_condition = f" {on_condition}" if on_condition else "" 4827 4828 path = f"{path}{passing}{on_condition}" 4829 4830 return self.func("JSON_EXISTS", this, path) 4831 4832 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4833 array_agg = self.function_fallback_sql(expression) 4834 4835 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4836 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4837 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4838 parent = expression.parent 4839 if isinstance(parent, exp.Filter): 4840 parent_cond = parent.expression.this 4841 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4842 else: 4843 this = expression.this 4844 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4845 if this.find(exp.Column): 4846 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4847 this_sql = ( 4848 self.expressions(this) 4849 if isinstance(this, exp.Distinct) 4850 else self.sql(expression, "this") 4851 ) 4852 4853 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4854 4855 return array_agg 4856 4857 def apply_sql(self, expression: exp.Apply) -> str: 4858 this = self.sql(expression, "this") 4859 expr = self.sql(expression, "expression") 4860 4861 return f"{this} APPLY({expr})" 4862 4863 def _grant_or_revoke_sql( 4864 self, 4865 expression: exp.Grant | exp.Revoke, 4866 keyword: str, 4867 preposition: str, 4868 grant_option_prefix: str = "", 4869 grant_option_suffix: str = "", 4870 ) -> str: 4871 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4872 4873 kind = self.sql(expression, "kind") 4874 kind = f" {kind}" if kind else "" 4875 4876 securable = self.sql(expression, "securable") 4877 securable = f" {securable}" if securable else "" 4878 4879 principals = self.expressions(expression, key="principals", flat=True) 4880 4881 if not expression.args.get("grant_option"): 4882 grant_option_prefix = grant_option_suffix = "" 4883 4884 # cascade for revoke only 4885 cascade = self.sql(expression, "cascade") 4886 cascade = f" {cascade}" if cascade else "" 4887 4888 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 4889 4890 def grant_sql(self, expression: exp.Grant) -> str: 4891 return self._grant_or_revoke_sql( 4892 expression, 4893 keyword="GRANT", 4894 preposition="TO", 4895 grant_option_suffix=" WITH GRANT OPTION", 4896 ) 4897 4898 def revoke_sql(self, expression: exp.Revoke) -> str: 4899 return self._grant_or_revoke_sql( 4900 expression, 4901 keyword="REVOKE", 4902 preposition="FROM", 4903 grant_option_prefix="GRANT OPTION FOR ", 4904 ) 4905 4906 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4907 this = self.sql(expression, "this") 4908 columns = self.expressions(expression, flat=True) 4909 columns = f"({columns})" if columns else "" 4910 4911 return f"{this}{columns}" 4912 4913 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4914 this = self.sql(expression, "this") 4915 4916 kind = self.sql(expression, "kind") 4917 kind = f"{kind} " if kind else "" 4918 4919 return f"{kind}{this}" 4920 4921 def columns_sql(self, expression: exp.Columns): 4922 func = self.function_fallback_sql(expression) 4923 if expression.args.get("unpack"): 4924 func = f"*{func}" 4925 4926 return func 4927 4928 def overlay_sql(self, expression: exp.Overlay): 4929 this = self.sql(expression, "this") 4930 expr = self.sql(expression, "expression") 4931 from_sql = self.sql(expression, "from") 4932 for_sql = self.sql(expression, "for") 4933 for_sql = f" FOR {for_sql}" if for_sql else "" 4934 4935 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4936 4937 @unsupported_args("format") 4938 def todouble_sql(self, expression: exp.ToDouble) -> str: 4939 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4940 4941 def string_sql(self, expression: exp.String) -> str: 4942 this = expression.this 4943 zone = expression.args.get("zone") 4944 4945 if zone: 4946 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4947 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4948 # set for source_tz to transpile the time conversion before the STRING cast 4949 this = exp.ConvertTimezone( 4950 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4951 ) 4952 4953 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4954 4955 def median_sql(self, expression: exp.Median): 4956 if not self.SUPPORTS_MEDIAN: 4957 return self.sql( 4958 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4959 ) 4960 4961 return self.function_fallback_sql(expression) 4962 4963 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4964 filler = self.sql(expression, "this") 4965 filler = f" {filler}" if filler else "" 4966 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4967 return f"TRUNCATE{filler} {with_count}" 4968 4969 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4970 if self.SUPPORTS_UNIX_SECONDS: 4971 return self.function_fallback_sql(expression) 4972 4973 start_ts = exp.cast( 4974 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4975 ) 4976 4977 return self.sql( 4978 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4979 ) 4980 4981 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4982 dim = expression.expression 4983 4984 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4985 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4986 if not (dim.is_int and dim.name == "1"): 4987 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4988 dim = None 4989 4990 # If dimension is required but not specified, default initialize it 4991 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4992 dim = exp.Literal.number(1) 4993 4994 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4995 4996 def attach_sql(self, expression: exp.Attach) -> str: 4997 this = self.sql(expression, "this") 4998 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4999 expressions = self.expressions(expression) 5000 expressions = f" ({expressions})" if expressions else "" 5001 5002 return f"ATTACH{exists_sql} {this}{expressions}" 5003 5004 def detach_sql(self, expression: exp.Detach) -> str: 5005 this = self.sql(expression, "this") 5006 # the DATABASE keyword is required if IF EXISTS is set 5007 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5008 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5009 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5010 5011 return f"DETACH{exists_sql} {this}" 5012 5013 def attachoption_sql(self, expression: exp.AttachOption) -> str: 5014 this = self.sql(expression, "this") 5015 value = self.sql(expression, "expression") 5016 value = f" {value}" if value else "" 5017 return f"{this}{value}" 5018 5019 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 5020 return ( 5021 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 5022 ) 5023 5024 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5025 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5026 encode = f"{encode} {self.sql(expression, 'this')}" 5027 5028 properties = expression.args.get("properties") 5029 if properties: 5030 encode = f"{encode} {self.properties(properties)}" 5031 5032 return encode 5033 5034 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5035 this = self.sql(expression, "this") 5036 include = f"INCLUDE {this}" 5037 5038 column_def = self.sql(expression, "column_def") 5039 if column_def: 5040 include = f"{include} {column_def}" 5041 5042 alias = self.sql(expression, "alias") 5043 if alias: 5044 include = f"{include} AS {alias}" 5045 5046 return include 5047 5048 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5049 name = f"NAME {self.sql(expression, 'this')}" 5050 return self.func("XMLELEMENT", name, *expression.expressions) 5051 5052 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5053 this = self.sql(expression, "this") 5054 expr = self.sql(expression, "expression") 5055 expr = f"({expr})" if expr else "" 5056 return f"{this}{expr}" 5057 5058 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5059 partitions = self.expressions(expression, "partition_expressions") 5060 create = self.expressions(expression, "create_expressions") 5061 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5062 5063 def partitionbyrangepropertydynamic_sql( 5064 self, expression: exp.PartitionByRangePropertyDynamic 5065 ) -> str: 5066 start = self.sql(expression, "start") 5067 end = self.sql(expression, "end") 5068 5069 every = expression.args["every"] 5070 if isinstance(every, exp.Interval) and every.this.is_string: 5071 every.this.replace(exp.Literal.number(every.name)) 5072 5073 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5074 5075 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5076 name = self.sql(expression, "this") 5077 values = self.expressions(expression, flat=True) 5078 5079 return f"NAME {name} VALUE {values}" 5080 5081 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5082 kind = self.sql(expression, "kind") 5083 sample = self.sql(expression, "sample") 5084 return f"SAMPLE {sample} {kind}" 5085 5086 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5087 kind = self.sql(expression, "kind") 5088 option = self.sql(expression, "option") 5089 option = f" {option}" if option else "" 5090 this = self.sql(expression, "this") 5091 this = f" {this}" if this else "" 5092 columns = self.expressions(expression) 5093 columns = f" {columns}" if columns else "" 5094 return f"{kind}{option} STATISTICS{this}{columns}" 5095 5096 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5097 this = self.sql(expression, "this") 5098 columns = self.expressions(expression) 5099 inner_expression = self.sql(expression, "expression") 5100 inner_expression = f" {inner_expression}" if inner_expression else "" 5101 update_options = self.sql(expression, "update_options") 5102 update_options = f" {update_options} UPDATE" if update_options else "" 5103 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5104 5105 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5106 kind = self.sql(expression, "kind") 5107 kind = f" {kind}" if kind else "" 5108 return f"DELETE{kind} STATISTICS" 5109 5110 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5111 inner_expression = self.sql(expression, "expression") 5112 return f"LIST CHAINED ROWS{inner_expression}" 5113 5114 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5115 kind = self.sql(expression, "kind") 5116 this = self.sql(expression, "this") 5117 this = f" {this}" if this else "" 5118 inner_expression = self.sql(expression, "expression") 5119 return f"VALIDATE {kind}{this}{inner_expression}" 5120 5121 def analyze_sql(self, expression: exp.Analyze) -> str: 5122 options = self.expressions(expression, key="options", sep=" ") 5123 options = f" {options}" if options else "" 5124 kind = self.sql(expression, "kind") 5125 kind = f" {kind}" if kind else "" 5126 this = self.sql(expression, "this") 5127 this = f" {this}" if this else "" 5128 mode = self.sql(expression, "mode") 5129 mode = f" {mode}" if mode else "" 5130 properties = self.sql(expression, "properties") 5131 properties = f" {properties}" if properties else "" 5132 partition = self.sql(expression, "partition") 5133 partition = f" {partition}" if partition else "" 5134 inner_expression = self.sql(expression, "expression") 5135 inner_expression = f" {inner_expression}" if inner_expression else "" 5136 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5137 5138 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5139 this = self.sql(expression, "this") 5140 namespaces = self.expressions(expression, key="namespaces") 5141 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5142 passing = self.expressions(expression, key="passing") 5143 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5144 columns = self.expressions(expression, key="columns") 5145 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5146 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5147 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5148 5149 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5150 this = self.sql(expression, "this") 5151 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5152 5153 def export_sql(self, expression: exp.Export) -> str: 5154 this = self.sql(expression, "this") 5155 connection = self.sql(expression, "connection") 5156 connection = f"WITH CONNECTION {connection} " if connection else "" 5157 options = self.sql(expression, "options") 5158 return f"EXPORT DATA {connection}{options} AS {this}" 5159 5160 def declare_sql(self, expression: exp.Declare) -> str: 5161 return f"DECLARE {self.expressions(expression, flat=True)}" 5162 5163 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5164 variable = self.sql(expression, "this") 5165 default = self.sql(expression, "default") 5166 default = f" = {default}" if default else "" 5167 5168 kind = self.sql(expression, "kind") 5169 if isinstance(expression.args.get("kind"), exp.Schema): 5170 kind = f"TABLE {kind}" 5171 5172 return f"{variable} AS {kind}{default}" 5173 5174 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5175 kind = self.sql(expression, "kind") 5176 this = self.sql(expression, "this") 5177 set = self.sql(expression, "expression") 5178 using = self.sql(expression, "using") 5179 using = f" USING {using}" if using else "" 5180 5181 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5182 5183 return f"{kind_sql} {this} SET {set}{using}" 5184 5185 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5186 params = self.expressions(expression, key="params", flat=True) 5187 return self.func(expression.name, *expression.expressions) + f"({params})" 5188 5189 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5190 return self.func(expression.name, *expression.expressions) 5191 5192 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5193 return self.anonymousaggfunc_sql(expression) 5194 5195 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5196 return self.parameterizedagg_sql(expression) 5197 5198 def show_sql(self, expression: exp.Show) -> str: 5199 self.unsupported("Unsupported SHOW statement") 5200 return "" 5201 5202 def install_sql(self, expression: exp.Install) -> str: 5203 self.unsupported("Unsupported INSTALL statement") 5204 return "" 5205 5206 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5207 # Snowflake GET/PUT statements: 5208 # PUT <file> <internalStage> <properties> 5209 # GET <internalStage> <file> <properties> 5210 props = expression.args.get("properties") 5211 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5212 this = self.sql(expression, "this") 5213 target = self.sql(expression, "target") 5214 5215 if isinstance(expression, exp.Put): 5216 return f"PUT {this} {target}{props_sql}" 5217 else: 5218 return f"GET {target} {this}{props_sql}" 5219 5220 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5221 this = self.sql(expression, "this") 5222 expr = self.sql(expression, "expression") 5223 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5224 return f"TRANSLATE({this} USING {expr}{with_error})" 5225 5226 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5227 if self.SUPPORTS_DECODE_CASE: 5228 return self.func("DECODE", *expression.expressions) 5229 5230 expression, *expressions = expression.expressions 5231 5232 ifs = [] 5233 for search, result in zip(expressions[::2], expressions[1::2]): 5234 if isinstance(search, exp.Literal): 5235 ifs.append(exp.If(this=expression.eq(search), true=result)) 5236 elif isinstance(search, exp.Null): 5237 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5238 else: 5239 if isinstance(search, exp.Binary): 5240 search = exp.paren(search) 5241 5242 cond = exp.or_( 5243 expression.eq(search), 5244 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5245 copy=False, 5246 ) 5247 ifs.append(exp.If(this=cond, true=result)) 5248 5249 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5250 return self.sql(case) 5251 5252 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5253 this = self.sql(expression, "this") 5254 this = self.seg(this, sep="") 5255 dimensions = self.expressions( 5256 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5257 ) 5258 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5259 metrics = self.expressions( 5260 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5261 ) 5262 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5263 where = self.sql(expression, "where") 5264 where = self.seg(f"WHERE {where}") if where else "" 5265 body = self.indent(this + metrics + dimensions + where, skip_first=True) 5266 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}" 5267 5268 def getextract_sql(self, expression: exp.GetExtract) -> str: 5269 this = expression.this 5270 expr = expression.expression 5271 5272 if not this.type or not expression.type: 5273 from sqlglot.optimizer.annotate_types import annotate_types 5274 5275 this = annotate_types(this, dialect=self.dialect) 5276 5277 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5278 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5279 5280 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5281 5282 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5283 return self.sql( 5284 exp.DateAdd( 5285 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5286 expression=expression.this, 5287 unit=exp.var("DAY"), 5288 ) 5289 ) 5290 5291 def space_sql(self: Generator, expression: exp.Space) -> str: 5292 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5293 5294 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5295 return f"BUILD {self.sql(expression, 'this')}" 5296 5297 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5298 method = self.sql(expression, "method") 5299 kind = expression.args.get("kind") 5300 if not kind: 5301 return f"REFRESH {method}" 5302 5303 every = self.sql(expression, "every") 5304 unit = self.sql(expression, "unit") 5305 every = f" EVERY {every} {unit}" if every else "" 5306 starts = self.sql(expression, "starts") 5307 starts = f" STARTS {starts}" if starts else "" 5308 5309 return f"REFRESH {method} ON {kind}{every}{starts}" 5310 5311 def modelattribute_sql(self, expression: exp.ModelAttribute) -> str: 5312 self.unsupported("The model!attribute syntax is not supported") 5313 return ""
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)
738 def __init__( 739 self, 740 pretty: t.Optional[bool] = None, 741 identify: str | bool = False, 742 normalize: bool = False, 743 pad: int = 2, 744 indent: int = 2, 745 normalize_functions: t.Optional[str | bool] = None, 746 unsupported_level: ErrorLevel = ErrorLevel.WARN, 747 max_unsupported: int = 3, 748 leading_comma: bool = False, 749 max_text_width: int = 80, 750 comments: bool = True, 751 dialect: DialectType = None, 752 ): 753 import sqlglot 754 from sqlglot.dialects import Dialect 755 756 self.pretty = pretty if pretty is not None else sqlglot.pretty 757 self.identify = identify 758 self.normalize = normalize 759 self.pad = pad 760 self._indent = indent 761 self.unsupported_level = unsupported_level 762 self.max_unsupported = max_unsupported 763 self.leading_comma = leading_comma 764 self.max_text_width = max_text_width 765 self.comments = comments 766 self.dialect = Dialect.get_or_raise(dialect) 767 768 # This is both a Dialect property and a Generator argument, so we prioritize the latter 769 self.normalize_functions = ( 770 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 771 ) 772 773 self.unsupported_messages: t.List[str] = [] 774 self._escaped_quote_end: str = ( 775 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 776 ) 777 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 778 779 self._next_name = name_sequence("_t") 780 781 self._identifier_start = self.dialect.IDENTIFIER_START 782 self._identifier_end = self.dialect.IDENTIFIER_END 783 784 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.JSONBContainsAnyTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBContainsAllTopKeys'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.JSONBDeleteAtPath'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.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.JSONPathRoot'>, <class 'sqlglot.expressions.JSONPathRecursive'>, <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'>}
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.VARCHAR: 'VARCHAR'>, <Type.NCHAR: 'NCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>}
786 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 787 """ 788 Generates the SQL string corresponding to the given syntax tree. 789 790 Args: 791 expression: The syntax tree. 792 copy: Whether to copy the expression. The generator performs mutations so 793 it is safer to copy. 794 795 Returns: 796 The SQL string corresponding to `expression`. 797 """ 798 if copy: 799 expression = expression.copy() 800 801 expression = self.preprocess(expression) 802 803 self.unsupported_messages = [] 804 sql = self.sql(expression).strip() 805 806 if self.pretty: 807 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 808 809 if self.unsupported_level == ErrorLevel.IGNORE: 810 return sql 811 812 if self.unsupported_level == ErrorLevel.WARN: 813 for msg in self.unsupported_messages: 814 logger.warning(msg) 815 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 816 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 817 818 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:
820 def preprocess(self, expression: exp.Expression) -> exp.Expression: 821 """Apply generic preprocessing transformations to a given expression.""" 822 expression = self._move_ctes_to_top_level(expression) 823 824 if self.ENSURE_BOOLS: 825 from sqlglot.transforms import ensure_bools 826 827 expression = ensure_bools(expression) 828 829 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
853 def sanitize_comment(self, comment: str) -> str: 854 comment = " " + comment if comment[0].strip() else comment 855 comment = comment + " " if comment[-1].strip() else comment 856 857 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 858 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 859 comment = comment.replace("*/", "* /") 860 861 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
863 def maybe_comment( 864 self, 865 sql: str, 866 expression: t.Optional[exp.Expression] = None, 867 comments: t.Optional[t.List[str]] = None, 868 separated: bool = False, 869 ) -> str: 870 comments = ( 871 ((expression and expression.comments) if comments is None else comments) # type: ignore 872 if self.comments 873 else None 874 ) 875 876 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 877 return sql 878 879 comments_sql = " ".join( 880 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 881 ) 882 883 if not comments_sql: 884 return sql 885 886 comments_sql = self._replace_line_breaks(comments_sql) 887 888 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 889 return ( 890 f"{self.sep()}{comments_sql}{sql}" 891 if not sql or sql[0].isspace() 892 else f"{comments_sql}{self.sep()}{sql}" 893 ) 894 895 return f"{sql} {comments_sql}"
897 def wrap(self, expression: exp.Expression | str) -> str: 898 this_sql = ( 899 self.sql(expression) 900 if isinstance(expression, exp.UNWRAPPED_QUERIES) 901 else self.sql(expression, "this") 902 ) 903 if not this_sql: 904 return "()" 905 906 this_sql = self.indent(this_sql, level=1, pad=0) 907 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:
923 def indent( 924 self, 925 sql: str, 926 level: int = 0, 927 pad: t.Optional[int] = None, 928 skip_first: bool = False, 929 skip_last: bool = False, 930 ) -> str: 931 if not self.pretty or not sql: 932 return sql 933 934 pad = self.pad if pad is None else pad 935 lines = sql.split("\n") 936 937 return "\n".join( 938 ( 939 line 940 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 941 else f"{' ' * (level * self._indent + pad)}{line}" 942 ) 943 for i, line in enumerate(lines) 944 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
946 def sql( 947 self, 948 expression: t.Optional[str | exp.Expression], 949 key: t.Optional[str] = None, 950 comment: bool = True, 951 ) -> str: 952 if not expression: 953 return "" 954 955 if isinstance(expression, str): 956 return expression 957 958 if key: 959 value = expression.args.get(key) 960 if value: 961 return self.sql(value) 962 return "" 963 964 transform = self.TRANSFORMS.get(expression.__class__) 965 966 if callable(transform): 967 sql = transform(self, expression) 968 elif isinstance(expression, exp.Expression): 969 exp_handler_name = f"{expression.key}_sql" 970 971 if hasattr(self, exp_handler_name): 972 sql = getattr(self, exp_handler_name)(expression) 973 elif isinstance(expression, exp.Func): 974 sql = self.function_fallback_sql(expression) 975 elif isinstance(expression, exp.Property): 976 sql = self.property_sql(expression) 977 else: 978 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 979 else: 980 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 981 982 return self.maybe_comment(sql, expression) if self.comments and comment else sql
989 def cache_sql(self, expression: exp.Cache) -> str: 990 lazy = " LAZY" if expression.args.get("lazy") else "" 991 table = self.sql(expression, "this") 992 options = expression.args.get("options") 993 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 994 sql = self.sql(expression, "expression") 995 sql = f" AS{self.sep()}{sql}" if sql else "" 996 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 997 return self.prepend_ctes(expression, sql)
999 def characterset_sql(self, expression: exp.CharacterSet) -> str: 1000 if isinstance(expression.parent, exp.Cast): 1001 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 1002 default = "DEFAULT " if expression.args.get("default") else "" 1003 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1017 def column_sql(self, expression: exp.Column) -> str: 1018 join_mark = " (+)" if expression.args.get("join_mark") else "" 1019 1020 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1021 join_mark = "" 1022 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1023 1024 return f"{self.column_parts(expression)}{join_mark}"
1032 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1033 column = self.sql(expression, "this") 1034 kind = self.sql(expression, "kind") 1035 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1036 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1037 kind = f"{sep}{kind}" if kind else "" 1038 constraints = f" {constraints}" if constraints else "" 1039 position = self.sql(expression, "position") 1040 position = f" {position}" if position else "" 1041 1042 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1043 kind = "" 1044 1045 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1052 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1053 this = self.sql(expression, "this") 1054 if expression.args.get("not_null"): 1055 persisted = " PERSISTED NOT NULL" 1056 elif expression.args.get("persisted"): 1057 persisted = " PERSISTED" 1058 else: 1059 persisted = "" 1060 1061 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1074 def generatedasidentitycolumnconstraint_sql( 1075 self, expression: exp.GeneratedAsIdentityColumnConstraint 1076 ) -> str: 1077 this = "" 1078 if expression.this is not None: 1079 on_null = " ON NULL" if expression.args.get("on_null") else "" 1080 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1081 1082 start = expression.args.get("start") 1083 start = f"START WITH {start}" if start else "" 1084 increment = expression.args.get("increment") 1085 increment = f" INCREMENT BY {increment}" if increment else "" 1086 minvalue = expression.args.get("minvalue") 1087 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1088 maxvalue = expression.args.get("maxvalue") 1089 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1090 cycle = expression.args.get("cycle") 1091 cycle_sql = "" 1092 1093 if cycle is not None: 1094 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1095 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1096 1097 sequence_opts = "" 1098 if start or increment or cycle_sql: 1099 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1100 sequence_opts = f" ({sequence_opts.strip()})" 1101 1102 expr = self.sql(expression, "expression") 1103 expr = f"({expr})" if expr else "IDENTITY" 1104 1105 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1107 def generatedasrowcolumnconstraint_sql( 1108 self, expression: exp.GeneratedAsRowColumnConstraint 1109 ) -> str: 1110 start = "START" if expression.args.get("start") else "END" 1111 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1112 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:
1122 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1123 desc = expression.args.get("desc") 1124 if desc is not None: 1125 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1126 options = self.expressions(expression, key="options", flat=True, sep=" ") 1127 options = f" {options}" if options else "" 1128 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1130 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1131 this = self.sql(expression, "this") 1132 this = f" {this}" if this else "" 1133 index_type = expression.args.get("index_type") 1134 index_type = f" USING {index_type}" if index_type else "" 1135 on_conflict = self.sql(expression, "on_conflict") 1136 on_conflict = f" {on_conflict}" if on_conflict else "" 1137 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1138 options = self.expressions(expression, key="options", flat=True, sep=" ") 1139 options = f" {options}" if options else "" 1140 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1145 def create_sql(self, expression: exp.Create) -> str: 1146 kind = self.sql(expression, "kind") 1147 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1148 properties = expression.args.get("properties") 1149 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1150 1151 this = self.createable_sql(expression, properties_locs) 1152 1153 properties_sql = "" 1154 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1155 exp.Properties.Location.POST_WITH 1156 ): 1157 props_ast = exp.Properties( 1158 expressions=[ 1159 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1160 *properties_locs[exp.Properties.Location.POST_WITH], 1161 ] 1162 ) 1163 props_ast.parent = expression 1164 properties_sql = self.sql(props_ast) 1165 1166 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1167 properties_sql = self.sep() + properties_sql 1168 elif not self.pretty: 1169 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1170 properties_sql = f" {properties_sql}" 1171 1172 begin = " BEGIN" if expression.args.get("begin") else "" 1173 end = " END" if expression.args.get("end") else "" 1174 1175 expression_sql = self.sql(expression, "expression") 1176 if expression_sql: 1177 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1178 1179 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1180 postalias_props_sql = "" 1181 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1182 postalias_props_sql = self.properties( 1183 exp.Properties( 1184 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1185 ), 1186 wrapped=False, 1187 ) 1188 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1189 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1190 1191 postindex_props_sql = "" 1192 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1193 postindex_props_sql = self.properties( 1194 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1195 wrapped=False, 1196 prefix=" ", 1197 ) 1198 1199 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1200 indexes = f" {indexes}" if indexes else "" 1201 index_sql = indexes + postindex_props_sql 1202 1203 replace = " OR REPLACE" if expression.args.get("replace") else "" 1204 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1205 unique = " UNIQUE" if expression.args.get("unique") else "" 1206 1207 clustered = expression.args.get("clustered") 1208 if clustered is None: 1209 clustered_sql = "" 1210 elif clustered: 1211 clustered_sql = " CLUSTERED COLUMNSTORE" 1212 else: 1213 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1214 1215 postcreate_props_sql = "" 1216 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1217 postcreate_props_sql = self.properties( 1218 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1219 sep=" ", 1220 prefix=" ", 1221 wrapped=False, 1222 ) 1223 1224 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1225 1226 postexpression_props_sql = "" 1227 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1228 postexpression_props_sql = self.properties( 1229 exp.Properties( 1230 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1231 ), 1232 sep=" ", 1233 prefix=" ", 1234 wrapped=False, 1235 ) 1236 1237 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1238 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1239 no_schema_binding = ( 1240 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1241 ) 1242 1243 clone = self.sql(expression, "clone") 1244 clone = f" {clone}" if clone else "" 1245 1246 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1247 properties_expression = f"{expression_sql}{properties_sql}" 1248 else: 1249 properties_expression = f"{properties_sql}{expression_sql}" 1250 1251 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1252 return self.prepend_ctes(expression, expression_sql)
1254 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1255 start = self.sql(expression, "start") 1256 start = f"START WITH {start}" if start else "" 1257 increment = self.sql(expression, "increment") 1258 increment = f" INCREMENT BY {increment}" if increment else "" 1259 minvalue = self.sql(expression, "minvalue") 1260 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1261 maxvalue = self.sql(expression, "maxvalue") 1262 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1263 owned = self.sql(expression, "owned") 1264 owned = f" OWNED BY {owned}" if owned else "" 1265 1266 cache = expression.args.get("cache") 1267 if cache is None: 1268 cache_str = "" 1269 elif cache is True: 1270 cache_str = " CACHE" 1271 else: 1272 cache_str = f" CACHE {cache}" 1273 1274 options = self.expressions(expression, key="options", flat=True, sep=" ") 1275 options = f" {options}" if options else "" 1276 1277 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1279 def clone_sql(self, expression: exp.Clone) -> str: 1280 this = self.sql(expression, "this") 1281 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1282 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1283 return f"{shallow}{keyword} {this}"
1285 def describe_sql(self, expression: exp.Describe) -> str: 1286 style = expression.args.get("style") 1287 style = f" {style}" if style else "" 1288 partition = self.sql(expression, "partition") 1289 partition = f" {partition}" if partition else "" 1290 format = self.sql(expression, "format") 1291 format = f" {format}" if format else "" 1292 1293 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1305 def with_sql(self, expression: exp.With) -> str: 1306 sql = self.expressions(expression, flat=True) 1307 recursive = ( 1308 "RECURSIVE " 1309 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1310 else "" 1311 ) 1312 search = self.sql(expression, "search") 1313 search = f" {search}" if search else "" 1314 1315 return f"WITH {recursive}{sql}{search}"
1317 def cte_sql(self, expression: exp.CTE) -> str: 1318 alias = expression.args.get("alias") 1319 if alias: 1320 alias.add_comments(expression.pop_comments()) 1321 1322 alias_sql = self.sql(expression, "alias") 1323 1324 materialized = expression.args.get("materialized") 1325 if materialized is False: 1326 materialized = "NOT MATERIALIZED " 1327 elif materialized: 1328 materialized = "MATERIALIZED " 1329 1330 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1332 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1333 alias = self.sql(expression, "this") 1334 columns = self.expressions(expression, key="columns", flat=True) 1335 columns = f"({columns})" if columns else "" 1336 1337 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1338 columns = "" 1339 self.unsupported("Named columns are not supported in table alias.") 1340 1341 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1342 alias = self._next_name() 1343 1344 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1352 def hexstring_sql( 1353 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1354 ) -> str: 1355 this = self.sql(expression, "this") 1356 is_integer_type = expression.args.get("is_integer") 1357 1358 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1359 not self.dialect.HEX_START and not binary_function_repr 1360 ): 1361 # Integer representation will be returned if: 1362 # - The read dialect treats the hex value as integer literal but not the write 1363 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1364 return f"{int(this, 16)}" 1365 1366 if not is_integer_type: 1367 # Read dialect treats the hex value as BINARY/BLOB 1368 if binary_function_repr: 1369 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1370 return self.func(binary_function_repr, exp.Literal.string(this)) 1371 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1372 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1373 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1374 1375 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1383 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1384 this = self.sql(expression, "this") 1385 escape = expression.args.get("escape") 1386 1387 if self.dialect.UNICODE_START: 1388 escape_substitute = r"\\\1" 1389 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1390 else: 1391 escape_substitute = r"\\u\1" 1392 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1393 1394 if escape: 1395 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1396 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1397 else: 1398 escape_pattern = ESCAPED_UNICODE_RE 1399 escape_sql = "" 1400 1401 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1402 this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this) 1403 1404 return f"{left_quote}{this}{right_quote}{escape_sql}"
1406 def rawstring_sql(self, expression: exp.RawString) -> str: 1407 string = expression.this 1408 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1409 string = string.replace("\\", "\\\\") 1410 1411 string = self.escape_str(string, escape_backslash=False) 1412 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1420 def datatype_sql(self, expression: exp.DataType) -> str: 1421 nested = "" 1422 values = "" 1423 interior = self.expressions(expression, flat=True) 1424 1425 type_value = expression.this 1426 if type_value in self.UNSUPPORTED_TYPES: 1427 self.unsupported( 1428 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1429 ) 1430 1431 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1432 type_sql = self.sql(expression, "kind") 1433 else: 1434 type_sql = ( 1435 self.TYPE_MAPPING.get(type_value, type_value.value) 1436 if isinstance(type_value, exp.DataType.Type) 1437 else type_value 1438 ) 1439 1440 if interior: 1441 if expression.args.get("nested"): 1442 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1443 if expression.args.get("values") is not None: 1444 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1445 values = self.expressions(expression, key="values", flat=True) 1446 values = f"{delimiters[0]}{values}{delimiters[1]}" 1447 elif type_value == exp.DataType.Type.INTERVAL: 1448 nested = f" {interior}" 1449 else: 1450 nested = f"({interior})" 1451 1452 type_sql = f"{type_sql}{nested}{values}" 1453 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1454 exp.DataType.Type.TIMETZ, 1455 exp.DataType.Type.TIMESTAMPTZ, 1456 ): 1457 type_sql = f"{type_sql} WITH TIME ZONE" 1458 1459 return type_sql
1461 def directory_sql(self, expression: exp.Directory) -> str: 1462 local = "LOCAL " if expression.args.get("local") else "" 1463 row_format = self.sql(expression, "row_format") 1464 row_format = f" {row_format}" if row_format else "" 1465 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1467 def delete_sql(self, expression: exp.Delete) -> str: 1468 this = self.sql(expression, "this") 1469 this = f" FROM {this}" if this else "" 1470 using = self.sql(expression, "using") 1471 using = f" USING {using}" if using else "" 1472 cluster = self.sql(expression, "cluster") 1473 cluster = f" {cluster}" if cluster else "" 1474 where = self.sql(expression, "where") 1475 returning = self.sql(expression, "returning") 1476 limit = self.sql(expression, "limit") 1477 tables = self.expressions(expression, key="tables") 1478 tables = f" {tables}" if tables else "" 1479 if self.RETURNING_END: 1480 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1481 else: 1482 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1483 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1485 def drop_sql(self, expression: exp.Drop) -> str: 1486 this = self.sql(expression, "this") 1487 expressions = self.expressions(expression, flat=True) 1488 expressions = f" ({expressions})" if expressions else "" 1489 kind = expression.args["kind"] 1490 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1491 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1492 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1493 on_cluster = self.sql(expression, "cluster") 1494 on_cluster = f" {on_cluster}" if on_cluster else "" 1495 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1496 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1497 cascade = " CASCADE" if expression.args.get("cascade") else "" 1498 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1499 purge = " PURGE" if expression.args.get("purge") else "" 1500 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1502 def set_operation(self, expression: exp.SetOperation) -> str: 1503 op_type = type(expression) 1504 op_name = op_type.key.upper() 1505 1506 distinct = expression.args.get("distinct") 1507 if ( 1508 distinct is False 1509 and op_type in (exp.Except, exp.Intersect) 1510 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1511 ): 1512 self.unsupported(f"{op_name} ALL is not supported") 1513 1514 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1515 1516 if distinct is None: 1517 distinct = default_distinct 1518 if distinct is None: 1519 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1520 1521 if distinct is default_distinct: 1522 distinct_or_all = "" 1523 else: 1524 distinct_or_all = " DISTINCT" if distinct else " ALL" 1525 1526 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1527 side_kind = f"{side_kind} " if side_kind else "" 1528 1529 by_name = " BY NAME" if expression.args.get("by_name") else "" 1530 on = self.expressions(expression, key="on", flat=True) 1531 on = f" ON ({on})" if on else "" 1532 1533 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1535 def set_operations(self, expression: exp.SetOperation) -> str: 1536 if not self.SET_OP_MODIFIERS: 1537 limit = expression.args.get("limit") 1538 order = expression.args.get("order") 1539 1540 if limit or order: 1541 select = self._move_ctes_to_top_level( 1542 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1543 ) 1544 1545 if limit: 1546 select = select.limit(limit.pop(), copy=False) 1547 if order: 1548 select = select.order_by(order.pop(), copy=False) 1549 return self.sql(select) 1550 1551 sqls: t.List[str] = [] 1552 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1553 1554 while stack: 1555 node = stack.pop() 1556 1557 if isinstance(node, exp.SetOperation): 1558 stack.append(node.expression) 1559 stack.append( 1560 self.maybe_comment( 1561 self.set_operation(node), comments=node.comments, separated=True 1562 ) 1563 ) 1564 stack.append(node.this) 1565 else: 1566 sqls.append(self.sql(node)) 1567 1568 this = self.sep().join(sqls) 1569 this = self.query_modifiers(expression, this) 1570 return self.prepend_ctes(expression, this)
1572 def fetch_sql(self, expression: exp.Fetch) -> str: 1573 direction = expression.args.get("direction") 1574 direction = f" {direction}" if direction else "" 1575 count = self.sql(expression, "count") 1576 count = f" {count}" if count else "" 1577 limit_options = self.sql(expression, "limit_options") 1578 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1579 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1581 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1582 percent = " PERCENT" if expression.args.get("percent") else "" 1583 rows = " ROWS" if expression.args.get("rows") else "" 1584 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1585 if not with_ties and rows: 1586 with_ties = " ONLY" 1587 return f"{percent}{rows}{with_ties}"
1589 def filter_sql(self, expression: exp.Filter) -> str: 1590 if self.AGGREGATE_FILTER_SUPPORTED: 1591 this = self.sql(expression, "this") 1592 where = self.sql(expression, "expression").strip() 1593 return f"{this} FILTER({where})" 1594 1595 agg = expression.this 1596 agg_arg = agg.this 1597 cond = expression.expression.this 1598 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1599 return self.sql(agg)
1608 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1609 using = self.sql(expression, "using") 1610 using = f" USING {using}" if using else "" 1611 columns = self.expressions(expression, key="columns", flat=True) 1612 columns = f"({columns})" if columns else "" 1613 partition_by = self.expressions(expression, key="partition_by", flat=True) 1614 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1615 where = self.sql(expression, "where") 1616 include = self.expressions(expression, key="include", flat=True) 1617 if include: 1618 include = f" INCLUDE ({include})" 1619 with_storage = self.expressions(expression, key="with_storage", flat=True) 1620 with_storage = f" WITH ({with_storage})" if with_storage else "" 1621 tablespace = self.sql(expression, "tablespace") 1622 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1623 on = self.sql(expression, "on") 1624 on = f" ON {on}" if on else "" 1625 1626 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1628 def index_sql(self, expression: exp.Index) -> str: 1629 unique = "UNIQUE " if expression.args.get("unique") else "" 1630 primary = "PRIMARY " if expression.args.get("primary") else "" 1631 amp = "AMP " if expression.args.get("amp") else "" 1632 name = self.sql(expression, "this") 1633 name = f"{name} " if name else "" 1634 table = self.sql(expression, "table") 1635 table = f"{self.INDEX_ON} {table}" if table else "" 1636 1637 index = "INDEX " if not table else "" 1638 1639 params = self.sql(expression, "params") 1640 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1642 def identifier_sql(self, expression: exp.Identifier) -> str: 1643 text = expression.name 1644 lower = text.lower() 1645 text = lower if self.normalize and not expression.quoted else text 1646 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1647 if ( 1648 expression.quoted 1649 or self.dialect.can_identify(text, self.identify) 1650 or lower in self.RESERVED_KEYWORDS 1651 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1652 ): 1653 text = f"{self._identifier_start}{text}{self._identifier_end}" 1654 return text
1669 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1670 input_format = self.sql(expression, "input_format") 1671 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1672 output_format = self.sql(expression, "output_format") 1673 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1674 return self.sep().join((input_format, output_format))
1684 def properties_sql(self, expression: exp.Properties) -> str: 1685 root_properties = [] 1686 with_properties = [] 1687 1688 for p in expression.expressions: 1689 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1690 if p_loc == exp.Properties.Location.POST_WITH: 1691 with_properties.append(p) 1692 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1693 root_properties.append(p) 1694 1695 root_props_ast = exp.Properties(expressions=root_properties) 1696 root_props_ast.parent = expression.parent 1697 1698 with_props_ast = exp.Properties(expressions=with_properties) 1699 with_props_ast.parent = expression.parent 1700 1701 root_props = self.root_properties(root_props_ast) 1702 with_props = self.with_properties(with_props_ast) 1703 1704 if root_props and with_props and not self.pretty: 1705 with_props = " " + with_props 1706 1707 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1714 def properties( 1715 self, 1716 properties: exp.Properties, 1717 prefix: str = "", 1718 sep: str = ", ", 1719 suffix: str = "", 1720 wrapped: bool = True, 1721 ) -> str: 1722 if properties.expressions: 1723 expressions = self.expressions(properties, sep=sep, indent=False) 1724 if expressions: 1725 expressions = self.wrap(expressions) if wrapped else expressions 1726 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1727 return ""
1732 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1733 properties_locs = defaultdict(list) 1734 for p in properties.expressions: 1735 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1736 if p_loc != exp.Properties.Location.UNSUPPORTED: 1737 properties_locs[p_loc].append(p) 1738 else: 1739 self.unsupported(f"Unsupported property {p.key}") 1740 1741 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1748 def property_sql(self, expression: exp.Property) -> str: 1749 property_cls = expression.__class__ 1750 if property_cls == exp.Property: 1751 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1752 1753 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1754 if not property_name: 1755 self.unsupported(f"Unsupported property {expression.key}") 1756 1757 return f"{property_name}={self.sql(expression, 'this')}"
1759 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1760 if self.SUPPORTS_CREATE_TABLE_LIKE: 1761 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1762 options = f" {options}" if options else "" 1763 1764 like = f"LIKE {self.sql(expression, 'this')}{options}" 1765 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1766 like = f"({like})" 1767 1768 return like 1769 1770 if expression.expressions: 1771 self.unsupported("Transpilation of LIKE property options is unsupported") 1772 1773 select = exp.select("*").from_(expression.this).limit(0) 1774 return f"AS {self.sql(select)}"
1781 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1782 no = "NO " if expression.args.get("no") else "" 1783 local = expression.args.get("local") 1784 local = f"{local} " if local else "" 1785 dual = "DUAL " if expression.args.get("dual") else "" 1786 before = "BEFORE " if expression.args.get("before") else "" 1787 after = "AFTER " if expression.args.get("after") else "" 1788 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1804 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1805 if expression.args.get("no"): 1806 return "NO MERGEBLOCKRATIO" 1807 if expression.args.get("default"): 1808 return "DEFAULT MERGEBLOCKRATIO" 1809 1810 percent = " PERCENT" if expression.args.get("percent") else "" 1811 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1813 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1814 default = expression.args.get("default") 1815 minimum = expression.args.get("minimum") 1816 maximum = expression.args.get("maximum") 1817 if default or minimum or maximum: 1818 if default: 1819 prop = "DEFAULT" 1820 elif minimum: 1821 prop = "MINIMUM" 1822 else: 1823 prop = "MAXIMUM" 1824 return f"{prop} DATABLOCKSIZE" 1825 units = expression.args.get("units") 1826 units = f" {units}" if units else "" 1827 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1829 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1830 autotemp = expression.args.get("autotemp") 1831 always = expression.args.get("always") 1832 default = expression.args.get("default") 1833 manual = expression.args.get("manual") 1834 never = expression.args.get("never") 1835 1836 if autotemp is not None: 1837 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1838 elif always: 1839 prop = "ALWAYS" 1840 elif default: 1841 prop = "DEFAULT" 1842 elif manual: 1843 prop = "MANUAL" 1844 elif never: 1845 prop = "NEVER" 1846 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1848 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1849 no = expression.args.get("no") 1850 no = " NO" if no else "" 1851 concurrent = expression.args.get("concurrent") 1852 concurrent = " CONCURRENT" if concurrent else "" 1853 target = self.sql(expression, "target") 1854 target = f" {target}" if target else "" 1855 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1857 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1858 if isinstance(expression.this, list): 1859 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1860 if expression.this: 1861 modulus = self.sql(expression, "this") 1862 remainder = self.sql(expression, "expression") 1863 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1864 1865 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1866 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1867 return f"FROM ({from_expressions}) TO ({to_expressions})"
1869 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1870 this = self.sql(expression, "this") 1871 1872 for_values_or_default = expression.expression 1873 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1874 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1875 else: 1876 for_values_or_default = " DEFAULT" 1877 1878 return f"PARTITION OF {this}{for_values_or_default}"
1880 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1881 kind = expression.args.get("kind") 1882 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1883 for_or_in = expression.args.get("for_or_in") 1884 for_or_in = f" {for_or_in}" if for_or_in else "" 1885 lock_type = expression.args.get("lock_type") 1886 override = " OVERRIDE" if expression.args.get("override") else "" 1887 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1889 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1890 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1891 statistics = expression.args.get("statistics") 1892 statistics_sql = "" 1893 if statistics is not None: 1894 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1895 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1897 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1898 this = self.sql(expression, "this") 1899 this = f"HISTORY_TABLE={this}" if this else "" 1900 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1901 data_consistency = ( 1902 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1903 ) 1904 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1905 retention_period = ( 1906 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1907 ) 1908 1909 if this: 1910 on_sql = self.func("ON", this, data_consistency, retention_period) 1911 else: 1912 on_sql = "ON" if expression.args.get("on") else "OFF" 1913 1914 sql = f"SYSTEM_VERSIONING={on_sql}" 1915 1916 return f"WITH({sql})" if expression.args.get("with") else sql
1918 def insert_sql(self, expression: exp.Insert) -> str: 1919 hint = self.sql(expression, "hint") 1920 overwrite = expression.args.get("overwrite") 1921 1922 if isinstance(expression.this, exp.Directory): 1923 this = " OVERWRITE" if overwrite else " INTO" 1924 else: 1925 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1926 1927 stored = self.sql(expression, "stored") 1928 stored = f" {stored}" if stored else "" 1929 alternative = expression.args.get("alternative") 1930 alternative = f" OR {alternative}" if alternative else "" 1931 ignore = " IGNORE" if expression.args.get("ignore") else "" 1932 is_function = expression.args.get("is_function") 1933 if is_function: 1934 this = f"{this} FUNCTION" 1935 this = f"{this} {self.sql(expression, 'this')}" 1936 1937 exists = " IF EXISTS" if expression.args.get("exists") else "" 1938 where = self.sql(expression, "where") 1939 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1940 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1941 on_conflict = self.sql(expression, "conflict") 1942 on_conflict = f" {on_conflict}" if on_conflict else "" 1943 by_name = " BY NAME" if expression.args.get("by_name") else "" 1944 returning = self.sql(expression, "returning") 1945 1946 if self.RETURNING_END: 1947 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1948 else: 1949 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1950 1951 partition_by = self.sql(expression, "partition") 1952 partition_by = f" {partition_by}" if partition_by else "" 1953 settings = self.sql(expression, "settings") 1954 settings = f" {settings}" if settings else "" 1955 1956 source = self.sql(expression, "source") 1957 source = f"TABLE {source}" if source else "" 1958 1959 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1960 return self.prepend_ctes(expression, sql)
1978 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1979 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1980 1981 constraint = self.sql(expression, "constraint") 1982 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1983 1984 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1985 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1986 action = self.sql(expression, "action") 1987 1988 expressions = self.expressions(expression, flat=True) 1989 if expressions: 1990 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1991 expressions = f" {set_keyword}{expressions}" 1992 1993 where = self.sql(expression, "where") 1994 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1999 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 2000 fields = self.sql(expression, "fields") 2001 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 2002 escaped = self.sql(expression, "escaped") 2003 escaped = f" ESCAPED BY {escaped}" if escaped else "" 2004 items = self.sql(expression, "collection_items") 2005 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 2006 keys = self.sql(expression, "map_keys") 2007 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 2008 lines = self.sql(expression, "lines") 2009 lines = f" LINES TERMINATED BY {lines}" if lines else "" 2010 null = self.sql(expression, "null") 2011 null = f" NULL DEFINED AS {null}" if null else "" 2012 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2040 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2041 table = self.table_parts(expression) 2042 only = "ONLY " if expression.args.get("only") else "" 2043 partition = self.sql(expression, "partition") 2044 partition = f" {partition}" if partition else "" 2045 version = self.sql(expression, "version") 2046 version = f" {version}" if version else "" 2047 alias = self.sql(expression, "alias") 2048 alias = f"{sep}{alias}" if alias else "" 2049 2050 sample = self.sql(expression, "sample") 2051 if self.dialect.ALIAS_POST_TABLESAMPLE: 2052 sample_pre_alias = sample 2053 sample_post_alias = "" 2054 else: 2055 sample_pre_alias = "" 2056 sample_post_alias = sample 2057 2058 hints = self.expressions(expression, key="hints", sep=" ") 2059 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2060 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2061 joins = self.indent( 2062 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2063 ) 2064 laterals = self.expressions(expression, key="laterals", sep="") 2065 2066 file_format = self.sql(expression, "format") 2067 if file_format: 2068 pattern = self.sql(expression, "pattern") 2069 pattern = f", PATTERN => {pattern}" if pattern else "" 2070 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2071 2072 ordinality = expression.args.get("ordinality") or "" 2073 if ordinality: 2074 ordinality = f" WITH ORDINALITY{alias}" 2075 alias = "" 2076 2077 when = self.sql(expression, "when") 2078 if when: 2079 table = f"{table} {when}" 2080 2081 changes = self.sql(expression, "changes") 2082 changes = f" {changes}" if changes else "" 2083 2084 rows_from = self.expressions(expression, key="rows_from") 2085 if rows_from: 2086 table = f"ROWS FROM {self.wrap(rows_from)}" 2087 2088 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2090 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2091 table = self.func("TABLE", expression.this) 2092 alias = self.sql(expression, "alias") 2093 alias = f" AS {alias}" if alias else "" 2094 sample = self.sql(expression, "sample") 2095 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2096 joins = self.indent( 2097 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2098 ) 2099 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2101 def tablesample_sql( 2102 self, 2103 expression: exp.TableSample, 2104 tablesample_keyword: t.Optional[str] = None, 2105 ) -> str: 2106 method = self.sql(expression, "method") 2107 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2108 numerator = self.sql(expression, "bucket_numerator") 2109 denominator = self.sql(expression, "bucket_denominator") 2110 field = self.sql(expression, "bucket_field") 2111 field = f" ON {field}" if field else "" 2112 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2113 seed = self.sql(expression, "seed") 2114 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2115 2116 size = self.sql(expression, "size") 2117 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2118 size = f"{size} ROWS" 2119 2120 percent = self.sql(expression, "percent") 2121 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2122 percent = f"{percent} PERCENT" 2123 2124 expr = f"{bucket}{percent}{size}" 2125 if self.TABLESAMPLE_REQUIRES_PARENS: 2126 expr = f"({expr})" 2127 2128 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2130 def pivot_sql(self, expression: exp.Pivot) -> str: 2131 expressions = self.expressions(expression, flat=True) 2132 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2133 2134 group = self.sql(expression, "group") 2135 2136 if expression.this: 2137 this = self.sql(expression, "this") 2138 if not expressions: 2139 return f"UNPIVOT {this}" 2140 2141 on = f"{self.seg('ON')} {expressions}" 2142 into = self.sql(expression, "into") 2143 into = f"{self.seg('INTO')} {into}" if into else "" 2144 using = self.expressions(expression, key="using", flat=True) 2145 using = f"{self.seg('USING')} {using}" if using else "" 2146 return f"{direction} {this}{on}{into}{using}{group}" 2147 2148 alias = self.sql(expression, "alias") 2149 alias = f" AS {alias}" if alias else "" 2150 2151 fields = self.expressions( 2152 expression, 2153 "fields", 2154 sep=" ", 2155 dynamic=True, 2156 new_line=True, 2157 skip_first=True, 2158 skip_last=True, 2159 ) 2160 2161 include_nulls = expression.args.get("include_nulls") 2162 if include_nulls is not None: 2163 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2164 else: 2165 nulls = "" 2166 2167 default_on_null = self.sql(expression, "default_on_null") 2168 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2169 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2180 def update_sql(self, expression: exp.Update) -> str: 2181 this = self.sql(expression, "this") 2182 set_sql = self.expressions(expression, flat=True) 2183 from_sql = self.sql(expression, "from") 2184 where_sql = self.sql(expression, "where") 2185 returning = self.sql(expression, "returning") 2186 order = self.sql(expression, "order") 2187 limit = self.sql(expression, "limit") 2188 if self.RETURNING_END: 2189 expression_sql = f"{from_sql}{where_sql}{returning}" 2190 else: 2191 expression_sql = f"{returning}{from_sql}{where_sql}" 2192 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2193 return self.prepend_ctes(expression, sql)
2195 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2196 values_as_table = values_as_table and self.VALUES_AS_TABLE 2197 2198 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2199 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2200 args = self.expressions(expression) 2201 alias = self.sql(expression, "alias") 2202 values = f"VALUES{self.seg('')}{args}" 2203 values = ( 2204 f"({values})" 2205 if self.WRAP_DERIVED_VALUES 2206 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2207 else values 2208 ) 2209 return f"{values} AS {alias}" if alias else values 2210 2211 # Converts `VALUES...` expression into a series of select unions. 2212 alias_node = expression.args.get("alias") 2213 column_names = alias_node and alias_node.columns 2214 2215 selects: t.List[exp.Query] = [] 2216 2217 for i, tup in enumerate(expression.expressions): 2218 row = tup.expressions 2219 2220 if i == 0 and column_names: 2221 row = [ 2222 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2223 ] 2224 2225 selects.append(exp.Select(expressions=row)) 2226 2227 if self.pretty: 2228 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2229 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2230 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2231 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2232 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2233 2234 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2235 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2236 return f"({unions}){alias}"
2241 @unsupported_args("expressions") 2242 def into_sql(self, expression: exp.Into) -> str: 2243 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2244 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2245 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2262 def group_sql(self, expression: exp.Group) -> str: 2263 group_by_all = expression.args.get("all") 2264 if group_by_all is True: 2265 modifier = " ALL" 2266 elif group_by_all is False: 2267 modifier = " DISTINCT" 2268 else: 2269 modifier = "" 2270 2271 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2272 2273 grouping_sets = self.expressions(expression, key="grouping_sets") 2274 cube = self.expressions(expression, key="cube") 2275 rollup = self.expressions(expression, key="rollup") 2276 2277 groupings = csv( 2278 self.seg(grouping_sets) if grouping_sets else "", 2279 self.seg(cube) if cube else "", 2280 self.seg(rollup) if rollup else "", 2281 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2282 sep=self.GROUPINGS_SEP, 2283 ) 2284 2285 if ( 2286 expression.expressions 2287 and groupings 2288 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2289 ): 2290 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2291 2292 return f"{group_by}{groupings}"
2298 def connect_sql(self, expression: exp.Connect) -> str: 2299 start = self.sql(expression, "start") 2300 start = self.seg(f"START WITH {start}") if start else "" 2301 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2302 connect = self.sql(expression, "connect") 2303 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2304 return start + connect
2309 def join_sql(self, expression: exp.Join) -> str: 2310 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2311 side = None 2312 else: 2313 side = expression.side 2314 2315 op_sql = " ".join( 2316 op 2317 for op in ( 2318 expression.method, 2319 "GLOBAL" if expression.args.get("global") else None, 2320 side, 2321 expression.kind, 2322 expression.hint if self.JOIN_HINTS else None, 2323 ) 2324 if op 2325 ) 2326 match_cond = self.sql(expression, "match_condition") 2327 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2328 on_sql = self.sql(expression, "on") 2329 using = expression.args.get("using") 2330 2331 if not on_sql and using: 2332 on_sql = csv(*(self.sql(column) for column in using)) 2333 2334 this = expression.this 2335 this_sql = self.sql(this) 2336 2337 exprs = self.expressions(expression) 2338 if exprs: 2339 this_sql = f"{this_sql},{self.seg(exprs)}" 2340 2341 if on_sql: 2342 on_sql = self.indent(on_sql, skip_first=True) 2343 space = self.seg(" " * self.pad) if self.pretty else " " 2344 if using: 2345 on_sql = f"{space}USING ({on_sql})" 2346 else: 2347 on_sql = f"{space}ON {on_sql}" 2348 elif not op_sql: 2349 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2350 return f" {this_sql}" 2351 2352 return f", {this_sql}" 2353 2354 if op_sql != "STRAIGHT_JOIN": 2355 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2356 2357 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2358 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:
2365 def lateral_op(self, expression: exp.Lateral) -> str: 2366 cross_apply = expression.args.get("cross_apply") 2367 2368 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2369 if cross_apply is True: 2370 op = "INNER JOIN " 2371 elif cross_apply is False: 2372 op = "LEFT JOIN " 2373 else: 2374 op = "" 2375 2376 return f"{op}LATERAL"
2378 def lateral_sql(self, expression: exp.Lateral) -> str: 2379 this = self.sql(expression, "this") 2380 2381 if expression.args.get("view"): 2382 alias = expression.args["alias"] 2383 columns = self.expressions(alias, key="columns", flat=True) 2384 table = f" {alias.name}" if alias.name else "" 2385 columns = f" AS {columns}" if columns else "" 2386 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2387 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2388 2389 alias = self.sql(expression, "alias") 2390 alias = f" AS {alias}" if alias else "" 2391 2392 ordinality = expression.args.get("ordinality") or "" 2393 if ordinality: 2394 ordinality = f" WITH ORDINALITY{alias}" 2395 alias = "" 2396 2397 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2399 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2400 this = self.sql(expression, "this") 2401 2402 args = [ 2403 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2404 for e in (expression.args.get(k) for k in ("offset", "expression")) 2405 if e 2406 ] 2407 2408 args_sql = ", ".join(self.sql(e) for e in args) 2409 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2410 expressions = self.expressions(expression, flat=True) 2411 limit_options = self.sql(expression, "limit_options") 2412 expressions = f" BY {expressions}" if expressions else "" 2413 2414 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2416 def offset_sql(self, expression: exp.Offset) -> str: 2417 this = self.sql(expression, "this") 2418 value = expression.expression 2419 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2420 expressions = self.expressions(expression, flat=True) 2421 expressions = f" BY {expressions}" if expressions else "" 2422 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2424 def setitem_sql(self, expression: exp.SetItem) -> str: 2425 kind = self.sql(expression, "kind") 2426 kind = f"{kind} " if kind else "" 2427 this = self.sql(expression, "this") 2428 expressions = self.expressions(expression) 2429 collate = self.sql(expression, "collate") 2430 collate = f" COLLATE {collate}" if collate else "" 2431 global_ = "GLOBAL " if expression.args.get("global") else "" 2432 return f"{global_}{kind}{this}{expressions}{collate}"
2439 def queryband_sql(self, expression: exp.QueryBand) -> str: 2440 this = self.sql(expression, "this") 2441 update = " UPDATE" if expression.args.get("update") else "" 2442 scope = self.sql(expression, "scope") 2443 scope = f" FOR {scope}" if scope else "" 2444 2445 return f"QUERY_BAND = {this}{update}{scope}"
2450 def lock_sql(self, expression: exp.Lock) -> str: 2451 if not self.LOCKING_READS_SUPPORTED: 2452 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2453 return "" 2454 2455 update = expression.args["update"] 2456 key = expression.args.get("key") 2457 if update: 2458 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2459 else: 2460 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2461 expressions = self.expressions(expression, flat=True) 2462 expressions = f" OF {expressions}" if expressions else "" 2463 wait = expression.args.get("wait") 2464 2465 if wait is not None: 2466 if isinstance(wait, exp.Literal): 2467 wait = f" WAIT {self.sql(wait)}" 2468 else: 2469 wait = " NOWAIT" if wait else " SKIP LOCKED" 2470 2471 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2479 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2480 if self.dialect.ESCAPED_SEQUENCES: 2481 to_escaped = self.dialect.ESCAPED_SEQUENCES 2482 text = "".join( 2483 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2484 ) 2485 2486 return self._replace_line_breaks(text).replace( 2487 self.dialect.QUOTE_END, self._escaped_quote_end 2488 )
2490 def loaddata_sql(self, expression: exp.LoadData) -> str: 2491 local = " LOCAL" if expression.args.get("local") else "" 2492 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2493 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2494 this = f" INTO TABLE {self.sql(expression, 'this')}" 2495 partition = self.sql(expression, "partition") 2496 partition = f" {partition}" if partition else "" 2497 input_format = self.sql(expression, "input_format") 2498 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2499 serde = self.sql(expression, "serde") 2500 serde = f" SERDE {serde}" if serde else "" 2501 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2509 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2510 this = self.sql(expression, "this") 2511 this = f"{this} " if this else this 2512 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2513 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2515 def withfill_sql(self, expression: exp.WithFill) -> str: 2516 from_sql = self.sql(expression, "from") 2517 from_sql = f" FROM {from_sql}" if from_sql else "" 2518 to_sql = self.sql(expression, "to") 2519 to_sql = f" TO {to_sql}" if to_sql else "" 2520 step_sql = self.sql(expression, "step") 2521 step_sql = f" STEP {step_sql}" if step_sql else "" 2522 interpolated_values = [ 2523 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2524 if isinstance(e, exp.Alias) 2525 else self.sql(e, "this") 2526 for e in expression.args.get("interpolate") or [] 2527 ] 2528 interpolate = ( 2529 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2530 ) 2531 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2542 def ordered_sql(self, expression: exp.Ordered) -> str: 2543 desc = expression.args.get("desc") 2544 asc = not desc 2545 2546 nulls_first = expression.args.get("nulls_first") 2547 nulls_last = not nulls_first 2548 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2549 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2550 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2551 2552 this = self.sql(expression, "this") 2553 2554 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2555 nulls_sort_change = "" 2556 if nulls_first and ( 2557 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2558 ): 2559 nulls_sort_change = " NULLS FIRST" 2560 elif ( 2561 nulls_last 2562 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2563 and not nulls_are_last 2564 ): 2565 nulls_sort_change = " NULLS LAST" 2566 2567 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2568 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2569 window = expression.find_ancestor(exp.Window, exp.Select) 2570 if isinstance(window, exp.Window) and window.args.get("spec"): 2571 self.unsupported( 2572 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2573 ) 2574 nulls_sort_change = "" 2575 elif self.NULL_ORDERING_SUPPORTED is False and ( 2576 (asc and nulls_sort_change == " NULLS LAST") 2577 or (desc and nulls_sort_change == " NULLS FIRST") 2578 ): 2579 # BigQuery does not allow these ordering/nulls combinations when used under 2580 # an aggregation func or under a window containing one 2581 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2582 2583 if isinstance(ancestor, exp.Window): 2584 ancestor = ancestor.this 2585 if isinstance(ancestor, exp.AggFunc): 2586 self.unsupported( 2587 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2588 ) 2589 nulls_sort_change = "" 2590 elif self.NULL_ORDERING_SUPPORTED is None: 2591 if expression.this.is_int: 2592 self.unsupported( 2593 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2594 ) 2595 elif not isinstance(expression.this, exp.Rand): 2596 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2597 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2598 nulls_sort_change = "" 2599 2600 with_fill = self.sql(expression, "with_fill") 2601 with_fill = f" {with_fill}" if with_fill else "" 2602 2603 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2613 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2614 partition = self.partition_by_sql(expression) 2615 order = self.sql(expression, "order") 2616 measures = self.expressions(expression, key="measures") 2617 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2618 rows = self.sql(expression, "rows") 2619 rows = self.seg(rows) if rows else "" 2620 after = self.sql(expression, "after") 2621 after = self.seg(after) if after else "" 2622 pattern = self.sql(expression, "pattern") 2623 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2624 definition_sqls = [ 2625 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2626 for definition in expression.args.get("define", []) 2627 ] 2628 definitions = self.expressions(sqls=definition_sqls) 2629 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2630 body = "".join( 2631 ( 2632 partition, 2633 order, 2634 measures, 2635 rows, 2636 after, 2637 pattern, 2638 define, 2639 ) 2640 ) 2641 alias = self.sql(expression, "alias") 2642 alias = f" {alias}" if alias else "" 2643 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2645 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2646 limit = expression.args.get("limit") 2647 2648 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2649 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2650 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2651 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2652 2653 return csv( 2654 *sqls, 2655 *[self.sql(join) for join in expression.args.get("joins") or []], 2656 self.sql(expression, "match"), 2657 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2658 self.sql(expression, "prewhere"), 2659 self.sql(expression, "where"), 2660 self.sql(expression, "connect"), 2661 self.sql(expression, "group"), 2662 self.sql(expression, "having"), 2663 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2664 self.sql(expression, "order"), 2665 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2666 *self.after_limit_modifiers(expression), 2667 self.options_modifier(expression), 2668 self.for_modifiers(expression), 2669 sep="", 2670 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2684 def offset_limit_modifiers( 2685 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2686 ) -> t.List[str]: 2687 return [ 2688 self.sql(expression, "offset") if fetch else self.sql(limit), 2689 self.sql(limit) if fetch else self.sql(expression, "offset"), 2690 ]
2697 def select_sql(self, expression: exp.Select) -> str: 2698 into = expression.args.get("into") 2699 if not self.SUPPORTS_SELECT_INTO and into: 2700 into.pop() 2701 2702 hint = self.sql(expression, "hint") 2703 distinct = self.sql(expression, "distinct") 2704 distinct = f" {distinct}" if distinct else "" 2705 kind = self.sql(expression, "kind") 2706 2707 limit = expression.args.get("limit") 2708 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2709 top = self.limit_sql(limit, top=True) 2710 limit.pop() 2711 else: 2712 top = "" 2713 2714 expressions = self.expressions(expression) 2715 2716 if kind: 2717 if kind in self.SELECT_KINDS: 2718 kind = f" AS {kind}" 2719 else: 2720 if kind == "STRUCT": 2721 expressions = self.expressions( 2722 sqls=[ 2723 self.sql( 2724 exp.Struct( 2725 expressions=[ 2726 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2727 if isinstance(e, exp.Alias) 2728 else e 2729 for e in expression.expressions 2730 ] 2731 ) 2732 ) 2733 ] 2734 ) 2735 kind = "" 2736 2737 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2738 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2739 2740 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2741 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2742 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2743 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2744 sql = self.query_modifiers( 2745 expression, 2746 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2747 self.sql(expression, "into", comment=False), 2748 self.sql(expression, "from", comment=False), 2749 ) 2750 2751 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2752 if expression.args.get("with"): 2753 sql = self.maybe_comment(sql, expression) 2754 expression.pop_comments() 2755 2756 sql = self.prepend_ctes(expression, sql) 2757 2758 if not self.SUPPORTS_SELECT_INTO and into: 2759 if into.args.get("temporary"): 2760 table_kind = " TEMPORARY" 2761 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2762 table_kind = " UNLOGGED" 2763 else: 2764 table_kind = "" 2765 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2766 2767 return sql
2779 def star_sql(self, expression: exp.Star) -> str: 2780 except_ = self.expressions(expression, key="except", flat=True) 2781 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2782 replace = self.expressions(expression, key="replace", flat=True) 2783 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2784 rename = self.expressions(expression, key="rename", flat=True) 2785 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2786 return f"*{except_}{replace}{rename}"
2802 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2803 alias = self.sql(expression, "alias") 2804 alias = f"{sep}{alias}" if alias else "" 2805 sample = self.sql(expression, "sample") 2806 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2807 alias = f"{sample}{alias}" 2808 2809 # Set to None so it's not generated again by self.query_modifiers() 2810 expression.set("sample", None) 2811 2812 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2813 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2814 return self.prepend_ctes(expression, sql)
2820 def unnest_sql(self, expression: exp.Unnest) -> str: 2821 args = self.expressions(expression, flat=True) 2822 2823 alias = expression.args.get("alias") 2824 offset = expression.args.get("offset") 2825 2826 if self.UNNEST_WITH_ORDINALITY: 2827 if alias and isinstance(offset, exp.Expression): 2828 alias.append("columns", offset) 2829 2830 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2831 columns = alias.columns 2832 alias = self.sql(columns[0]) if columns else "" 2833 else: 2834 alias = self.sql(alias) 2835 2836 alias = f" AS {alias}" if alias else alias 2837 if self.UNNEST_WITH_ORDINALITY: 2838 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2839 else: 2840 if isinstance(offset, exp.Expression): 2841 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2842 elif offset: 2843 suffix = f"{alias} WITH OFFSET" 2844 else: 2845 suffix = alias 2846 2847 return f"UNNEST({args}){suffix}"
2856 def window_sql(self, expression: exp.Window) -> str: 2857 this = self.sql(expression, "this") 2858 partition = self.partition_by_sql(expression) 2859 order = expression.args.get("order") 2860 order = self.order_sql(order, flat=True) if order else "" 2861 spec = self.sql(expression, "spec") 2862 alias = self.sql(expression, "alias") 2863 over = self.sql(expression, "over") or "OVER" 2864 2865 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2866 2867 first = expression.args.get("first") 2868 if first is None: 2869 first = "" 2870 else: 2871 first = "FIRST" if first else "LAST" 2872 2873 if not partition and not order and not spec and alias: 2874 return f"{this} {alias}" 2875 2876 args = self.format_args( 2877 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2878 ) 2879 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2885 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2886 kind = self.sql(expression, "kind") 2887 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2888 end = ( 2889 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2890 or "CURRENT ROW" 2891 ) 2892 2893 window_spec = f"{kind} BETWEEN {start} AND {end}" 2894 2895 exclude = self.sql(expression, "exclude") 2896 if exclude: 2897 if self.SUPPORTS_WINDOW_EXCLUDE: 2898 window_spec += f" EXCLUDE {exclude}" 2899 else: 2900 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2901 2902 return window_spec
2909 def between_sql(self, expression: exp.Between) -> str: 2910 this = self.sql(expression, "this") 2911 low = self.sql(expression, "low") 2912 high = self.sql(expression, "high") 2913 symmetric = expression.args.get("symmetric") 2914 2915 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2916 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2917 2918 flag = ( 2919 " SYMMETRIC" 2920 if symmetric 2921 else " ASYMMETRIC" 2922 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2923 else "" # silently drop ASYMMETRIC – semantics identical 2924 ) 2925 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]:
2927 def bracket_offset_expressions( 2928 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2929 ) -> t.List[exp.Expression]: 2930 return apply_index_offset( 2931 expression.this, 2932 expression.expressions, 2933 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2934 dialect=self.dialect, 2935 )
2948 def any_sql(self, expression: exp.Any) -> str: 2949 this = self.sql(expression, "this") 2950 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2951 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2952 this = self.wrap(this) 2953 return f"ANY{this}" 2954 return f"ANY {this}"
2959 def case_sql(self, expression: exp.Case) -> str: 2960 this = self.sql(expression, "this") 2961 statements = [f"CASE {this}" if this else "CASE"] 2962 2963 for e in expression.args["ifs"]: 2964 statements.append(f"WHEN {self.sql(e, 'this')}") 2965 statements.append(f"THEN {self.sql(e, 'true')}") 2966 2967 default = self.sql(expression, "default") 2968 2969 if default: 2970 statements.append(f"ELSE {default}") 2971 2972 statements.append("END") 2973 2974 if self.pretty and self.too_wide(statements): 2975 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2976 2977 return " ".join(statements)
2989 def extract_sql(self, expression: exp.Extract) -> str: 2990 from sqlglot.dialects.dialect import map_date_part 2991 2992 this = ( 2993 map_date_part(expression.this, self.dialect) 2994 if self.NORMALIZE_EXTRACT_DATE_PARTS 2995 else expression.this 2996 ) 2997 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2998 expression_sql = self.sql(expression, "expression") 2999 3000 return f"EXTRACT({this_sql} FROM {expression_sql})"
3002 def trim_sql(self, expression: exp.Trim) -> str: 3003 trim_type = self.sql(expression, "position") 3004 3005 if trim_type == "LEADING": 3006 func_name = "LTRIM" 3007 elif trim_type == "TRAILING": 3008 func_name = "RTRIM" 3009 else: 3010 func_name = "TRIM" 3011 3012 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]:
3014 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3015 args = expression.expressions 3016 if isinstance(expression, exp.ConcatWs): 3017 args = args[1:] # Skip the delimiter 3018 3019 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3020 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3021 3022 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3023 3024 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3025 if not e.type: 3026 from sqlglot.optimizer.annotate_types import annotate_types 3027 3028 e = annotate_types(e, dialect=self.dialect) 3029 3030 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3031 return e 3032 3033 return exp.func("coalesce", e, exp.Literal.string("")) 3034 3035 args = [_wrap_with_coalesce(e) for e in args] 3036 3037 return args
3039 def concat_sql(self, expression: exp.Concat) -> str: 3040 expressions = self.convert_concat_args(expression) 3041 3042 # Some dialects don't allow a single-argument CONCAT call 3043 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3044 return self.sql(expressions[0]) 3045 3046 return self.func("CONCAT", *expressions)
3057 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3058 expressions = self.expressions(expression, flat=True) 3059 expressions = f" ({expressions})" if expressions else "" 3060 reference = self.sql(expression, "reference") 3061 reference = f" {reference}" if reference else "" 3062 delete = self.sql(expression, "delete") 3063 delete = f" ON DELETE {delete}" if delete else "" 3064 update = self.sql(expression, "update") 3065 update = f" ON UPDATE {update}" if update else "" 3066 options = self.expressions(expression, key="options", flat=True, sep=" ") 3067 options = f" {options}" if options else "" 3068 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3070 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3071 expressions = self.expressions(expression, flat=True) 3072 include = self.sql(expression, "include") 3073 options = self.expressions(expression, key="options", flat=True, sep=" ") 3074 options = f" {options}" if options else "" 3075 return f"PRIMARY KEY ({expressions}){include}{options}"
3080 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3081 if self.MATCH_AGAINST_TABLE_PREFIX: 3082 expressions = [] 3083 for expr in expression.expressions: 3084 if isinstance(expr, exp.Table): 3085 expressions.append(f"TABLE {self.sql(expr)}") 3086 else: 3087 expressions.append(expr) 3088 else: 3089 expressions = expression.expressions 3090 3091 modifier = expression.args.get("modifier") 3092 modifier = f" {modifier}" if modifier else "" 3093 return ( 3094 f"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3095 )
3100 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3101 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3102 3103 if expression.args.get("escape"): 3104 path = self.escape_str(path) 3105 3106 if self.QUOTE_JSON_PATH: 3107 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3108 3109 return path
3111 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3112 if isinstance(expression, exp.JSONPathPart): 3113 transform = self.TRANSFORMS.get(expression.__class__) 3114 if not callable(transform): 3115 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3116 return "" 3117 3118 return transform(self, expression) 3119 3120 if isinstance(expression, int): 3121 return str(expression) 3122 3123 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3124 escaped = expression.replace("'", "\\'") 3125 escaped = f"\\'{expression}\\'" 3126 else: 3127 escaped = expression.replace('"', '\\"') 3128 escaped = f'"{escaped}"' 3129 3130 return escaped
3135 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3136 # Output the Teradata column FORMAT override. 3137 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3138 this = self.sql(expression, "this") 3139 fmt = self.sql(expression, "format") 3140 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3142 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3143 null_handling = expression.args.get("null_handling") 3144 null_handling = f" {null_handling}" if null_handling else "" 3145 3146 unique_keys = expression.args.get("unique_keys") 3147 if unique_keys is not None: 3148 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3149 else: 3150 unique_keys = "" 3151 3152 return_type = self.sql(expression, "return_type") 3153 return_type = f" RETURNING {return_type}" if return_type else "" 3154 encoding = self.sql(expression, "encoding") 3155 encoding = f" ENCODING {encoding}" if encoding else "" 3156 3157 return self.func( 3158 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3159 *expression.expressions, 3160 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3161 )
3166 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3167 null_handling = expression.args.get("null_handling") 3168 null_handling = f" {null_handling}" if null_handling else "" 3169 return_type = self.sql(expression, "return_type") 3170 return_type = f" RETURNING {return_type}" if return_type else "" 3171 strict = " STRICT" if expression.args.get("strict") else "" 3172 return self.func( 3173 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3174 )
3176 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3177 this = self.sql(expression, "this") 3178 order = self.sql(expression, "order") 3179 null_handling = expression.args.get("null_handling") 3180 null_handling = f" {null_handling}" if null_handling else "" 3181 return_type = self.sql(expression, "return_type") 3182 return_type = f" RETURNING {return_type}" if return_type else "" 3183 strict = " STRICT" if expression.args.get("strict") else "" 3184 return self.func( 3185 "JSON_ARRAYAGG", 3186 this, 3187 suffix=f"{order}{null_handling}{return_type}{strict})", 3188 )
3190 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3191 path = self.sql(expression, "path") 3192 path = f" PATH {path}" if path else "" 3193 nested_schema = self.sql(expression, "nested_schema") 3194 3195 if nested_schema: 3196 return f"NESTED{path} {nested_schema}" 3197 3198 this = self.sql(expression, "this") 3199 kind = self.sql(expression, "kind") 3200 kind = f" {kind}" if kind else "" 3201 return f"{this}{kind}{path}"
3206 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3207 this = self.sql(expression, "this") 3208 path = self.sql(expression, "path") 3209 path = f", {path}" if path else "" 3210 error_handling = expression.args.get("error_handling") 3211 error_handling = f" {error_handling}" if error_handling else "" 3212 empty_handling = expression.args.get("empty_handling") 3213 empty_handling = f" {empty_handling}" if empty_handling else "" 3214 schema = self.sql(expression, "schema") 3215 return self.func( 3216 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3217 )
3219 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3220 this = self.sql(expression, "this") 3221 kind = self.sql(expression, "kind") 3222 path = self.sql(expression, "path") 3223 path = f" {path}" if path else "" 3224 as_json = " AS JSON" if expression.args.get("as_json") else "" 3225 return f"{this} {kind}{path}{as_json}"
3227 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3228 this = self.sql(expression, "this") 3229 path = self.sql(expression, "path") 3230 path = f", {path}" if path else "" 3231 expressions = self.expressions(expression) 3232 with_ = ( 3233 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3234 if expressions 3235 else "" 3236 ) 3237 return f"OPENJSON({this}{path}){with_}"
3239 def in_sql(self, expression: exp.In) -> str: 3240 query = expression.args.get("query") 3241 unnest = expression.args.get("unnest") 3242 field = expression.args.get("field") 3243 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3244 3245 if query: 3246 in_sql = self.sql(query) 3247 elif unnest: 3248 in_sql = self.in_unnest_op(unnest) 3249 elif field: 3250 in_sql = self.sql(field) 3251 else: 3252 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3253 3254 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3259 def interval_sql(self, expression: exp.Interval) -> str: 3260 unit = self.sql(expression, "unit") 3261 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3262 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3263 unit = f" {unit}" if unit else "" 3264 3265 if self.SINGLE_STRING_INTERVAL: 3266 this = expression.this.name if expression.this else "" 3267 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3268 3269 this = self.sql(expression, "this") 3270 if this: 3271 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3272 this = f" {this}" if unwrapped else f" ({this})" 3273 3274 return f"INTERVAL{this}{unit}"
3279 def reference_sql(self, expression: exp.Reference) -> str: 3280 this = self.sql(expression, "this") 3281 expressions = self.expressions(expression, flat=True) 3282 expressions = f"({expressions})" if expressions else "" 3283 options = self.expressions(expression, key="options", flat=True, sep=" ") 3284 options = f" {options}" if options else "" 3285 return f"REFERENCES {this}{expressions}{options}"
3287 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3288 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3289 parent = expression.parent 3290 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3291 return self.func( 3292 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3293 )
3313 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3314 alias = expression.args["alias"] 3315 3316 parent = expression.parent 3317 pivot = parent and parent.parent 3318 3319 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3320 identifier_alias = isinstance(alias, exp.Identifier) 3321 literal_alias = isinstance(alias, exp.Literal) 3322 3323 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3324 alias.replace(exp.Literal.string(alias.output_name)) 3325 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3326 alias.replace(exp.to_identifier(alias.output_name)) 3327 3328 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:
3366 def connector_sql( 3367 self, 3368 expression: exp.Connector, 3369 op: str, 3370 stack: t.Optional[t.List[str | exp.Expression]] = None, 3371 ) -> str: 3372 if stack is not None: 3373 if expression.expressions: 3374 stack.append(self.expressions(expression, sep=f" {op} ")) 3375 else: 3376 stack.append(expression.right) 3377 if expression.comments and self.comments: 3378 for comment in expression.comments: 3379 if comment: 3380 op += f" /*{self.sanitize_comment(comment)}*/" 3381 stack.extend((op, expression.left)) 3382 return op 3383 3384 stack = [expression] 3385 sqls: t.List[str] = [] 3386 ops = set() 3387 3388 while stack: 3389 node = stack.pop() 3390 if isinstance(node, exp.Connector): 3391 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3392 else: 3393 sql = self.sql(node) 3394 if sqls and sqls[-1] in ops: 3395 sqls[-1] += f" {sql}" 3396 else: 3397 sqls.append(sql) 3398 3399 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3400 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3420 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3421 format_sql = self.sql(expression, "format") 3422 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3423 to_sql = self.sql(expression, "to") 3424 to_sql = f" {to_sql}" if to_sql else "" 3425 action = self.sql(expression, "action") 3426 action = f" {action}" if action else "" 3427 default = self.sql(expression, "default") 3428 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3429 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3443 def comment_sql(self, expression: exp.Comment) -> str: 3444 this = self.sql(expression, "this") 3445 kind = expression.args["kind"] 3446 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3447 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3448 expression_sql = self.sql(expression, "expression") 3449 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3451 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3452 this = self.sql(expression, "this") 3453 delete = " DELETE" if expression.args.get("delete") else "" 3454 recompress = self.sql(expression, "recompress") 3455 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3456 to_disk = self.sql(expression, "to_disk") 3457 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3458 to_volume = self.sql(expression, "to_volume") 3459 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3460 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3462 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3463 where = self.sql(expression, "where") 3464 group = self.sql(expression, "group") 3465 aggregates = self.expressions(expression, key="aggregates") 3466 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3467 3468 if not (where or group or aggregates) and len(expression.expressions) == 1: 3469 return f"TTL {self.expressions(expression, flat=True)}" 3470 3471 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3490 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3491 this = self.sql(expression, "this") 3492 3493 dtype = self.sql(expression, "dtype") 3494 if dtype: 3495 collate = self.sql(expression, "collate") 3496 collate = f" COLLATE {collate}" if collate else "" 3497 using = self.sql(expression, "using") 3498 using = f" USING {using}" if using else "" 3499 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3500 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3501 3502 default = self.sql(expression, "default") 3503 if default: 3504 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3505 3506 comment = self.sql(expression, "comment") 3507 if comment: 3508 return f"ALTER COLUMN {this} COMMENT {comment}" 3509 3510 visible = expression.args.get("visible") 3511 if visible: 3512 return f"ALTER COLUMN {this} SET {visible}" 3513 3514 allow_null = expression.args.get("allow_null") 3515 drop = expression.args.get("drop") 3516 3517 if not drop and not allow_null: 3518 self.unsupported("Unsupported ALTER COLUMN syntax") 3519 3520 if allow_null is not None: 3521 keyword = "DROP" if drop else "SET" 3522 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3523 3524 return f"ALTER COLUMN {this} DROP DEFAULT"
3540 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3541 compound = " COMPOUND" if expression.args.get("compound") else "" 3542 this = self.sql(expression, "this") 3543 expressions = self.expressions(expression, flat=True) 3544 expressions = f"({expressions})" if expressions else "" 3545 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3547 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3548 if not self.RENAME_TABLE_WITH_DB: 3549 # Remove db from tables 3550 expression = expression.transform( 3551 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3552 ).assert_is(exp.AlterRename) 3553 this = self.sql(expression, "this") 3554 to_kw = " TO" if include_to else "" 3555 return f"RENAME{to_kw} {this}"
3570 def alter_sql(self, expression: exp.Alter) -> str: 3571 actions = expression.args["actions"] 3572 3573 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3574 actions[0], exp.ColumnDef 3575 ): 3576 actions_sql = self.expressions(expression, key="actions", flat=True) 3577 actions_sql = f"ADD {actions_sql}" 3578 else: 3579 actions_list = [] 3580 for action in actions: 3581 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3582 action_sql = self.add_column_sql(action) 3583 else: 3584 action_sql = self.sql(action) 3585 if isinstance(action, exp.Query): 3586 action_sql = f"AS {action_sql}" 3587 3588 actions_list.append(action_sql) 3589 3590 actions_sql = self.format_args(*actions_list).lstrip("\n") 3591 3592 exists = " IF EXISTS" if expression.args.get("exists") else "" 3593 on_cluster = self.sql(expression, "cluster") 3594 on_cluster = f" {on_cluster}" if on_cluster else "" 3595 only = " ONLY" if expression.args.get("only") else "" 3596 options = self.expressions(expression, key="options") 3597 options = f", {options}" if options else "" 3598 kind = self.sql(expression, "kind") 3599 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3600 check = " WITH CHECK" if expression.args.get("check") else "" 3601 this = self.sql(expression, "this") 3602 this = f" {this}" if this else "" 3603 3604 return f"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}"
3611 def add_column_sql(self, expression: exp.Expression) -> str: 3612 sql = self.sql(expression) 3613 if isinstance(expression, exp.Schema): 3614 column_text = " COLUMNS" 3615 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3616 column_text = " COLUMN" 3617 else: 3618 column_text = "" 3619 3620 return f"ADD{column_text} {sql}"
3630 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3631 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3632 location = self.sql(expression, "location") 3633 location = f" {location}" if location else "" 3634 return f"ADD {exists}{self.sql(expression.this)}{location}"
3636 def distinct_sql(self, expression: exp.Distinct) -> str: 3637 this = self.expressions(expression, flat=True) 3638 3639 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3640 case = exp.case() 3641 for arg in expression.expressions: 3642 case = case.when(arg.is_(exp.null()), exp.null()) 3643 this = self.sql(case.else_(f"({this})")) 3644 3645 this = f" {this}" if this else "" 3646 3647 on = self.sql(expression, "on") 3648 on = f" ON {on}" if on else "" 3649 return f"DISTINCT{this}{on}"
3678 def div_sql(self, expression: exp.Div) -> str: 3679 l, r = expression.left, expression.right 3680 3681 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3682 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3683 3684 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3685 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3686 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3687 3688 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3689 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3690 return self.sql( 3691 exp.cast( 3692 l / r, 3693 to=exp.DataType.Type.BIGINT, 3694 ) 3695 ) 3696 3697 return self.binary(expression, "/")
3814 def log_sql(self, expression: exp.Log) -> str: 3815 this = expression.this 3816 expr = expression.expression 3817 3818 if self.dialect.LOG_BASE_FIRST is False: 3819 this, expr = expr, this 3820 elif self.dialect.LOG_BASE_FIRST is None and expr: 3821 if this.name in ("2", "10"): 3822 return self.func(f"LOG{this.name}", expr) 3823 3824 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3825 3826 return self.func("LOG", this, expr)
3835 def binary(self, expression: exp.Binary, op: str) -> str: 3836 sqls: t.List[str] = [] 3837 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3838 binary_type = type(expression) 3839 3840 while stack: 3841 node = stack.pop() 3842 3843 if type(node) is binary_type: 3844 op_func = node.args.get("operator") 3845 if op_func: 3846 op = f"OPERATOR({self.sql(op_func)})" 3847 3848 stack.append(node.right) 3849 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3850 stack.append(node.left) 3851 else: 3852 sqls.append(self.sql(node)) 3853 3854 return "".join(sqls)
3863 def function_fallback_sql(self, expression: exp.Func) -> str: 3864 args = [] 3865 3866 for key in expression.arg_types: 3867 arg_value = expression.args.get(key) 3868 3869 if isinstance(arg_value, list): 3870 for value in arg_value: 3871 args.append(value) 3872 elif arg_value is not None: 3873 args.append(arg_value) 3874 3875 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3876 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3877 else: 3878 name = expression.sql_name() 3879 3880 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:
3882 def func( 3883 self, 3884 name: str, 3885 *args: t.Optional[exp.Expression | str], 3886 prefix: str = "(", 3887 suffix: str = ")", 3888 normalize: bool = True, 3889 ) -> str: 3890 name = self.normalize_func(name) if normalize else name 3891 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3893 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3894 arg_sqls = tuple( 3895 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3896 ) 3897 if self.pretty and self.too_wide(arg_sqls): 3898 return self.indent( 3899 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3900 ) 3901 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]:
3906 def format_time( 3907 self, 3908 expression: exp.Expression, 3909 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3910 inverse_time_trie: t.Optional[t.Dict] = None, 3911 ) -> t.Optional[str]: 3912 return format_time( 3913 self.sql(expression, "format"), 3914 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3915 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3916 )
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:
3918 def expressions( 3919 self, 3920 expression: t.Optional[exp.Expression] = None, 3921 key: t.Optional[str] = None, 3922 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3923 flat: bool = False, 3924 indent: bool = True, 3925 skip_first: bool = False, 3926 skip_last: bool = False, 3927 sep: str = ", ", 3928 prefix: str = "", 3929 dynamic: bool = False, 3930 new_line: bool = False, 3931 ) -> str: 3932 expressions = expression.args.get(key or "expressions") if expression else sqls 3933 3934 if not expressions: 3935 return "" 3936 3937 if flat: 3938 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3939 3940 num_sqls = len(expressions) 3941 result_sqls = [] 3942 3943 for i, e in enumerate(expressions): 3944 sql = self.sql(e, comment=False) 3945 if not sql: 3946 continue 3947 3948 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3949 3950 if self.pretty: 3951 if self.leading_comma: 3952 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3953 else: 3954 result_sqls.append( 3955 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3956 ) 3957 else: 3958 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3959 3960 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3961 if new_line: 3962 result_sqls.insert(0, "") 3963 result_sqls.append("") 3964 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3965 else: 3966 result_sql = "".join(result_sqls) 3967 3968 return ( 3969 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3970 if indent 3971 else result_sql 3972 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3974 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3975 flat = flat or isinstance(expression.parent, exp.Properties) 3976 expressions_sql = self.expressions(expression, flat=flat) 3977 if flat: 3978 return f"{op} {expressions_sql}" 3979 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3981 def naked_property(self, expression: exp.Property) -> str: 3982 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3983 if not property_name: 3984 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3985 return f"{property_name} {self.sql(expression, 'this')}"
3993 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3994 this = self.sql(expression, "this") 3995 expressions = self.no_identify(self.expressions, expression) 3996 expressions = ( 3997 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3998 ) 3999 return f"{this}{expressions}" if expressions.strip() != "" else this
4009 def when_sql(self, expression: exp.When) -> str: 4010 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 4011 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 4012 condition = self.sql(expression, "condition") 4013 condition = f" AND {condition}" if condition else "" 4014 4015 then_expression = expression.args.get("then") 4016 if isinstance(then_expression, exp.Insert): 4017 this = self.sql(then_expression, "this") 4018 this = f"INSERT {this}" if this else "INSERT" 4019 then = self.sql(then_expression, "expression") 4020 then = f"{this} VALUES {then}" if then else this 4021 elif isinstance(then_expression, exp.Update): 4022 if isinstance(then_expression.args.get("expressions"), exp.Star): 4023 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 4024 else: 4025 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 4026 else: 4027 then = self.sql(then_expression) 4028 return f"WHEN {matched}{source}{condition} THEN {then}"
4033 def merge_sql(self, expression: exp.Merge) -> str: 4034 table = expression.this 4035 table_alias = "" 4036 4037 hints = table.args.get("hints") 4038 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4039 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4040 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4041 4042 this = self.sql(table) 4043 using = f"USING {self.sql(expression, 'using')}" 4044 on = f"ON {self.sql(expression, 'on')}" 4045 whens = self.sql(expression, "whens") 4046 4047 returning = self.sql(expression, "returning") 4048 if returning: 4049 whens = f"{whens}{returning}" 4050 4051 sep = self.sep() 4052 4053 return self.prepend_ctes( 4054 expression, 4055 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4056 )
4062 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4063 if not self.SUPPORTS_TO_NUMBER: 4064 self.unsupported("Unsupported TO_NUMBER function") 4065 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4066 4067 fmt = expression.args.get("format") 4068 if not fmt: 4069 self.unsupported("Conversion format is required for TO_NUMBER") 4070 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4071 4072 return self.func("TO_NUMBER", expression.this, fmt)
4074 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4075 this = self.sql(expression, "this") 4076 kind = self.sql(expression, "kind") 4077 settings_sql = self.expressions(expression, key="settings", sep=" ") 4078 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4079 return f"{this}({kind}{args})"
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4100 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4101 expressions = self.expressions(expression, flat=True) 4102 expressions = f" {self.wrap(expressions)}" if expressions else "" 4103 buckets = self.sql(expression, "buckets") 4104 kind = self.sql(expression, "kind") 4105 buckets = f" BUCKETS {buckets}" if buckets else "" 4106 order = self.sql(expression, "order") 4107 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4112 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4113 expressions = self.expressions(expression, key="expressions", flat=True) 4114 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4115 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4116 buckets = self.sql(expression, "buckets") 4117 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4119 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4120 this = self.sql(expression, "this") 4121 having = self.sql(expression, "having") 4122 4123 if having: 4124 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4125 4126 return self.func("ANY_VALUE", this)
4128 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4129 transform = self.func("TRANSFORM", *expression.expressions) 4130 row_format_before = self.sql(expression, "row_format_before") 4131 row_format_before = f" {row_format_before}" if row_format_before else "" 4132 record_writer = self.sql(expression, "record_writer") 4133 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4134 using = f" USING {self.sql(expression, 'command_script')}" 4135 schema = self.sql(expression, "schema") 4136 schema = f" AS {schema}" if schema else "" 4137 row_format_after = self.sql(expression, "row_format_after") 4138 row_format_after = f" {row_format_after}" if row_format_after else "" 4139 record_reader = self.sql(expression, "record_reader") 4140 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4141 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4143 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4144 key_block_size = self.sql(expression, "key_block_size") 4145 if key_block_size: 4146 return f"KEY_BLOCK_SIZE = {key_block_size}" 4147 4148 using = self.sql(expression, "using") 4149 if using: 4150 return f"USING {using}" 4151 4152 parser = self.sql(expression, "parser") 4153 if parser: 4154 return f"WITH PARSER {parser}" 4155 4156 comment = self.sql(expression, "comment") 4157 if comment: 4158 return f"COMMENT {comment}" 4159 4160 visible = expression.args.get("visible") 4161 if visible is not None: 4162 return "VISIBLE" if visible else "INVISIBLE" 4163 4164 engine_attr = self.sql(expression, "engine_attr") 4165 if engine_attr: 4166 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4167 4168 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4169 if secondary_engine_attr: 4170 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4171 4172 self.unsupported("Unsupported index constraint option.") 4173 return ""
4179 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4180 kind = self.sql(expression, "kind") 4181 kind = f"{kind} INDEX" if kind else "INDEX" 4182 this = self.sql(expression, "this") 4183 this = f" {this}" if this else "" 4184 index_type = self.sql(expression, "index_type") 4185 index_type = f" USING {index_type}" if index_type else "" 4186 expressions = self.expressions(expression, flat=True) 4187 expressions = f" ({expressions})" if expressions else "" 4188 options = self.expressions(expression, key="options", sep=" ") 4189 options = f" {options}" if options else "" 4190 return f"{kind}{this}{index_type}{expressions}{options}"
4192 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4193 if self.NVL2_SUPPORTED: 4194 return self.function_fallback_sql(expression) 4195 4196 case = exp.Case().when( 4197 expression.this.is_(exp.null()).not_(copy=False), 4198 expression.args["true"], 4199 copy=False, 4200 ) 4201 else_cond = expression.args.get("false") 4202 if else_cond: 4203 case.else_(else_cond, copy=False) 4204 4205 return self.sql(case)
4207 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4208 this = self.sql(expression, "this") 4209 expr = self.sql(expression, "expression") 4210 iterator = self.sql(expression, "iterator") 4211 condition = self.sql(expression, "condition") 4212 condition = f" IF {condition}" if condition else "" 4213 return f"{this} FOR {expr} IN {iterator}{condition}"
4248 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4249 this_sql = self.sql(expression, "this") 4250 if isinstance(expression.this, exp.Table): 4251 this_sql = f"TABLE {this_sql}" 4252 4253 return self.func( 4254 "FEATURES_AT_TIME", 4255 this_sql, 4256 expression.args.get("time"), 4257 expression.args.get("num_rows"), 4258 expression.args.get("ignore_feature_nulls"), 4259 )
4261 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4262 this_sql = self.sql(expression, "this") 4263 if isinstance(expression.this, exp.Table): 4264 this_sql = f"TABLE {this_sql}" 4265 4266 query_table = self.sql(expression, "query_table") 4267 if isinstance(expression.args["query_table"], exp.Table): 4268 query_table = f"TABLE {query_table}" 4269 4270 return self.func( 4271 "VECTOR_SEARCH", 4272 this_sql, 4273 expression.args.get("column_to_search"), 4274 query_table, 4275 expression.args.get("query_column_to_search"), 4276 expression.args.get("top_k"), 4277 expression.args.get("distance_type"), 4278 expression.args.get("options"), 4279 )
4291 def toarray_sql(self, expression: exp.ToArray) -> str: 4292 arg = expression.this 4293 if not arg.type: 4294 from sqlglot.optimizer.annotate_types import annotate_types 4295 4296 arg = annotate_types(arg, dialect=self.dialect) 4297 4298 if arg.is_type(exp.DataType.Type.ARRAY): 4299 return self.sql(arg) 4300 4301 cond_for_null = arg.is_(exp.null()) 4302 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4304 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4305 this = expression.this 4306 time_format = self.format_time(expression) 4307 4308 if time_format: 4309 return self.sql( 4310 exp.cast( 4311 exp.StrToTime(this=this, format=expression.args["format"]), 4312 exp.DataType.Type.TIME, 4313 ) 4314 ) 4315 4316 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4317 return self.sql(this) 4318 4319 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4321 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4322 this = expression.this 4323 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4324 return self.sql(this) 4325 4326 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4328 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4329 this = expression.this 4330 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4331 return self.sql(this) 4332 4333 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4335 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4336 this = expression.this 4337 time_format = self.format_time(expression) 4338 4339 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4340 return self.sql( 4341 exp.cast( 4342 exp.StrToTime(this=this, format=expression.args["format"]), 4343 exp.DataType.Type.DATE, 4344 ) 4345 ) 4346 4347 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4348 return self.sql(this) 4349 4350 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4362 def lastday_sql(self, expression: exp.LastDay) -> str: 4363 if self.LAST_DAY_SUPPORTS_DATE_PART: 4364 return self.function_fallback_sql(expression) 4365 4366 unit = expression.text("unit") 4367 if unit and unit != "MONTH": 4368 self.unsupported("Date parts are not supported in LAST_DAY.") 4369 4370 return self.func("LAST_DAY", expression.this)
4379 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4380 if self.CAN_IMPLEMENT_ARRAY_ANY: 4381 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4382 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4383 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4384 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4385 4386 from sqlglot.dialects import Dialect 4387 4388 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4389 if self.dialect.__class__ != Dialect: 4390 self.unsupported("ARRAY_ANY is unsupported") 4391 4392 return self.function_fallback_sql(expression)
4394 def struct_sql(self, expression: exp.Struct) -> str: 4395 expression.set( 4396 "expressions", 4397 [ 4398 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4399 if isinstance(e, exp.PropertyEQ) 4400 else e 4401 for e in expression.expressions 4402 ], 4403 ) 4404 4405 return self.function_fallback_sql(expression)
4413 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4414 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4415 tables = f" {self.expressions(expression)}" 4416 4417 exists = " IF EXISTS" if expression.args.get("exists") else "" 4418 4419 on_cluster = self.sql(expression, "cluster") 4420 on_cluster = f" {on_cluster}" if on_cluster else "" 4421 4422 identity = self.sql(expression, "identity") 4423 identity = f" {identity} IDENTITY" if identity else "" 4424 4425 option = self.sql(expression, "option") 4426 option = f" {option}" if option else "" 4427 4428 partition = self.sql(expression, "partition") 4429 partition = f" {partition}" if partition else "" 4430 4431 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4435 def convert_sql(self, expression: exp.Convert) -> str: 4436 to = expression.this 4437 value = expression.expression 4438 style = expression.args.get("style") 4439 safe = expression.args.get("safe") 4440 strict = expression.args.get("strict") 4441 4442 if not to or not value: 4443 return "" 4444 4445 # Retrieve length of datatype and override to default if not specified 4446 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4447 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4448 4449 transformed: t.Optional[exp.Expression] = None 4450 cast = exp.Cast if strict else exp.TryCast 4451 4452 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4453 if isinstance(style, exp.Literal) and style.is_int: 4454 from sqlglot.dialects.tsql import TSQL 4455 4456 style_value = style.name 4457 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4458 if not converted_style: 4459 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4460 4461 fmt = exp.Literal.string(converted_style) 4462 4463 if to.this == exp.DataType.Type.DATE: 4464 transformed = exp.StrToDate(this=value, format=fmt) 4465 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4466 transformed = exp.StrToTime(this=value, format=fmt) 4467 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4468 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4469 elif to.this == exp.DataType.Type.TEXT: 4470 transformed = exp.TimeToStr(this=value, format=fmt) 4471 4472 if not transformed: 4473 transformed = cast(this=value, to=to, safe=safe) 4474 4475 return self.sql(transformed)
4543 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4544 option = self.sql(expression, "this") 4545 4546 if expression.expressions: 4547 upper = option.upper() 4548 4549 # Snowflake FILE_FORMAT options are separated by whitespace 4550 sep = " " if upper == "FILE_FORMAT" else ", " 4551 4552 # Databricks copy/format options do not set their list of values with EQ 4553 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4554 values = self.expressions(expression, flat=True, sep=sep) 4555 return f"{option}{op}({values})" 4556 4557 value = self.sql(expression, "expression") 4558 4559 if not value: 4560 return option 4561 4562 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4563 4564 return f"{option}{op}{value}"
4566 def credentials_sql(self, expression: exp.Credentials) -> str: 4567 cred_expr = expression.args.get("credentials") 4568 if isinstance(cred_expr, exp.Literal): 4569 # Redshift case: CREDENTIALS <string> 4570 credentials = self.sql(expression, "credentials") 4571 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4572 else: 4573 # Snowflake case: CREDENTIALS = (...) 4574 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4575 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4576 4577 storage = self.sql(expression, "storage") 4578 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4579 4580 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4581 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4582 4583 iam_role = self.sql(expression, "iam_role") 4584 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4585 4586 region = self.sql(expression, "region") 4587 region = f" REGION {region}" if region else "" 4588 4589 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4591 def copy_sql(self, expression: exp.Copy) -> str: 4592 this = self.sql(expression, "this") 4593 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4594 4595 credentials = self.sql(expression, "credentials") 4596 credentials = self.seg(credentials) if credentials else "" 4597 files = self.expressions(expression, key="files", flat=True) 4598 kind = self.seg("FROM" if expression.args.get("kind") else "TO") if files else "" 4599 4600 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4601 params = self.expressions( 4602 expression, 4603 key="params", 4604 sep=sep, 4605 new_line=True, 4606 skip_last=True, 4607 skip_first=True, 4608 indent=self.COPY_PARAMS_ARE_WRAPPED, 4609 ) 4610 4611 if params: 4612 if self.COPY_PARAMS_ARE_WRAPPED: 4613 params = f" WITH ({params})" 4614 elif not self.pretty and (files or credentials): 4615 params = f" {params}" 4616 4617 return f"COPY{this}{kind} {files}{credentials}{params}"
4622 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4623 on_sql = "ON" if expression.args.get("on") else "OFF" 4624 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4625 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4626 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4627 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4628 4629 if filter_col or retention_period: 4630 on_sql = self.func("ON", filter_col, retention_period) 4631 4632 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4634 def maskingpolicycolumnconstraint_sql( 4635 self, expression: exp.MaskingPolicyColumnConstraint 4636 ) -> str: 4637 this = self.sql(expression, "this") 4638 expressions = self.expressions(expression, flat=True) 4639 expressions = f" USING ({expressions})" if expressions else "" 4640 return f"MASKING POLICY {this}{expressions}"
4650 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4651 this = self.sql(expression, "this") 4652 expr = expression.expression 4653 4654 if isinstance(expr, exp.Func): 4655 # T-SQL's CLR functions are case sensitive 4656 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4657 else: 4658 expr = self.sql(expression, "expression") 4659 4660 return self.scope_resolution(expr, this)
4668 def rand_sql(self, expression: exp.Rand) -> str: 4669 lower = self.sql(expression, "lower") 4670 upper = self.sql(expression, "upper") 4671 4672 if lower and upper: 4673 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4674 return self.func("RAND", expression.this)
4676 def changes_sql(self, expression: exp.Changes) -> str: 4677 information = self.sql(expression, "information") 4678 information = f"INFORMATION => {information}" 4679 at_before = self.sql(expression, "at_before") 4680 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4681 end = self.sql(expression, "end") 4682 end = f"{self.seg('')}{end}" if end else "" 4683 4684 return f"CHANGES ({information}){at_before}{end}"
4686 def pad_sql(self, expression: exp.Pad) -> str: 4687 prefix = "L" if expression.args.get("is_left") else "R" 4688 4689 fill_pattern = self.sql(expression, "fill_pattern") or None 4690 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4691 fill_pattern = "' '" 4692 4693 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4699 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4700 generate_series = exp.GenerateSeries(**expression.args) 4701 4702 parent = expression.parent 4703 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4704 parent = parent.parent 4705 4706 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4707 return self.sql(exp.Unnest(expressions=[generate_series])) 4708 4709 if isinstance(parent, exp.Select): 4710 self.unsupported("GenerateSeries projection unnesting is not supported.") 4711 4712 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4714 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4715 exprs = expression.expressions 4716 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4717 if len(exprs) == 0: 4718 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4719 else: 4720 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4721 else: 4722 rhs = self.expressions(expression) # type: ignore 4723 4724 return self.func(name, expression.this, rhs or None)
4726 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4727 if self.SUPPORTS_CONVERT_TIMEZONE: 4728 return self.function_fallback_sql(expression) 4729 4730 source_tz = expression.args.get("source_tz") 4731 target_tz = expression.args.get("target_tz") 4732 timestamp = expression.args.get("timestamp") 4733 4734 if source_tz and timestamp: 4735 timestamp = exp.AtTimeZone( 4736 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4737 ) 4738 4739 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4740 4741 return self.sql(expr)
4743 def json_sql(self, expression: exp.JSON) -> str: 4744 this = self.sql(expression, "this") 4745 this = f" {this}" if this else "" 4746 4747 _with = expression.args.get("with") 4748 4749 if _with is None: 4750 with_sql = "" 4751 elif not _with: 4752 with_sql = " WITHOUT" 4753 else: 4754 with_sql = " WITH" 4755 4756 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4757 4758 return f"JSON{this}{with_sql}{unique_sql}"
4760 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4761 def _generate_on_options(arg: t.Any) -> str: 4762 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4763 4764 path = self.sql(expression, "path") 4765 returning = self.sql(expression, "returning") 4766 returning = f" RETURNING {returning}" if returning else "" 4767 4768 on_condition = self.sql(expression, "on_condition") 4769 on_condition = f" {on_condition}" if on_condition else "" 4770 4771 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4773 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4774 else_ = "ELSE " if expression.args.get("else_") else "" 4775 condition = self.sql(expression, "expression") 4776 condition = f"WHEN {condition} THEN " if condition else else_ 4777 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4778 return f"{condition}{insert}"
4786 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4787 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4788 empty = expression.args.get("empty") 4789 empty = ( 4790 f"DEFAULT {empty} ON EMPTY" 4791 if isinstance(empty, exp.Expression) 4792 else self.sql(expression, "empty") 4793 ) 4794 4795 error = expression.args.get("error") 4796 error = ( 4797 f"DEFAULT {error} ON ERROR" 4798 if isinstance(error, exp.Expression) 4799 else self.sql(expression, "error") 4800 ) 4801 4802 if error and empty: 4803 error = ( 4804 f"{empty} {error}" 4805 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4806 else f"{error} {empty}" 4807 ) 4808 empty = "" 4809 4810 null = self.sql(expression, "null") 4811 4812 return f"{empty}{error}{null}"
4818 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4819 this = self.sql(expression, "this") 4820 path = self.sql(expression, "path") 4821 4822 passing = self.expressions(expression, "passing") 4823 passing = f" PASSING {passing}" if passing else "" 4824 4825 on_condition = self.sql(expression, "on_condition") 4826 on_condition = f" {on_condition}" if on_condition else "" 4827 4828 path = f"{path}{passing}{on_condition}" 4829 4830 return self.func("JSON_EXISTS", this, path)
4832 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4833 array_agg = self.function_fallback_sql(expression) 4834 4835 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4836 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4837 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4838 parent = expression.parent 4839 if isinstance(parent, exp.Filter): 4840 parent_cond = parent.expression.this 4841 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4842 else: 4843 this = expression.this 4844 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4845 if this.find(exp.Column): 4846 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4847 this_sql = ( 4848 self.expressions(this) 4849 if isinstance(this, exp.Distinct) 4850 else self.sql(expression, "this") 4851 ) 4852 4853 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4854 4855 return array_agg
4928 def overlay_sql(self, expression: exp.Overlay): 4929 this = self.sql(expression, "this") 4930 expr = self.sql(expression, "expression") 4931 from_sql = self.sql(expression, "from") 4932 for_sql = self.sql(expression, "for") 4933 for_sql = f" FOR {for_sql}" if for_sql else "" 4934 4935 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4941 def string_sql(self, expression: exp.String) -> str: 4942 this = expression.this 4943 zone = expression.args.get("zone") 4944 4945 if zone: 4946 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4947 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4948 # set for source_tz to transpile the time conversion before the STRING cast 4949 this = exp.ConvertTimezone( 4950 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4951 ) 4952 4953 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4963 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4964 filler = self.sql(expression, "this") 4965 filler = f" {filler}" if filler else "" 4966 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4967 return f"TRUNCATE{filler} {with_count}"
4969 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4970 if self.SUPPORTS_UNIX_SECONDS: 4971 return self.function_fallback_sql(expression) 4972 4973 start_ts = exp.cast( 4974 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4975 ) 4976 4977 return self.sql( 4978 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4979 )
4981 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4982 dim = expression.expression 4983 4984 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4985 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4986 if not (dim.is_int and dim.name == "1"): 4987 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4988 dim = None 4989 4990 # If dimension is required but not specified, default initialize it 4991 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4992 dim = exp.Literal.number(1) 4993 4994 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4996 def attach_sql(self, expression: exp.Attach) -> str: 4997 this = self.sql(expression, "this") 4998 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4999 expressions = self.expressions(expression) 5000 expressions = f" ({expressions})" if expressions else "" 5001 5002 return f"ATTACH{exists_sql} {this}{expressions}"
5004 def detach_sql(self, expression: exp.Detach) -> str: 5005 this = self.sql(expression, "this") 5006 # the DATABASE keyword is required if IF EXISTS is set 5007 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 5008 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 5009 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 5010 5011 return f"DETACH{exists_sql} {this}"
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
5024 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 5025 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 5026 encode = f"{encode} {self.sql(expression, 'this')}" 5027 5028 properties = expression.args.get("properties") 5029 if properties: 5030 encode = f"{encode} {self.properties(properties)}" 5031 5032 return encode
5034 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 5035 this = self.sql(expression, "this") 5036 include = f"INCLUDE {this}" 5037 5038 column_def = self.sql(expression, "column_def") 5039 if column_def: 5040 include = f"{include} {column_def}" 5041 5042 alias = self.sql(expression, "alias") 5043 if alias: 5044 include = f"{include} AS {alias}" 5045 5046 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
5058 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5059 partitions = self.expressions(expression, "partition_expressions") 5060 create = self.expressions(expression, "create_expressions") 5061 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
5063 def partitionbyrangepropertydynamic_sql( 5064 self, expression: exp.PartitionByRangePropertyDynamic 5065 ) -> str: 5066 start = self.sql(expression, "start") 5067 end = self.sql(expression, "end") 5068 5069 every = expression.args["every"] 5070 if isinstance(every, exp.Interval) and every.this.is_string: 5071 every.this.replace(exp.Literal.number(every.name)) 5072 5073 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5086 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5087 kind = self.sql(expression, "kind") 5088 option = self.sql(expression, "option") 5089 option = f" {option}" if option else "" 5090 this = self.sql(expression, "this") 5091 this = f" {this}" if this else "" 5092 columns = self.expressions(expression) 5093 columns = f" {columns}" if columns else "" 5094 return f"{kind}{option} STATISTICS{this}{columns}"
5096 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5097 this = self.sql(expression, "this") 5098 columns = self.expressions(expression) 5099 inner_expression = self.sql(expression, "expression") 5100 inner_expression = f" {inner_expression}" if inner_expression else "" 5101 update_options = self.sql(expression, "update_options") 5102 update_options = f" {update_options} UPDATE" if update_options else "" 5103 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
5114 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5115 kind = self.sql(expression, "kind") 5116 this = self.sql(expression, "this") 5117 this = f" {this}" if this else "" 5118 inner_expression = self.sql(expression, "expression") 5119 return f"VALIDATE {kind}{this}{inner_expression}"
5121 def analyze_sql(self, expression: exp.Analyze) -> str: 5122 options = self.expressions(expression, key="options", sep=" ") 5123 options = f" {options}" if options else "" 5124 kind = self.sql(expression, "kind") 5125 kind = f" {kind}" if kind else "" 5126 this = self.sql(expression, "this") 5127 this = f" {this}" if this else "" 5128 mode = self.sql(expression, "mode") 5129 mode = f" {mode}" if mode else "" 5130 properties = self.sql(expression, "properties") 5131 properties = f" {properties}" if properties else "" 5132 partition = self.sql(expression, "partition") 5133 partition = f" {partition}" if partition else "" 5134 inner_expression = self.sql(expression, "expression") 5135 inner_expression = f" {inner_expression}" if inner_expression else "" 5136 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5138 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5139 this = self.sql(expression, "this") 5140 namespaces = self.expressions(expression, key="namespaces") 5141 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5142 passing = self.expressions(expression, key="passing") 5143 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5144 columns = self.expressions(expression, key="columns") 5145 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5146 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5147 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5153 def export_sql(self, expression: exp.Export) -> str: 5154 this = self.sql(expression, "this") 5155 connection = self.sql(expression, "connection") 5156 connection = f"WITH CONNECTION {connection} " if connection else "" 5157 options = self.sql(expression, "options") 5158 return f"EXPORT DATA {connection}{options} AS {this}"
5163 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5164 variable = self.sql(expression, "this") 5165 default = self.sql(expression, "default") 5166 default = f" = {default}" if default else "" 5167 5168 kind = self.sql(expression, "kind") 5169 if isinstance(expression.args.get("kind"), exp.Schema): 5170 kind = f"TABLE {kind}" 5171 5172 return f"{variable} AS {kind}{default}"
5174 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5175 kind = self.sql(expression, "kind") 5176 this = self.sql(expression, "this") 5177 set = self.sql(expression, "expression") 5178 using = self.sql(expression, "using") 5179 using = f" USING {using}" if using else "" 5180 5181 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5182 5183 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5206 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5207 # Snowflake GET/PUT statements: 5208 # PUT <file> <internalStage> <properties> 5209 # GET <internalStage> <file> <properties> 5210 props = expression.args.get("properties") 5211 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5212 this = self.sql(expression, "this") 5213 target = self.sql(expression, "target") 5214 5215 if isinstance(expression, exp.Put): 5216 return f"PUT {this} {target}{props_sql}" 5217 else: 5218 return f"GET {target} {this}{props_sql}"
5226 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5227 if self.SUPPORTS_DECODE_CASE: 5228 return self.func("DECODE", *expression.expressions) 5229 5230 expression, *expressions = expression.expressions 5231 5232 ifs = [] 5233 for search, result in zip(expressions[::2], expressions[1::2]): 5234 if isinstance(search, exp.Literal): 5235 ifs.append(exp.If(this=expression.eq(search), true=result)) 5236 elif isinstance(search, exp.Null): 5237 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5238 else: 5239 if isinstance(search, exp.Binary): 5240 search = exp.paren(search) 5241 5242 cond = exp.or_( 5243 expression.eq(search), 5244 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5245 copy=False, 5246 ) 5247 ifs.append(exp.If(this=cond, true=result)) 5248 5249 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5250 return self.sql(case)
5252 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5253 this = self.sql(expression, "this") 5254 this = self.seg(this, sep="") 5255 dimensions = self.expressions( 5256 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5257 ) 5258 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5259 metrics = self.expressions( 5260 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5261 ) 5262 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5263 where = self.sql(expression, "where") 5264 where = self.seg(f"WHERE {where}") if where else "" 5265 body = self.indent(this + metrics + dimensions + where, skip_first=True) 5266 return f"SEMANTIC_VIEW({body}{self.seg(')', sep='')}"
5268 def getextract_sql(self, expression: exp.GetExtract) -> str: 5269 this = expression.this 5270 expr = expression.expression 5271 5272 if not this.type or not expression.type: 5273 from sqlglot.optimizer.annotate_types import annotate_types 5274 5275 this = annotate_types(this, dialect=self.dialect) 5276 5277 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5278 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5279 5280 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql(self, expression: sqlglot.expressions.RefreshTriggerProperty) -> str:
5297 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5298 method = self.sql(expression, "method") 5299 kind = expression.args.get("kind") 5300 if not kind: 5301 return f"REFRESH {method}" 5302 5303 every = self.sql(expression, "every") 5304 unit = self.sql(expression, "unit") 5305 every = f" EVERY {every} {unit}" if every else "" 5306 starts = self.sql(expression, "starts") 5307 starts = f" STARTS {starts}" if starts else "" 5308 5309 return f"REFRESH {method} ON {kind}{every}{starts}"