sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from functools import reduce, wraps 8 9from sqlglot import exp 10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 11from sqlglot.helper import apply_index_offset, csv, name_sequence, seq_get 12from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 13from sqlglot.time import format_time 14from sqlglot.tokens import TokenType 15 16if t.TYPE_CHECKING: 17 from sqlglot._typing import E 18 from sqlglot.dialects.dialect import DialectType 19 20 G = t.TypeVar("G", bound="Generator") 21 GeneratorMethod = t.Callable[[G, E], str] 22 23logger = logging.getLogger("sqlglot") 24 25ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 26UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 27 28 29def unsupported_args( 30 *args: t.Union[str, t.Tuple[str, str]], 31) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 32 """ 33 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 34 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 35 """ 36 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 37 for arg in args: 38 if isinstance(arg, str): 39 diagnostic_by_arg[arg] = None 40 else: 41 diagnostic_by_arg[arg[0]] = arg[1] 42 43 def decorator(func: GeneratorMethod) -> GeneratorMethod: 44 @wraps(func) 45 def _func(generator: G, expression: E) -> str: 46 expression_name = expression.__class__.__name__ 47 dialect_name = generator.dialect.__class__.__name__ 48 49 for arg_name, diagnostic in diagnostic_by_arg.items(): 50 if expression.args.get(arg_name): 51 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 52 arg_name, expression_name, dialect_name 53 ) 54 generator.unsupported(diagnostic) 55 56 return func(generator, expression) 57 58 return _func 59 60 return decorator 61 62 63class _Generator(type): 64 def __new__(cls, clsname, bases, attrs): 65 klass = super().__new__(cls, clsname, bases, attrs) 66 67 # Remove transforms that correspond to unsupported JSONPathPart expressions 68 for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: 69 klass.TRANSFORMS.pop(part, None) 70 71 return klass 72 73 74class Generator(metaclass=_Generator): 75 """ 76 Generator converts a given syntax tree to the corresponding SQL string. 77 78 Args: 79 pretty: Whether to format the produced SQL string. 80 Default: False. 81 identify: Determines when an identifier should be quoted. Possible values are: 82 False (default): Never quote, except in cases where it's mandatory by the dialect. 83 True or 'always': Always quote. 84 'safe': Only quote identifiers that are case insensitive. 85 normalize: Whether to normalize identifiers to lowercase. 86 Default: False. 87 pad: The pad size in a formatted string. For example, this affects the indentation of 88 a projection in a query, relative to its nesting level. 89 Default: 2. 90 indent: The indentation size in a formatted string. For example, this affects the 91 indentation of subqueries and filters under a `WHERE` clause. 92 Default: 2. 93 normalize_functions: How to normalize function names. Possible values are: 94 "upper" or True (default): Convert names to uppercase. 95 "lower": Convert names to lowercase. 96 False: Disables function name normalization. 97 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 98 Default ErrorLevel.WARN. 99 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 100 This is only relevant if unsupported_level is ErrorLevel.RAISE. 101 Default: 3 102 leading_comma: Whether the comma is leading or trailing in select expressions. 103 This is only relevant when generating in pretty mode. 104 Default: False 105 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 106 The default is on the smaller end because the length only represents a segment and not the true 107 line length. 108 Default: 80 109 comments: Whether to preserve comments in the output SQL code. 110 Default: True 111 """ 112 113 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 114 **JSON_PATH_PART_TRANSFORMS, 115 exp.AllowedValuesProperty: lambda self, 116 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 117 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 118 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 119 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 120 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 121 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 122 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 123 exp.CaseSpecificColumnConstraint: lambda _, 124 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 125 exp.Ceil: lambda self, e: self.ceil_floor(e), 126 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 127 exp.CharacterSetProperty: lambda self, 128 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 129 exp.ClusteredColumnConstraint: lambda self, 130 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 131 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 132 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 133 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 134 exp.ConvertToCharset: lambda self, e: self.func( 135 "CONVERT", e.this, e.args["dest"], e.args.get("source") 136 ), 137 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 138 exp.CredentialsProperty: lambda self, 139 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 140 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 141 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 142 exp.DynamicProperty: lambda *_: "DYNAMIC", 143 exp.EmptyProperty: lambda *_: "EMPTY", 144 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 145 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 146 exp.EphemeralColumnConstraint: lambda self, 147 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 148 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 149 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 150 exp.Except: lambda self, e: self.set_operations(e), 151 exp.ExternalProperty: lambda *_: "EXTERNAL", 152 exp.Floor: lambda self, e: self.ceil_floor(e), 153 exp.Get: lambda self, e: self.get_put_sql(e), 154 exp.GlobalProperty: lambda *_: "GLOBAL", 155 exp.HeapProperty: lambda *_: "HEAP", 156 exp.IcebergProperty: lambda *_: "ICEBERG", 157 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 158 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 159 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 160 exp.Intersect: lambda self, e: self.set_operations(e), 161 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 162 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 163 exp.LanguageProperty: lambda self, e: self.naked_property(e), 164 exp.LocationProperty: lambda self, e: self.naked_property(e), 165 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 166 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 167 exp.NonClusteredColumnConstraint: lambda self, 168 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 169 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 170 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 171 exp.OnCommitProperty: lambda _, 172 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 173 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 174 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 175 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 176 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 177 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 178 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 179 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 180 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 181 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 182 exp.ProjectionPolicyColumnConstraint: lambda self, 183 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 184 exp.Put: lambda self, e: self.get_put_sql(e), 185 exp.RemoteWithConnectionModelProperty: lambda self, 186 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 187 exp.ReturnsProperty: lambda self, e: ( 188 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 189 ), 190 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 191 exp.SecureProperty: lambda *_: "SECURE", 192 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 193 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 194 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 195 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 196 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 197 exp.SqlReadWriteProperty: lambda _, e: e.name, 198 exp.SqlSecurityProperty: lambda _, 199 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 200 exp.StabilityProperty: lambda _, e: e.name, 201 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 202 exp.StreamingTableProperty: lambda *_: "STREAMING", 203 exp.StrictProperty: lambda *_: "STRICT", 204 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 205 exp.TableColumn: lambda self, e: self.sql(e.this), 206 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 207 exp.TemporaryProperty: lambda *_: "TEMPORARY", 208 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 209 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 210 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 211 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 212 exp.TransientProperty: lambda *_: "TRANSIENT", 213 exp.Union: lambda self, e: self.set_operations(e), 214 exp.UnloggedProperty: lambda *_: "UNLOGGED", 215 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 216 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 217 exp.Uuid: lambda *_: "UUID()", 218 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 219 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 220 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 221 exp.VolatileProperty: lambda *_: "VOLATILE", 222 exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})", 223 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 224 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 225 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 226 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 227 exp.ForceProperty: lambda *_: "FORCE", 228 } 229 230 # Whether null ordering is supported in order by 231 # True: Full Support, None: No support, False: No support for certain cases 232 # such as window specifications, aggregate functions etc 233 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 234 235 # Whether ignore nulls is inside the agg or outside. 236 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 237 IGNORE_NULLS_IN_FUNC = False 238 239 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 240 LOCKING_READS_SUPPORTED = False 241 242 # Whether the EXCEPT and INTERSECT operations can return duplicates 243 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 244 245 # Wrap derived values in parens, usually standard but spark doesn't support it 246 WRAP_DERIVED_VALUES = True 247 248 # Whether create function uses an AS before the RETURN 249 CREATE_FUNCTION_RETURN_AS = True 250 251 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 252 MATCHED_BY_SOURCE = True 253 254 # Whether the INTERVAL expression works only with values like '1 day' 255 SINGLE_STRING_INTERVAL = False 256 257 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 258 INTERVAL_ALLOWS_PLURAL_FORM = True 259 260 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 261 LIMIT_FETCH = "ALL" 262 263 # Whether limit and fetch allows expresions or just limits 264 LIMIT_ONLY_LITERALS = False 265 266 # Whether a table is allowed to be renamed with a db 267 RENAME_TABLE_WITH_DB = True 268 269 # The separator for grouping sets and rollups 270 GROUPINGS_SEP = "," 271 272 # The string used for creating an index on a table 273 INDEX_ON = "ON" 274 275 # Whether join hints should be generated 276 JOIN_HINTS = True 277 278 # Whether table hints should be generated 279 TABLE_HINTS = True 280 281 # Whether query hints should be generated 282 QUERY_HINTS = True 283 284 # What kind of separator to use for query hints 285 QUERY_HINT_SEP = ", " 286 287 # Whether comparing against booleans (e.g. x IS TRUE) is supported 288 IS_BOOL_ALLOWED = True 289 290 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 291 DUPLICATE_KEY_UPDATE_WITH_SET = True 292 293 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 294 LIMIT_IS_TOP = False 295 296 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 297 RETURNING_END = True 298 299 # Whether to generate an unquoted value for EXTRACT's date part argument 300 EXTRACT_ALLOWS_QUOTES = True 301 302 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 303 TZ_TO_WITH_TIME_ZONE = False 304 305 # Whether the NVL2 function is supported 306 NVL2_SUPPORTED = True 307 308 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 309 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 310 311 # Whether VALUES statements can be used as derived tables. 312 # MySQL 5 and Redshift do not allow this, so when False, it will convert 313 # SELECT * VALUES into SELECT UNION 314 VALUES_AS_TABLE = True 315 316 # Whether the word COLUMN is included when adding a column with ALTER TABLE 317 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 318 319 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 320 UNNEST_WITH_ORDINALITY = True 321 322 # Whether FILTER (WHERE cond) can be used for conditional aggregation 323 AGGREGATE_FILTER_SUPPORTED = True 324 325 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 326 SEMI_ANTI_JOIN_WITH_SIDE = True 327 328 # Whether to include the type of a computed column in the CREATE DDL 329 COMPUTED_COLUMN_WITH_TYPE = True 330 331 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 332 SUPPORTS_TABLE_COPY = True 333 334 # Whether parentheses are required around the table sample's expression 335 TABLESAMPLE_REQUIRES_PARENS = True 336 337 # Whether a table sample clause's size needs to be followed by the ROWS keyword 338 TABLESAMPLE_SIZE_IS_ROWS = True 339 340 # The keyword(s) to use when generating a sample clause 341 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 342 343 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 344 TABLESAMPLE_WITH_METHOD = True 345 346 # The keyword to use when specifying the seed of a sample clause 347 TABLESAMPLE_SEED_KEYWORD = "SEED" 348 349 # Whether COLLATE is a function instead of a binary operator 350 COLLATE_IS_FUNC = False 351 352 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 353 DATA_TYPE_SPECIFIERS_ALLOWED = False 354 355 # Whether conditions require booleans WHERE x = 0 vs WHERE x 356 ENSURE_BOOLS = False 357 358 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 359 CTE_RECURSIVE_KEYWORD_REQUIRED = True 360 361 # Whether CONCAT requires >1 arguments 362 SUPPORTS_SINGLE_ARG_CONCAT = True 363 364 # Whether LAST_DAY function supports a date part argument 365 LAST_DAY_SUPPORTS_DATE_PART = True 366 367 # Whether named columns are allowed in table aliases 368 SUPPORTS_TABLE_ALIAS_COLUMNS = True 369 370 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 371 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 372 373 # What delimiter to use for separating JSON key/value pairs 374 JSON_KEY_VALUE_PAIR_SEP = ":" 375 376 # INSERT OVERWRITE TABLE x override 377 INSERT_OVERWRITE = " OVERWRITE TABLE" 378 379 # Whether the SELECT .. INTO syntax is used instead of CTAS 380 SUPPORTS_SELECT_INTO = False 381 382 # Whether UNLOGGED tables can be created 383 SUPPORTS_UNLOGGED_TABLES = False 384 385 # Whether the CREATE TABLE LIKE statement is supported 386 SUPPORTS_CREATE_TABLE_LIKE = True 387 388 # Whether the LikeProperty needs to be specified inside of the schema clause 389 LIKE_PROPERTY_INSIDE_SCHEMA = False 390 391 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 392 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 393 MULTI_ARG_DISTINCT = True 394 395 # Whether the JSON extraction operators expect a value of type JSON 396 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 397 398 # Whether bracketed keys like ["foo"] are supported in JSON paths 399 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 400 401 # Whether to escape keys using single quotes in JSON paths 402 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 403 404 # The JSONPathPart expressions supported by this dialect 405 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 406 407 # Whether any(f(x) for x in array) can be implemented by this dialect 408 CAN_IMPLEMENT_ARRAY_ANY = False 409 410 # Whether the function TO_NUMBER is supported 411 SUPPORTS_TO_NUMBER = True 412 413 # Whether EXCLUDE in window specification is supported 414 SUPPORTS_WINDOW_EXCLUDE = False 415 416 # Whether or not set op modifiers apply to the outer set op or select. 417 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 418 # True means limit 1 happens after the set op, False means it it happens on y. 419 SET_OP_MODIFIERS = True 420 421 # Whether parameters from COPY statement are wrapped in parentheses 422 COPY_PARAMS_ARE_WRAPPED = True 423 424 # Whether values of params are set with "=" token or empty space 425 COPY_PARAMS_EQ_REQUIRED = False 426 427 # Whether COPY statement has INTO keyword 428 COPY_HAS_INTO_KEYWORD = True 429 430 # Whether the conditional TRY(expression) function is supported 431 TRY_SUPPORTED = True 432 433 # Whether the UESCAPE syntax in unicode strings is supported 434 SUPPORTS_UESCAPE = True 435 436 # The keyword to use when generating a star projection with excluded columns 437 STAR_EXCEPT = "EXCEPT" 438 439 # The HEX function name 440 HEX_FUNC = "HEX" 441 442 # The keywords to use when prefixing & separating WITH based properties 443 WITH_PROPERTIES_PREFIX = "WITH" 444 445 # Whether to quote the generated expression of exp.JsonPath 446 QUOTE_JSON_PATH = True 447 448 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 449 PAD_FILL_PATTERN_IS_REQUIRED = False 450 451 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 452 SUPPORTS_EXPLODING_PROJECTIONS = True 453 454 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 455 ARRAY_CONCAT_IS_VAR_LEN = True 456 457 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 458 SUPPORTS_CONVERT_TIMEZONE = False 459 460 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 461 SUPPORTS_MEDIAN = True 462 463 # Whether UNIX_SECONDS(timestamp) is supported 464 SUPPORTS_UNIX_SECONDS = False 465 466 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 467 ALTER_SET_WRAPPED = False 468 469 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 470 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 471 # TODO: The normalization should be done by default once we've tested it across all dialects. 472 NORMALIZE_EXTRACT_DATE_PARTS = False 473 474 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 475 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 476 477 # The function name of the exp.ArraySize expression 478 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 479 480 # The syntax to use when altering the type of a column 481 ALTER_SET_TYPE = "SET DATA TYPE" 482 483 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 484 # None -> Doesn't support it at all 485 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 486 # True (Postgres) -> Explicitly requires it 487 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 488 489 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 490 SUPPORTS_DECODE_CASE = True 491 492 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 493 SUPPORTS_BETWEEN_FLAGS = False 494 495 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 496 SUPPORTS_LIKE_QUANTIFIERS = True 497 498 TYPE_MAPPING = { 499 exp.DataType.Type.DATETIME2: "TIMESTAMP", 500 exp.DataType.Type.NCHAR: "CHAR", 501 exp.DataType.Type.NVARCHAR: "VARCHAR", 502 exp.DataType.Type.MEDIUMTEXT: "TEXT", 503 exp.DataType.Type.LONGTEXT: "TEXT", 504 exp.DataType.Type.TINYTEXT: "TEXT", 505 exp.DataType.Type.BLOB: "VARBINARY", 506 exp.DataType.Type.MEDIUMBLOB: "BLOB", 507 exp.DataType.Type.LONGBLOB: "BLOB", 508 exp.DataType.Type.TINYBLOB: "BLOB", 509 exp.DataType.Type.INET: "INET", 510 exp.DataType.Type.ROWVERSION: "VARBINARY", 511 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 512 } 513 514 TIME_PART_SINGULARS = { 515 "MICROSECONDS": "MICROSECOND", 516 "SECONDS": "SECOND", 517 "MINUTES": "MINUTE", 518 "HOURS": "HOUR", 519 "DAYS": "DAY", 520 "WEEKS": "WEEK", 521 "MONTHS": "MONTH", 522 "QUARTERS": "QUARTER", 523 "YEARS": "YEAR", 524 } 525 526 AFTER_HAVING_MODIFIER_TRANSFORMS = { 527 "cluster": lambda self, e: self.sql(e, "cluster"), 528 "distribute": lambda self, e: self.sql(e, "distribute"), 529 "sort": lambda self, e: self.sql(e, "sort"), 530 "windows": lambda self, e: ( 531 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 532 if e.args.get("windows") 533 else "" 534 ), 535 "qualify": lambda self, e: self.sql(e, "qualify"), 536 } 537 538 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 539 540 STRUCT_DELIMITER = ("<", ">") 541 542 PARAMETER_TOKEN = "@" 543 NAMED_PLACEHOLDER_TOKEN = ":" 544 545 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 546 547 PROPERTIES_LOCATION = { 548 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 549 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 550 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 551 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 552 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 554 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 556 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 559 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 560 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 561 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 562 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 563 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 565 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 566 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 568 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 572 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 573 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 576 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 577 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 578 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 579 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 580 exp.HeapProperty: exp.Properties.Location.POST_WITH, 581 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 583 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 586 exp.JournalProperty: exp.Properties.Location.POST_NAME, 587 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 592 exp.LogProperty: exp.Properties.Location.POST_NAME, 593 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 594 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 595 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 596 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 598 exp.Order: exp.Properties.Location.POST_SCHEMA, 599 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 600 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 601 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 603 exp.Property: exp.Properties.Location.POST_WITH, 604 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 611 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 612 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 614 exp.Set: exp.Properties.Location.POST_SCHEMA, 615 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.SetProperty: exp.Properties.Location.POST_CREATE, 617 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 619 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 620 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 623 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 626 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.Tags: exp.Properties.Location.POST_WITH, 628 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 629 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 630 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 631 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 633 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 634 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 636 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 637 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 638 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 639 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 640 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 641 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 642 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 643 } 644 645 # Keywords that can't be used as unquoted identifier names 646 RESERVED_KEYWORDS: t.Set[str] = set() 647 648 # Expressions whose comments are separated from them for better formatting 649 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 650 exp.Command, 651 exp.Create, 652 exp.Describe, 653 exp.Delete, 654 exp.Drop, 655 exp.From, 656 exp.Insert, 657 exp.Join, 658 exp.MultitableInserts, 659 exp.Order, 660 exp.Group, 661 exp.Having, 662 exp.Select, 663 exp.SetOperation, 664 exp.Update, 665 exp.Where, 666 exp.With, 667 ) 668 669 # Expressions that should not have their comments generated in maybe_comment 670 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 671 exp.Binary, 672 exp.SetOperation, 673 ) 674 675 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 676 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 677 exp.Column, 678 exp.Literal, 679 exp.Neg, 680 exp.Paren, 681 ) 682 683 PARAMETERIZABLE_TEXT_TYPES = { 684 exp.DataType.Type.NVARCHAR, 685 exp.DataType.Type.VARCHAR, 686 exp.DataType.Type.CHAR, 687 exp.DataType.Type.NCHAR, 688 } 689 690 # Expressions that need to have all CTEs under them bubbled up to them 691 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 692 693 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 694 695 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 696 697 __slots__ = ( 698 "pretty", 699 "identify", 700 "normalize", 701 "pad", 702 "_indent", 703 "normalize_functions", 704 "unsupported_level", 705 "max_unsupported", 706 "leading_comma", 707 "max_text_width", 708 "comments", 709 "dialect", 710 "unsupported_messages", 711 "_escaped_quote_end", 712 "_escaped_identifier_end", 713 "_next_name", 714 "_identifier_start", 715 "_identifier_end", 716 "_quote_json_path_key_using_brackets", 717 ) 718 719 def __init__( 720 self, 721 pretty: t.Optional[bool] = None, 722 identify: str | bool = False, 723 normalize: bool = False, 724 pad: int = 2, 725 indent: int = 2, 726 normalize_functions: t.Optional[str | bool] = None, 727 unsupported_level: ErrorLevel = ErrorLevel.WARN, 728 max_unsupported: int = 3, 729 leading_comma: bool = False, 730 max_text_width: int = 80, 731 comments: bool = True, 732 dialect: DialectType = None, 733 ): 734 import sqlglot 735 from sqlglot.dialects import Dialect 736 737 self.pretty = pretty if pretty is not None else sqlglot.pretty 738 self.identify = identify 739 self.normalize = normalize 740 self.pad = pad 741 self._indent = indent 742 self.unsupported_level = unsupported_level 743 self.max_unsupported = max_unsupported 744 self.leading_comma = leading_comma 745 self.max_text_width = max_text_width 746 self.comments = comments 747 self.dialect = Dialect.get_or_raise(dialect) 748 749 # This is both a Dialect property and a Generator argument, so we prioritize the latter 750 self.normalize_functions = ( 751 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 752 ) 753 754 self.unsupported_messages: t.List[str] = [] 755 self._escaped_quote_end: str = ( 756 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 757 ) 758 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 759 760 self._next_name = name_sequence("_t") 761 762 self._identifier_start = self.dialect.IDENTIFIER_START 763 self._identifier_end = self.dialect.IDENTIFIER_END 764 765 self._quote_json_path_key_using_brackets = True 766 767 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 768 """ 769 Generates the SQL string corresponding to the given syntax tree. 770 771 Args: 772 expression: The syntax tree. 773 copy: Whether to copy the expression. The generator performs mutations so 774 it is safer to copy. 775 776 Returns: 777 The SQL string corresponding to `expression`. 778 """ 779 if copy: 780 expression = expression.copy() 781 782 expression = self.preprocess(expression) 783 784 self.unsupported_messages = [] 785 sql = self.sql(expression).strip() 786 787 if self.pretty: 788 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 789 790 if self.unsupported_level == ErrorLevel.IGNORE: 791 return sql 792 793 if self.unsupported_level == ErrorLevel.WARN: 794 for msg in self.unsupported_messages: 795 logger.warning(msg) 796 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 797 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 798 799 return sql 800 801 def preprocess(self, expression: exp.Expression) -> exp.Expression: 802 """Apply generic preprocessing transformations to a given expression.""" 803 expression = self._move_ctes_to_top_level(expression) 804 805 if self.ENSURE_BOOLS: 806 from sqlglot.transforms import ensure_bools 807 808 expression = ensure_bools(expression) 809 810 return expression 811 812 def _move_ctes_to_top_level(self, expression: E) -> E: 813 if ( 814 not expression.parent 815 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 816 and any(node.parent is not expression for node in expression.find_all(exp.With)) 817 ): 818 from sqlglot.transforms import move_ctes_to_top_level 819 820 expression = move_ctes_to_top_level(expression) 821 return expression 822 823 def unsupported(self, message: str) -> None: 824 if self.unsupported_level == ErrorLevel.IMMEDIATE: 825 raise UnsupportedError(message) 826 self.unsupported_messages.append(message) 827 828 def sep(self, sep: str = " ") -> str: 829 return f"{sep.strip()}\n" if self.pretty else sep 830 831 def seg(self, sql: str, sep: str = " ") -> str: 832 return f"{self.sep(sep)}{sql}" 833 834 def sanitize_comment(self, comment: str) -> str: 835 comment = " " + comment if comment[0].strip() else comment 836 comment = comment + " " if comment[-1].strip() else comment 837 838 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 839 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 840 comment = comment.replace("*/", "* /") 841 842 return comment 843 844 def maybe_comment( 845 self, 846 sql: str, 847 expression: t.Optional[exp.Expression] = None, 848 comments: t.Optional[t.List[str]] = None, 849 separated: bool = False, 850 ) -> str: 851 comments = ( 852 ((expression and expression.comments) if comments is None else comments) # type: ignore 853 if self.comments 854 else None 855 ) 856 857 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 858 return sql 859 860 comments_sql = " ".join( 861 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 862 ) 863 864 if not comments_sql: 865 return sql 866 867 comments_sql = self._replace_line_breaks(comments_sql) 868 869 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 870 return ( 871 f"{self.sep()}{comments_sql}{sql}" 872 if not sql or sql[0].isspace() 873 else f"{comments_sql}{self.sep()}{sql}" 874 ) 875 876 return f"{sql} {comments_sql}" 877 878 def wrap(self, expression: exp.Expression | str) -> str: 879 this_sql = ( 880 self.sql(expression) 881 if isinstance(expression, exp.UNWRAPPED_QUERIES) 882 else self.sql(expression, "this") 883 ) 884 if not this_sql: 885 return "()" 886 887 this_sql = self.indent(this_sql, level=1, pad=0) 888 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 889 890 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 891 original = self.identify 892 self.identify = False 893 result = func(*args, **kwargs) 894 self.identify = original 895 return result 896 897 def normalize_func(self, name: str) -> str: 898 if self.normalize_functions == "upper" or self.normalize_functions is True: 899 return name.upper() 900 if self.normalize_functions == "lower": 901 return name.lower() 902 return name 903 904 def indent( 905 self, 906 sql: str, 907 level: int = 0, 908 pad: t.Optional[int] = None, 909 skip_first: bool = False, 910 skip_last: bool = False, 911 ) -> str: 912 if not self.pretty or not sql: 913 return sql 914 915 pad = self.pad if pad is None else pad 916 lines = sql.split("\n") 917 918 return "\n".join( 919 ( 920 line 921 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 922 else f"{' ' * (level * self._indent + pad)}{line}" 923 ) 924 for i, line in enumerate(lines) 925 ) 926 927 def sql( 928 self, 929 expression: t.Optional[str | exp.Expression], 930 key: t.Optional[str] = None, 931 comment: bool = True, 932 ) -> str: 933 if not expression: 934 return "" 935 936 if isinstance(expression, str): 937 return expression 938 939 if key: 940 value = expression.args.get(key) 941 if value: 942 return self.sql(value) 943 return "" 944 945 transform = self.TRANSFORMS.get(expression.__class__) 946 947 if callable(transform): 948 sql = transform(self, expression) 949 elif isinstance(expression, exp.Expression): 950 exp_handler_name = f"{expression.key}_sql" 951 952 if hasattr(self, exp_handler_name): 953 sql = getattr(self, exp_handler_name)(expression) 954 elif isinstance(expression, exp.Func): 955 sql = self.function_fallback_sql(expression) 956 elif isinstance(expression, exp.Property): 957 sql = self.property_sql(expression) 958 else: 959 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 960 else: 961 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 962 963 return self.maybe_comment(sql, expression) if self.comments and comment else sql 964 965 def uncache_sql(self, expression: exp.Uncache) -> str: 966 table = self.sql(expression, "this") 967 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 968 return f"UNCACHE TABLE{exists_sql} {table}" 969 970 def cache_sql(self, expression: exp.Cache) -> str: 971 lazy = " LAZY" if expression.args.get("lazy") else "" 972 table = self.sql(expression, "this") 973 options = expression.args.get("options") 974 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 975 sql = self.sql(expression, "expression") 976 sql = f" AS{self.sep()}{sql}" if sql else "" 977 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 978 return self.prepend_ctes(expression, sql) 979 980 def characterset_sql(self, expression: exp.CharacterSet) -> str: 981 if isinstance(expression.parent, exp.Cast): 982 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 983 default = "DEFAULT " if expression.args.get("default") else "" 984 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 985 986 def column_parts(self, expression: exp.Column) -> str: 987 return ".".join( 988 self.sql(part) 989 for part in ( 990 expression.args.get("catalog"), 991 expression.args.get("db"), 992 expression.args.get("table"), 993 expression.args.get("this"), 994 ) 995 if part 996 ) 997 998 def column_sql(self, expression: exp.Column) -> str: 999 join_mark = " (+)" if expression.args.get("join_mark") else "" 1000 1001 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1002 join_mark = "" 1003 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1004 1005 return f"{self.column_parts(expression)}{join_mark}" 1006 1007 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1008 this = self.sql(expression, "this") 1009 this = f" {this}" if this else "" 1010 position = self.sql(expression, "position") 1011 return f"{position}{this}" 1012 1013 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1014 column = self.sql(expression, "this") 1015 kind = self.sql(expression, "kind") 1016 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1017 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1018 kind = f"{sep}{kind}" if kind else "" 1019 constraints = f" {constraints}" if constraints else "" 1020 position = self.sql(expression, "position") 1021 position = f" {position}" if position else "" 1022 1023 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1024 kind = "" 1025 1026 return f"{exists}{column}{kind}{constraints}{position}" 1027 1028 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1029 this = self.sql(expression, "this") 1030 kind_sql = self.sql(expression, "kind").strip() 1031 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1032 1033 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1034 this = self.sql(expression, "this") 1035 if expression.args.get("not_null"): 1036 persisted = " PERSISTED NOT NULL" 1037 elif expression.args.get("persisted"): 1038 persisted = " PERSISTED" 1039 else: 1040 persisted = "" 1041 1042 return f"AS {this}{persisted}" 1043 1044 def autoincrementcolumnconstraint_sql(self, _) -> str: 1045 return self.token_sql(TokenType.AUTO_INCREMENT) 1046 1047 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1048 if isinstance(expression.this, list): 1049 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1050 else: 1051 this = self.sql(expression, "this") 1052 1053 return f"COMPRESS {this}" 1054 1055 def generatedasidentitycolumnconstraint_sql( 1056 self, expression: exp.GeneratedAsIdentityColumnConstraint 1057 ) -> str: 1058 this = "" 1059 if expression.this is not None: 1060 on_null = " ON NULL" if expression.args.get("on_null") else "" 1061 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1062 1063 start = expression.args.get("start") 1064 start = f"START WITH {start}" if start else "" 1065 increment = expression.args.get("increment") 1066 increment = f" INCREMENT BY {increment}" if increment else "" 1067 minvalue = expression.args.get("minvalue") 1068 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1069 maxvalue = expression.args.get("maxvalue") 1070 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1071 cycle = expression.args.get("cycle") 1072 cycle_sql = "" 1073 1074 if cycle is not None: 1075 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1076 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1077 1078 sequence_opts = "" 1079 if start or increment or cycle_sql: 1080 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1081 sequence_opts = f" ({sequence_opts.strip()})" 1082 1083 expr = self.sql(expression, "expression") 1084 expr = f"({expr})" if expr else "IDENTITY" 1085 1086 return f"GENERATED{this} AS {expr}{sequence_opts}" 1087 1088 def generatedasrowcolumnconstraint_sql( 1089 self, expression: exp.GeneratedAsRowColumnConstraint 1090 ) -> str: 1091 start = "START" if expression.args.get("start") else "END" 1092 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1093 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1094 1095 def periodforsystemtimeconstraint_sql( 1096 self, expression: exp.PeriodForSystemTimeConstraint 1097 ) -> str: 1098 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1099 1100 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1101 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1102 1103 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1104 desc = expression.args.get("desc") 1105 if desc is not None: 1106 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1107 options = self.expressions(expression, key="options", flat=True, sep=" ") 1108 options = f" {options}" if options else "" 1109 return f"PRIMARY KEY{options}" 1110 1111 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1112 this = self.sql(expression, "this") 1113 this = f" {this}" if this else "" 1114 index_type = expression.args.get("index_type") 1115 index_type = f" USING {index_type}" if index_type else "" 1116 on_conflict = self.sql(expression, "on_conflict") 1117 on_conflict = f" {on_conflict}" if on_conflict else "" 1118 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1119 options = self.expressions(expression, key="options", flat=True, sep=" ") 1120 options = f" {options}" if options else "" 1121 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1122 1123 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1124 return self.sql(expression, "this") 1125 1126 def create_sql(self, expression: exp.Create) -> str: 1127 kind = self.sql(expression, "kind") 1128 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1129 properties = expression.args.get("properties") 1130 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1131 1132 this = self.createable_sql(expression, properties_locs) 1133 1134 properties_sql = "" 1135 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1136 exp.Properties.Location.POST_WITH 1137 ): 1138 properties_sql = self.sql( 1139 exp.Properties( 1140 expressions=[ 1141 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1142 *properties_locs[exp.Properties.Location.POST_WITH], 1143 ] 1144 ) 1145 ) 1146 1147 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1148 properties_sql = self.sep() + properties_sql 1149 elif not self.pretty: 1150 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1151 properties_sql = f" {properties_sql}" 1152 1153 begin = " BEGIN" if expression.args.get("begin") else "" 1154 end = " END" if expression.args.get("end") else "" 1155 1156 expression_sql = self.sql(expression, "expression") 1157 if expression_sql: 1158 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1159 1160 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1161 postalias_props_sql = "" 1162 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1163 postalias_props_sql = self.properties( 1164 exp.Properties( 1165 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1166 ), 1167 wrapped=False, 1168 ) 1169 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1170 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1171 1172 postindex_props_sql = "" 1173 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1174 postindex_props_sql = self.properties( 1175 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1176 wrapped=False, 1177 prefix=" ", 1178 ) 1179 1180 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1181 indexes = f" {indexes}" if indexes else "" 1182 index_sql = indexes + postindex_props_sql 1183 1184 replace = " OR REPLACE" if expression.args.get("replace") else "" 1185 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1186 unique = " UNIQUE" if expression.args.get("unique") else "" 1187 1188 clustered = expression.args.get("clustered") 1189 if clustered is None: 1190 clustered_sql = "" 1191 elif clustered: 1192 clustered_sql = " CLUSTERED COLUMNSTORE" 1193 else: 1194 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1195 1196 postcreate_props_sql = "" 1197 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1198 postcreate_props_sql = self.properties( 1199 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1200 sep=" ", 1201 prefix=" ", 1202 wrapped=False, 1203 ) 1204 1205 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1206 1207 postexpression_props_sql = "" 1208 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1209 postexpression_props_sql = self.properties( 1210 exp.Properties( 1211 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1212 ), 1213 sep=" ", 1214 prefix=" ", 1215 wrapped=False, 1216 ) 1217 1218 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1219 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1220 no_schema_binding = ( 1221 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1222 ) 1223 1224 clone = self.sql(expression, "clone") 1225 clone = f" {clone}" if clone else "" 1226 1227 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1228 properties_expression = f"{expression_sql}{properties_sql}" 1229 else: 1230 properties_expression = f"{properties_sql}{expression_sql}" 1231 1232 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1233 return self.prepend_ctes(expression, expression_sql) 1234 1235 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1236 start = self.sql(expression, "start") 1237 start = f"START WITH {start}" if start else "" 1238 increment = self.sql(expression, "increment") 1239 increment = f" INCREMENT BY {increment}" if increment else "" 1240 minvalue = self.sql(expression, "minvalue") 1241 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1242 maxvalue = self.sql(expression, "maxvalue") 1243 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1244 owned = self.sql(expression, "owned") 1245 owned = f" OWNED BY {owned}" if owned else "" 1246 1247 cache = expression.args.get("cache") 1248 if cache is None: 1249 cache_str = "" 1250 elif cache is True: 1251 cache_str = " CACHE" 1252 else: 1253 cache_str = f" CACHE {cache}" 1254 1255 options = self.expressions(expression, key="options", flat=True, sep=" ") 1256 options = f" {options}" if options else "" 1257 1258 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1259 1260 def clone_sql(self, expression: exp.Clone) -> str: 1261 this = self.sql(expression, "this") 1262 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1263 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1264 return f"{shallow}{keyword} {this}" 1265 1266 def describe_sql(self, expression: exp.Describe) -> str: 1267 style = expression.args.get("style") 1268 style = f" {style}" if style else "" 1269 partition = self.sql(expression, "partition") 1270 partition = f" {partition}" if partition else "" 1271 format = self.sql(expression, "format") 1272 format = f" {format}" if format else "" 1273 1274 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1275 1276 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1277 tag = self.sql(expression, "tag") 1278 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1279 1280 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1281 with_ = self.sql(expression, "with") 1282 if with_: 1283 sql = f"{with_}{self.sep()}{sql}" 1284 return sql 1285 1286 def with_sql(self, expression: exp.With) -> str: 1287 sql = self.expressions(expression, flat=True) 1288 recursive = ( 1289 "RECURSIVE " 1290 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1291 else "" 1292 ) 1293 search = self.sql(expression, "search") 1294 search = f" {search}" if search else "" 1295 1296 return f"WITH {recursive}{sql}{search}" 1297 1298 def cte_sql(self, expression: exp.CTE) -> str: 1299 alias = expression.args.get("alias") 1300 if alias: 1301 alias.add_comments(expression.pop_comments()) 1302 1303 alias_sql = self.sql(expression, "alias") 1304 1305 materialized = expression.args.get("materialized") 1306 if materialized is False: 1307 materialized = "NOT MATERIALIZED " 1308 elif materialized: 1309 materialized = "MATERIALIZED " 1310 1311 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1312 1313 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1314 alias = self.sql(expression, "this") 1315 columns = self.expressions(expression, key="columns", flat=True) 1316 columns = f"({columns})" if columns else "" 1317 1318 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1319 columns = "" 1320 self.unsupported("Named columns are not supported in table alias.") 1321 1322 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1323 alias = self._next_name() 1324 1325 return f"{alias}{columns}" 1326 1327 def bitstring_sql(self, expression: exp.BitString) -> str: 1328 this = self.sql(expression, "this") 1329 if self.dialect.BIT_START: 1330 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1331 return f"{int(this, 2)}" 1332 1333 def hexstring_sql( 1334 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1335 ) -> str: 1336 this = self.sql(expression, "this") 1337 is_integer_type = expression.args.get("is_integer") 1338 1339 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1340 not self.dialect.HEX_START and not binary_function_repr 1341 ): 1342 # Integer representation will be returned if: 1343 # - The read dialect treats the hex value as integer literal but not the write 1344 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1345 return f"{int(this, 16)}" 1346 1347 if not is_integer_type: 1348 # Read dialect treats the hex value as BINARY/BLOB 1349 if binary_function_repr: 1350 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1351 return self.func(binary_function_repr, exp.Literal.string(this)) 1352 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1353 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1354 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1355 1356 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1357 1358 def bytestring_sql(self, expression: exp.ByteString) -> str: 1359 this = self.sql(expression, "this") 1360 if self.dialect.BYTE_START: 1361 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1362 return this 1363 1364 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1365 this = self.sql(expression, "this") 1366 escape = expression.args.get("escape") 1367 1368 if self.dialect.UNICODE_START: 1369 escape_substitute = r"\\\1" 1370 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1371 else: 1372 escape_substitute = r"\\u\1" 1373 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1374 1375 if escape: 1376 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1377 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1378 else: 1379 escape_pattern = ESCAPED_UNICODE_RE 1380 escape_sql = "" 1381 1382 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1383 this = escape_pattern.sub(escape_substitute, this) 1384 1385 return f"{left_quote}{this}{right_quote}{escape_sql}" 1386 1387 def rawstring_sql(self, expression: exp.RawString) -> str: 1388 string = expression.this 1389 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1390 string = string.replace("\\", "\\\\") 1391 1392 string = self.escape_str(string, escape_backslash=False) 1393 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1394 1395 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1396 this = self.sql(expression, "this") 1397 specifier = self.sql(expression, "expression") 1398 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1399 return f"{this}{specifier}" 1400 1401 def datatype_sql(self, expression: exp.DataType) -> str: 1402 nested = "" 1403 values = "" 1404 interior = self.expressions(expression, flat=True) 1405 1406 type_value = expression.this 1407 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1408 type_sql = self.sql(expression, "kind") 1409 else: 1410 type_sql = ( 1411 self.TYPE_MAPPING.get(type_value, type_value.value) 1412 if isinstance(type_value, exp.DataType.Type) 1413 else type_value 1414 ) 1415 1416 if interior: 1417 if expression.args.get("nested"): 1418 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1419 if expression.args.get("values") is not None: 1420 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1421 values = self.expressions(expression, key="values", flat=True) 1422 values = f"{delimiters[0]}{values}{delimiters[1]}" 1423 elif type_value == exp.DataType.Type.INTERVAL: 1424 nested = f" {interior}" 1425 else: 1426 nested = f"({interior})" 1427 1428 type_sql = f"{type_sql}{nested}{values}" 1429 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1430 exp.DataType.Type.TIMETZ, 1431 exp.DataType.Type.TIMESTAMPTZ, 1432 ): 1433 type_sql = f"{type_sql} WITH TIME ZONE" 1434 1435 return type_sql 1436 1437 def directory_sql(self, expression: exp.Directory) -> str: 1438 local = "LOCAL " if expression.args.get("local") else "" 1439 row_format = self.sql(expression, "row_format") 1440 row_format = f" {row_format}" if row_format else "" 1441 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1442 1443 def delete_sql(self, expression: exp.Delete) -> str: 1444 this = self.sql(expression, "this") 1445 this = f" FROM {this}" if this else "" 1446 using = self.sql(expression, "using") 1447 using = f" USING {using}" if using else "" 1448 cluster = self.sql(expression, "cluster") 1449 cluster = f" {cluster}" if cluster else "" 1450 where = self.sql(expression, "where") 1451 returning = self.sql(expression, "returning") 1452 limit = self.sql(expression, "limit") 1453 tables = self.expressions(expression, key="tables") 1454 tables = f" {tables}" if tables else "" 1455 if self.RETURNING_END: 1456 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1457 else: 1458 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1459 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1460 1461 def drop_sql(self, expression: exp.Drop) -> str: 1462 this = self.sql(expression, "this") 1463 expressions = self.expressions(expression, flat=True) 1464 expressions = f" ({expressions})" if expressions else "" 1465 kind = expression.args["kind"] 1466 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1467 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1468 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1469 on_cluster = self.sql(expression, "cluster") 1470 on_cluster = f" {on_cluster}" if on_cluster else "" 1471 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1472 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1473 cascade = " CASCADE" if expression.args.get("cascade") else "" 1474 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1475 purge = " PURGE" if expression.args.get("purge") else "" 1476 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1477 1478 def set_operation(self, expression: exp.SetOperation) -> str: 1479 op_type = type(expression) 1480 op_name = op_type.key.upper() 1481 1482 distinct = expression.args.get("distinct") 1483 if ( 1484 distinct is False 1485 and op_type in (exp.Except, exp.Intersect) 1486 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1487 ): 1488 self.unsupported(f"{op_name} ALL is not supported") 1489 1490 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1491 1492 if distinct is None: 1493 distinct = default_distinct 1494 if distinct is None: 1495 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1496 1497 if distinct is default_distinct: 1498 distinct_or_all = "" 1499 else: 1500 distinct_or_all = " DISTINCT" if distinct else " ALL" 1501 1502 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1503 side_kind = f"{side_kind} " if side_kind else "" 1504 1505 by_name = " BY NAME" if expression.args.get("by_name") else "" 1506 on = self.expressions(expression, key="on", flat=True) 1507 on = f" ON ({on})" if on else "" 1508 1509 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1510 1511 def set_operations(self, expression: exp.SetOperation) -> str: 1512 if not self.SET_OP_MODIFIERS: 1513 limit = expression.args.get("limit") 1514 order = expression.args.get("order") 1515 1516 if limit or order: 1517 select = self._move_ctes_to_top_level( 1518 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1519 ) 1520 1521 if limit: 1522 select = select.limit(limit.pop(), copy=False) 1523 if order: 1524 select = select.order_by(order.pop(), copy=False) 1525 return self.sql(select) 1526 1527 sqls: t.List[str] = [] 1528 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1529 1530 while stack: 1531 node = stack.pop() 1532 1533 if isinstance(node, exp.SetOperation): 1534 stack.append(node.expression) 1535 stack.append( 1536 self.maybe_comment( 1537 self.set_operation(node), comments=node.comments, separated=True 1538 ) 1539 ) 1540 stack.append(node.this) 1541 else: 1542 sqls.append(self.sql(node)) 1543 1544 this = self.sep().join(sqls) 1545 this = self.query_modifiers(expression, this) 1546 return self.prepend_ctes(expression, this) 1547 1548 def fetch_sql(self, expression: exp.Fetch) -> str: 1549 direction = expression.args.get("direction") 1550 direction = f" {direction}" if direction else "" 1551 count = self.sql(expression, "count") 1552 count = f" {count}" if count else "" 1553 limit_options = self.sql(expression, "limit_options") 1554 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1555 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1556 1557 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1558 percent = " PERCENT" if expression.args.get("percent") else "" 1559 rows = " ROWS" if expression.args.get("rows") else "" 1560 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1561 if not with_ties and rows: 1562 with_ties = " ONLY" 1563 return f"{percent}{rows}{with_ties}" 1564 1565 def filter_sql(self, expression: exp.Filter) -> str: 1566 if self.AGGREGATE_FILTER_SUPPORTED: 1567 this = self.sql(expression, "this") 1568 where = self.sql(expression, "expression").strip() 1569 return f"{this} FILTER({where})" 1570 1571 agg = expression.this 1572 agg_arg = agg.this 1573 cond = expression.expression.this 1574 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1575 return self.sql(agg) 1576 1577 def hint_sql(self, expression: exp.Hint) -> str: 1578 if not self.QUERY_HINTS: 1579 self.unsupported("Hints are not supported") 1580 return "" 1581 1582 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1583 1584 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1585 using = self.sql(expression, "using") 1586 using = f" USING {using}" if using else "" 1587 columns = self.expressions(expression, key="columns", flat=True) 1588 columns = f"({columns})" if columns else "" 1589 partition_by = self.expressions(expression, key="partition_by", flat=True) 1590 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1591 where = self.sql(expression, "where") 1592 include = self.expressions(expression, key="include", flat=True) 1593 if include: 1594 include = f" INCLUDE ({include})" 1595 with_storage = self.expressions(expression, key="with_storage", flat=True) 1596 with_storage = f" WITH ({with_storage})" if with_storage else "" 1597 tablespace = self.sql(expression, "tablespace") 1598 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1599 on = self.sql(expression, "on") 1600 on = f" ON {on}" if on else "" 1601 1602 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1603 1604 def index_sql(self, expression: exp.Index) -> str: 1605 unique = "UNIQUE " if expression.args.get("unique") else "" 1606 primary = "PRIMARY " if expression.args.get("primary") else "" 1607 amp = "AMP " if expression.args.get("amp") else "" 1608 name = self.sql(expression, "this") 1609 name = f"{name} " if name else "" 1610 table = self.sql(expression, "table") 1611 table = f"{self.INDEX_ON} {table}" if table else "" 1612 1613 index = "INDEX " if not table else "" 1614 1615 params = self.sql(expression, "params") 1616 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1617 1618 def identifier_sql(self, expression: exp.Identifier) -> str: 1619 text = expression.name 1620 lower = text.lower() 1621 text = lower if self.normalize and not expression.quoted else text 1622 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1623 if ( 1624 expression.quoted 1625 or self.dialect.can_identify(text, self.identify) 1626 or lower in self.RESERVED_KEYWORDS 1627 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1628 ): 1629 text = f"{self._identifier_start}{text}{self._identifier_end}" 1630 return text 1631 1632 def hex_sql(self, expression: exp.Hex) -> str: 1633 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1634 if self.dialect.HEX_LOWERCASE: 1635 text = self.func("LOWER", text) 1636 1637 return text 1638 1639 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1640 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1641 if not self.dialect.HEX_LOWERCASE: 1642 text = self.func("LOWER", text) 1643 return text 1644 1645 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1646 input_format = self.sql(expression, "input_format") 1647 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1648 output_format = self.sql(expression, "output_format") 1649 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1650 return self.sep().join((input_format, output_format)) 1651 1652 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1653 string = self.sql(exp.Literal.string(expression.name)) 1654 return f"{prefix}{string}" 1655 1656 def partition_sql(self, expression: exp.Partition) -> str: 1657 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1658 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1659 1660 def properties_sql(self, expression: exp.Properties) -> str: 1661 root_properties = [] 1662 with_properties = [] 1663 1664 for p in expression.expressions: 1665 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1666 if p_loc == exp.Properties.Location.POST_WITH: 1667 with_properties.append(p) 1668 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1669 root_properties.append(p) 1670 1671 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1672 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1673 1674 if root_props and with_props and not self.pretty: 1675 with_props = " " + with_props 1676 1677 return root_props + with_props 1678 1679 def root_properties(self, properties: exp.Properties) -> str: 1680 if properties.expressions: 1681 return self.expressions(properties, indent=False, sep=" ") 1682 return "" 1683 1684 def properties( 1685 self, 1686 properties: exp.Properties, 1687 prefix: str = "", 1688 sep: str = ", ", 1689 suffix: str = "", 1690 wrapped: bool = True, 1691 ) -> str: 1692 if properties.expressions: 1693 expressions = self.expressions(properties, sep=sep, indent=False) 1694 if expressions: 1695 expressions = self.wrap(expressions) if wrapped else expressions 1696 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1697 return "" 1698 1699 def with_properties(self, properties: exp.Properties) -> str: 1700 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1701 1702 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1703 properties_locs = defaultdict(list) 1704 for p in properties.expressions: 1705 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1706 if p_loc != exp.Properties.Location.UNSUPPORTED: 1707 properties_locs[p_loc].append(p) 1708 else: 1709 self.unsupported(f"Unsupported property {p.key}") 1710 1711 return properties_locs 1712 1713 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1714 if isinstance(expression.this, exp.Dot): 1715 return self.sql(expression, "this") 1716 return f"'{expression.name}'" if string_key else expression.name 1717 1718 def property_sql(self, expression: exp.Property) -> str: 1719 property_cls = expression.__class__ 1720 if property_cls == exp.Property: 1721 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1722 1723 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1724 if not property_name: 1725 self.unsupported(f"Unsupported property {expression.key}") 1726 1727 return f"{property_name}={self.sql(expression, 'this')}" 1728 1729 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1730 if self.SUPPORTS_CREATE_TABLE_LIKE: 1731 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1732 options = f" {options}" if options else "" 1733 1734 like = f"LIKE {self.sql(expression, 'this')}{options}" 1735 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1736 like = f"({like})" 1737 1738 return like 1739 1740 if expression.expressions: 1741 self.unsupported("Transpilation of LIKE property options is unsupported") 1742 1743 select = exp.select("*").from_(expression.this).limit(0) 1744 return f"AS {self.sql(select)}" 1745 1746 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1747 no = "NO " if expression.args.get("no") else "" 1748 protection = " PROTECTION" if expression.args.get("protection") else "" 1749 return f"{no}FALLBACK{protection}" 1750 1751 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1752 no = "NO " if expression.args.get("no") else "" 1753 local = expression.args.get("local") 1754 local = f"{local} " if local else "" 1755 dual = "DUAL " if expression.args.get("dual") else "" 1756 before = "BEFORE " if expression.args.get("before") else "" 1757 after = "AFTER " if expression.args.get("after") else "" 1758 return f"{no}{local}{dual}{before}{after}JOURNAL" 1759 1760 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1761 freespace = self.sql(expression, "this") 1762 percent = " PERCENT" if expression.args.get("percent") else "" 1763 return f"FREESPACE={freespace}{percent}" 1764 1765 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1766 if expression.args.get("default"): 1767 property = "DEFAULT" 1768 elif expression.args.get("on"): 1769 property = "ON" 1770 else: 1771 property = "OFF" 1772 return f"CHECKSUM={property}" 1773 1774 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1775 if expression.args.get("no"): 1776 return "NO MERGEBLOCKRATIO" 1777 if expression.args.get("default"): 1778 return "DEFAULT MERGEBLOCKRATIO" 1779 1780 percent = " PERCENT" if expression.args.get("percent") else "" 1781 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1782 1783 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1784 default = expression.args.get("default") 1785 minimum = expression.args.get("minimum") 1786 maximum = expression.args.get("maximum") 1787 if default or minimum or maximum: 1788 if default: 1789 prop = "DEFAULT" 1790 elif minimum: 1791 prop = "MINIMUM" 1792 else: 1793 prop = "MAXIMUM" 1794 return f"{prop} DATABLOCKSIZE" 1795 units = expression.args.get("units") 1796 units = f" {units}" if units else "" 1797 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1798 1799 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1800 autotemp = expression.args.get("autotemp") 1801 always = expression.args.get("always") 1802 default = expression.args.get("default") 1803 manual = expression.args.get("manual") 1804 never = expression.args.get("never") 1805 1806 if autotemp is not None: 1807 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1808 elif always: 1809 prop = "ALWAYS" 1810 elif default: 1811 prop = "DEFAULT" 1812 elif manual: 1813 prop = "MANUAL" 1814 elif never: 1815 prop = "NEVER" 1816 return f"BLOCKCOMPRESSION={prop}" 1817 1818 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1819 no = expression.args.get("no") 1820 no = " NO" if no else "" 1821 concurrent = expression.args.get("concurrent") 1822 concurrent = " CONCURRENT" if concurrent else "" 1823 target = self.sql(expression, "target") 1824 target = f" {target}" if target else "" 1825 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1826 1827 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1828 if isinstance(expression.this, list): 1829 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1830 if expression.this: 1831 modulus = self.sql(expression, "this") 1832 remainder = self.sql(expression, "expression") 1833 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1834 1835 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1836 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1837 return f"FROM ({from_expressions}) TO ({to_expressions})" 1838 1839 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1840 this = self.sql(expression, "this") 1841 1842 for_values_or_default = expression.expression 1843 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1844 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1845 else: 1846 for_values_or_default = " DEFAULT" 1847 1848 return f"PARTITION OF {this}{for_values_or_default}" 1849 1850 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1851 kind = expression.args.get("kind") 1852 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1853 for_or_in = expression.args.get("for_or_in") 1854 for_or_in = f" {for_or_in}" if for_or_in else "" 1855 lock_type = expression.args.get("lock_type") 1856 override = " OVERRIDE" if expression.args.get("override") else "" 1857 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1858 1859 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1860 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1861 statistics = expression.args.get("statistics") 1862 statistics_sql = "" 1863 if statistics is not None: 1864 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1865 return f"{data_sql}{statistics_sql}" 1866 1867 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1868 this = self.sql(expression, "this") 1869 this = f"HISTORY_TABLE={this}" if this else "" 1870 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1871 data_consistency = ( 1872 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1873 ) 1874 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1875 retention_period = ( 1876 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1877 ) 1878 1879 if this: 1880 on_sql = self.func("ON", this, data_consistency, retention_period) 1881 else: 1882 on_sql = "ON" if expression.args.get("on") else "OFF" 1883 1884 sql = f"SYSTEM_VERSIONING={on_sql}" 1885 1886 return f"WITH({sql})" if expression.args.get("with") else sql 1887 1888 def insert_sql(self, expression: exp.Insert) -> str: 1889 hint = self.sql(expression, "hint") 1890 overwrite = expression.args.get("overwrite") 1891 1892 if isinstance(expression.this, exp.Directory): 1893 this = " OVERWRITE" if overwrite else " INTO" 1894 else: 1895 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1896 1897 stored = self.sql(expression, "stored") 1898 stored = f" {stored}" if stored else "" 1899 alternative = expression.args.get("alternative") 1900 alternative = f" OR {alternative}" if alternative else "" 1901 ignore = " IGNORE" if expression.args.get("ignore") else "" 1902 is_function = expression.args.get("is_function") 1903 if is_function: 1904 this = f"{this} FUNCTION" 1905 this = f"{this} {self.sql(expression, 'this')}" 1906 1907 exists = " IF EXISTS" if expression.args.get("exists") else "" 1908 where = self.sql(expression, "where") 1909 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1910 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1911 on_conflict = self.sql(expression, "conflict") 1912 on_conflict = f" {on_conflict}" if on_conflict else "" 1913 by_name = " BY NAME" if expression.args.get("by_name") else "" 1914 returning = self.sql(expression, "returning") 1915 1916 if self.RETURNING_END: 1917 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1918 else: 1919 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1920 1921 partition_by = self.sql(expression, "partition") 1922 partition_by = f" {partition_by}" if partition_by else "" 1923 settings = self.sql(expression, "settings") 1924 settings = f" {settings}" if settings else "" 1925 1926 source = self.sql(expression, "source") 1927 source = f"TABLE {source}" if source else "" 1928 1929 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1930 return self.prepend_ctes(expression, sql) 1931 1932 def introducer_sql(self, expression: exp.Introducer) -> str: 1933 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1934 1935 def kill_sql(self, expression: exp.Kill) -> str: 1936 kind = self.sql(expression, "kind") 1937 kind = f" {kind}" if kind else "" 1938 this = self.sql(expression, "this") 1939 this = f" {this}" if this else "" 1940 return f"KILL{kind}{this}" 1941 1942 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1943 return expression.name 1944 1945 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1946 return expression.name 1947 1948 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1949 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1950 1951 constraint = self.sql(expression, "constraint") 1952 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1953 1954 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1955 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1956 action = self.sql(expression, "action") 1957 1958 expressions = self.expressions(expression, flat=True) 1959 if expressions: 1960 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1961 expressions = f" {set_keyword}{expressions}" 1962 1963 where = self.sql(expression, "where") 1964 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1965 1966 def returning_sql(self, expression: exp.Returning) -> str: 1967 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1968 1969 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1970 fields = self.sql(expression, "fields") 1971 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1972 escaped = self.sql(expression, "escaped") 1973 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1974 items = self.sql(expression, "collection_items") 1975 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1976 keys = self.sql(expression, "map_keys") 1977 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1978 lines = self.sql(expression, "lines") 1979 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1980 null = self.sql(expression, "null") 1981 null = f" NULL DEFINED AS {null}" if null else "" 1982 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1983 1984 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1985 return f"WITH ({self.expressions(expression, flat=True)})" 1986 1987 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1988 this = f"{self.sql(expression, 'this')} INDEX" 1989 target = self.sql(expression, "target") 1990 target = f" FOR {target}" if target else "" 1991 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1992 1993 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1994 this = self.sql(expression, "this") 1995 kind = self.sql(expression, "kind") 1996 expr = self.sql(expression, "expression") 1997 return f"{this} ({kind} => {expr})" 1998 1999 def table_parts(self, expression: exp.Table) -> str: 2000 return ".".join( 2001 self.sql(part) 2002 for part in ( 2003 expression.args.get("catalog"), 2004 expression.args.get("db"), 2005 expression.args.get("this"), 2006 ) 2007 if part is not None 2008 ) 2009 2010 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2011 table = self.table_parts(expression) 2012 only = "ONLY " if expression.args.get("only") else "" 2013 partition = self.sql(expression, "partition") 2014 partition = f" {partition}" if partition else "" 2015 version = self.sql(expression, "version") 2016 version = f" {version}" if version else "" 2017 alias = self.sql(expression, "alias") 2018 alias = f"{sep}{alias}" if alias else "" 2019 2020 sample = self.sql(expression, "sample") 2021 if self.dialect.ALIAS_POST_TABLESAMPLE: 2022 sample_pre_alias = sample 2023 sample_post_alias = "" 2024 else: 2025 sample_pre_alias = "" 2026 sample_post_alias = sample 2027 2028 hints = self.expressions(expression, key="hints", sep=" ") 2029 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2030 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2031 joins = self.indent( 2032 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2033 ) 2034 laterals = self.expressions(expression, key="laterals", sep="") 2035 2036 file_format = self.sql(expression, "format") 2037 if file_format: 2038 pattern = self.sql(expression, "pattern") 2039 pattern = f", PATTERN => {pattern}" if pattern else "" 2040 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2041 2042 ordinality = expression.args.get("ordinality") or "" 2043 if ordinality: 2044 ordinality = f" WITH ORDINALITY{alias}" 2045 alias = "" 2046 2047 when = self.sql(expression, "when") 2048 if when: 2049 table = f"{table} {when}" 2050 2051 changes = self.sql(expression, "changes") 2052 changes = f" {changes}" if changes else "" 2053 2054 rows_from = self.expressions(expression, key="rows_from") 2055 if rows_from: 2056 table = f"ROWS FROM {self.wrap(rows_from)}" 2057 2058 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2059 2060 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2061 table = self.func("TABLE", expression.this) 2062 alias = self.sql(expression, "alias") 2063 alias = f" AS {alias}" if alias else "" 2064 sample = self.sql(expression, "sample") 2065 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2066 joins = self.indent( 2067 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2068 ) 2069 return f"{table}{alias}{pivots}{sample}{joins}" 2070 2071 def tablesample_sql( 2072 self, 2073 expression: exp.TableSample, 2074 tablesample_keyword: t.Optional[str] = None, 2075 ) -> str: 2076 method = self.sql(expression, "method") 2077 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2078 numerator = self.sql(expression, "bucket_numerator") 2079 denominator = self.sql(expression, "bucket_denominator") 2080 field = self.sql(expression, "bucket_field") 2081 field = f" ON {field}" if field else "" 2082 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2083 seed = self.sql(expression, "seed") 2084 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2085 2086 size = self.sql(expression, "size") 2087 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2088 size = f"{size} ROWS" 2089 2090 percent = self.sql(expression, "percent") 2091 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2092 percent = f"{percent} PERCENT" 2093 2094 expr = f"{bucket}{percent}{size}" 2095 if self.TABLESAMPLE_REQUIRES_PARENS: 2096 expr = f"({expr})" 2097 2098 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2099 2100 def pivot_sql(self, expression: exp.Pivot) -> str: 2101 expressions = self.expressions(expression, flat=True) 2102 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2103 2104 group = self.sql(expression, "group") 2105 2106 if expression.this: 2107 this = self.sql(expression, "this") 2108 if not expressions: 2109 return f"UNPIVOT {this}" 2110 2111 on = f"{self.seg('ON')} {expressions}" 2112 into = self.sql(expression, "into") 2113 into = f"{self.seg('INTO')} {into}" if into else "" 2114 using = self.expressions(expression, key="using", flat=True) 2115 using = f"{self.seg('USING')} {using}" if using else "" 2116 return f"{direction} {this}{on}{into}{using}{group}" 2117 2118 alias = self.sql(expression, "alias") 2119 alias = f" AS {alias}" if alias else "" 2120 2121 fields = self.expressions( 2122 expression, 2123 "fields", 2124 sep=" ", 2125 dynamic=True, 2126 new_line=True, 2127 skip_first=True, 2128 skip_last=True, 2129 ) 2130 2131 include_nulls = expression.args.get("include_nulls") 2132 if include_nulls is not None: 2133 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2134 else: 2135 nulls = "" 2136 2137 default_on_null = self.sql(expression, "default_on_null") 2138 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2139 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2140 2141 def version_sql(self, expression: exp.Version) -> str: 2142 this = f"FOR {expression.name}" 2143 kind = expression.text("kind") 2144 expr = self.sql(expression, "expression") 2145 return f"{this} {kind} {expr}" 2146 2147 def tuple_sql(self, expression: exp.Tuple) -> str: 2148 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2149 2150 def update_sql(self, expression: exp.Update) -> str: 2151 this = self.sql(expression, "this") 2152 set_sql = self.expressions(expression, flat=True) 2153 from_sql = self.sql(expression, "from") 2154 where_sql = self.sql(expression, "where") 2155 returning = self.sql(expression, "returning") 2156 order = self.sql(expression, "order") 2157 limit = self.sql(expression, "limit") 2158 if self.RETURNING_END: 2159 expression_sql = f"{from_sql}{where_sql}{returning}" 2160 else: 2161 expression_sql = f"{returning}{from_sql}{where_sql}" 2162 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2163 return self.prepend_ctes(expression, sql) 2164 2165 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2166 values_as_table = values_as_table and self.VALUES_AS_TABLE 2167 2168 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2169 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2170 args = self.expressions(expression) 2171 alias = self.sql(expression, "alias") 2172 values = f"VALUES{self.seg('')}{args}" 2173 values = ( 2174 f"({values})" 2175 if self.WRAP_DERIVED_VALUES 2176 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2177 else values 2178 ) 2179 return f"{values} AS {alias}" if alias else values 2180 2181 # Converts `VALUES...` expression into a series of select unions. 2182 alias_node = expression.args.get("alias") 2183 column_names = alias_node and alias_node.columns 2184 2185 selects: t.List[exp.Query] = [] 2186 2187 for i, tup in enumerate(expression.expressions): 2188 row = tup.expressions 2189 2190 if i == 0 and column_names: 2191 row = [ 2192 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2193 ] 2194 2195 selects.append(exp.Select(expressions=row)) 2196 2197 if self.pretty: 2198 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2199 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2200 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2201 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2202 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2203 2204 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2205 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2206 return f"({unions}){alias}" 2207 2208 def var_sql(self, expression: exp.Var) -> str: 2209 return self.sql(expression, "this") 2210 2211 @unsupported_args("expressions") 2212 def into_sql(self, expression: exp.Into) -> str: 2213 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2214 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2215 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2216 2217 def from_sql(self, expression: exp.From) -> str: 2218 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2219 2220 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2221 grouping_sets = self.expressions(expression, indent=False) 2222 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2223 2224 def rollup_sql(self, expression: exp.Rollup) -> str: 2225 expressions = self.expressions(expression, indent=False) 2226 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2227 2228 def cube_sql(self, expression: exp.Cube) -> str: 2229 expressions = self.expressions(expression, indent=False) 2230 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2231 2232 def group_sql(self, expression: exp.Group) -> str: 2233 group_by_all = expression.args.get("all") 2234 if group_by_all is True: 2235 modifier = " ALL" 2236 elif group_by_all is False: 2237 modifier = " DISTINCT" 2238 else: 2239 modifier = "" 2240 2241 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2242 2243 grouping_sets = self.expressions(expression, key="grouping_sets") 2244 cube = self.expressions(expression, key="cube") 2245 rollup = self.expressions(expression, key="rollup") 2246 2247 groupings = csv( 2248 self.seg(grouping_sets) if grouping_sets else "", 2249 self.seg(cube) if cube else "", 2250 self.seg(rollup) if rollup else "", 2251 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2252 sep=self.GROUPINGS_SEP, 2253 ) 2254 2255 if ( 2256 expression.expressions 2257 and groupings 2258 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2259 ): 2260 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2261 2262 return f"{group_by}{groupings}" 2263 2264 def having_sql(self, expression: exp.Having) -> str: 2265 this = self.indent(self.sql(expression, "this")) 2266 return f"{self.seg('HAVING')}{self.sep()}{this}" 2267 2268 def connect_sql(self, expression: exp.Connect) -> str: 2269 start = self.sql(expression, "start") 2270 start = self.seg(f"START WITH {start}") if start else "" 2271 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2272 connect = self.sql(expression, "connect") 2273 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2274 return start + connect 2275 2276 def prior_sql(self, expression: exp.Prior) -> str: 2277 return f"PRIOR {self.sql(expression, 'this')}" 2278 2279 def join_sql(self, expression: exp.Join) -> str: 2280 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2281 side = None 2282 else: 2283 side = expression.side 2284 2285 op_sql = " ".join( 2286 op 2287 for op in ( 2288 expression.method, 2289 "GLOBAL" if expression.args.get("global") else None, 2290 side, 2291 expression.kind, 2292 expression.hint if self.JOIN_HINTS else None, 2293 ) 2294 if op 2295 ) 2296 match_cond = self.sql(expression, "match_condition") 2297 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2298 on_sql = self.sql(expression, "on") 2299 using = expression.args.get("using") 2300 2301 if not on_sql and using: 2302 on_sql = csv(*(self.sql(column) for column in using)) 2303 2304 this = expression.this 2305 this_sql = self.sql(this) 2306 2307 exprs = self.expressions(expression) 2308 if exprs: 2309 this_sql = f"{this_sql},{self.seg(exprs)}" 2310 2311 if on_sql: 2312 on_sql = self.indent(on_sql, skip_first=True) 2313 space = self.seg(" " * self.pad) if self.pretty else " " 2314 if using: 2315 on_sql = f"{space}USING ({on_sql})" 2316 else: 2317 on_sql = f"{space}ON {on_sql}" 2318 elif not op_sql: 2319 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2320 return f" {this_sql}" 2321 2322 return f", {this_sql}" 2323 2324 if op_sql != "STRAIGHT_JOIN": 2325 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2326 2327 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2328 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2329 2330 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2331 args = self.expressions(expression, flat=True) 2332 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2333 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2334 2335 def lateral_op(self, expression: exp.Lateral) -> str: 2336 cross_apply = expression.args.get("cross_apply") 2337 2338 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2339 if cross_apply is True: 2340 op = "INNER JOIN " 2341 elif cross_apply is False: 2342 op = "LEFT JOIN " 2343 else: 2344 op = "" 2345 2346 return f"{op}LATERAL" 2347 2348 def lateral_sql(self, expression: exp.Lateral) -> str: 2349 this = self.sql(expression, "this") 2350 2351 if expression.args.get("view"): 2352 alias = expression.args["alias"] 2353 columns = self.expressions(alias, key="columns", flat=True) 2354 table = f" {alias.name}" if alias.name else "" 2355 columns = f" AS {columns}" if columns else "" 2356 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2357 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2358 2359 alias = self.sql(expression, "alias") 2360 alias = f" AS {alias}" if alias else "" 2361 2362 ordinality = expression.args.get("ordinality") or "" 2363 if ordinality: 2364 ordinality = f" WITH ORDINALITY{alias}" 2365 alias = "" 2366 2367 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2368 2369 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2370 this = self.sql(expression, "this") 2371 2372 args = [ 2373 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2374 for e in (expression.args.get(k) for k in ("offset", "expression")) 2375 if e 2376 ] 2377 2378 args_sql = ", ".join(self.sql(e) for e in args) 2379 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2380 expressions = self.expressions(expression, flat=True) 2381 limit_options = self.sql(expression, "limit_options") 2382 expressions = f" BY {expressions}" if expressions else "" 2383 2384 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2385 2386 def offset_sql(self, expression: exp.Offset) -> str: 2387 this = self.sql(expression, "this") 2388 value = expression.expression 2389 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2390 expressions = self.expressions(expression, flat=True) 2391 expressions = f" BY {expressions}" if expressions else "" 2392 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2393 2394 def setitem_sql(self, expression: exp.SetItem) -> str: 2395 kind = self.sql(expression, "kind") 2396 kind = f"{kind} " if kind else "" 2397 this = self.sql(expression, "this") 2398 expressions = self.expressions(expression) 2399 collate = self.sql(expression, "collate") 2400 collate = f" COLLATE {collate}" if collate else "" 2401 global_ = "GLOBAL " if expression.args.get("global") else "" 2402 return f"{global_}{kind}{this}{expressions}{collate}" 2403 2404 def set_sql(self, expression: exp.Set) -> str: 2405 expressions = f" {self.expressions(expression, flat=True)}" 2406 tag = " TAG" if expression.args.get("tag") else "" 2407 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2408 2409 def queryband_sql(self, expression: exp.QueryBand) -> str: 2410 this = self.sql(expression, "this") 2411 update = " UPDATE" if expression.args.get("update") else "" 2412 scope = self.sql(expression, "scope") 2413 scope = f" FOR {scope}" if scope else "" 2414 2415 return f"QUERY_BAND = {this}{update}{scope}" 2416 2417 def pragma_sql(self, expression: exp.Pragma) -> str: 2418 return f"PRAGMA {self.sql(expression, 'this')}" 2419 2420 def lock_sql(self, expression: exp.Lock) -> str: 2421 if not self.LOCKING_READS_SUPPORTED: 2422 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2423 return "" 2424 2425 update = expression.args["update"] 2426 key = expression.args.get("key") 2427 if update: 2428 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2429 else: 2430 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2431 expressions = self.expressions(expression, flat=True) 2432 expressions = f" OF {expressions}" if expressions else "" 2433 wait = expression.args.get("wait") 2434 2435 if wait is not None: 2436 if isinstance(wait, exp.Literal): 2437 wait = f" WAIT {self.sql(wait)}" 2438 else: 2439 wait = " NOWAIT" if wait else " SKIP LOCKED" 2440 2441 return f"{lock_type}{expressions}{wait or ''}" 2442 2443 def literal_sql(self, expression: exp.Literal) -> str: 2444 text = expression.this or "" 2445 if expression.is_string: 2446 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2447 return text 2448 2449 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2450 if self.dialect.ESCAPED_SEQUENCES: 2451 to_escaped = self.dialect.ESCAPED_SEQUENCES 2452 text = "".join( 2453 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2454 ) 2455 2456 return self._replace_line_breaks(text).replace( 2457 self.dialect.QUOTE_END, self._escaped_quote_end 2458 ) 2459 2460 def loaddata_sql(self, expression: exp.LoadData) -> str: 2461 local = " LOCAL" if expression.args.get("local") else "" 2462 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2463 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2464 this = f" INTO TABLE {self.sql(expression, 'this')}" 2465 partition = self.sql(expression, "partition") 2466 partition = f" {partition}" if partition else "" 2467 input_format = self.sql(expression, "input_format") 2468 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2469 serde = self.sql(expression, "serde") 2470 serde = f" SERDE {serde}" if serde else "" 2471 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2472 2473 def null_sql(self, *_) -> str: 2474 return "NULL" 2475 2476 def boolean_sql(self, expression: exp.Boolean) -> str: 2477 return "TRUE" if expression.this else "FALSE" 2478 2479 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2480 this = self.sql(expression, "this") 2481 this = f"{this} " if this else this 2482 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2483 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2484 2485 def withfill_sql(self, expression: exp.WithFill) -> str: 2486 from_sql = self.sql(expression, "from") 2487 from_sql = f" FROM {from_sql}" if from_sql else "" 2488 to_sql = self.sql(expression, "to") 2489 to_sql = f" TO {to_sql}" if to_sql else "" 2490 step_sql = self.sql(expression, "step") 2491 step_sql = f" STEP {step_sql}" if step_sql else "" 2492 interpolated_values = [ 2493 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2494 if isinstance(e, exp.Alias) 2495 else self.sql(e, "this") 2496 for e in expression.args.get("interpolate") or [] 2497 ] 2498 interpolate = ( 2499 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2500 ) 2501 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2502 2503 def cluster_sql(self, expression: exp.Cluster) -> str: 2504 return self.op_expressions("CLUSTER BY", expression) 2505 2506 def distribute_sql(self, expression: exp.Distribute) -> str: 2507 return self.op_expressions("DISTRIBUTE BY", expression) 2508 2509 def sort_sql(self, expression: exp.Sort) -> str: 2510 return self.op_expressions("SORT BY", expression) 2511 2512 def ordered_sql(self, expression: exp.Ordered) -> str: 2513 desc = expression.args.get("desc") 2514 asc = not desc 2515 2516 nulls_first = expression.args.get("nulls_first") 2517 nulls_last = not nulls_first 2518 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2519 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2520 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2521 2522 this = self.sql(expression, "this") 2523 2524 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2525 nulls_sort_change = "" 2526 if nulls_first and ( 2527 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2528 ): 2529 nulls_sort_change = " NULLS FIRST" 2530 elif ( 2531 nulls_last 2532 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2533 and not nulls_are_last 2534 ): 2535 nulls_sort_change = " NULLS LAST" 2536 2537 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2538 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2539 window = expression.find_ancestor(exp.Window, exp.Select) 2540 if isinstance(window, exp.Window) and window.args.get("spec"): 2541 self.unsupported( 2542 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2543 ) 2544 nulls_sort_change = "" 2545 elif self.NULL_ORDERING_SUPPORTED is False and ( 2546 (asc and nulls_sort_change == " NULLS LAST") 2547 or (desc and nulls_sort_change == " NULLS FIRST") 2548 ): 2549 # BigQuery does not allow these ordering/nulls combinations when used under 2550 # an aggregation func or under a window containing one 2551 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2552 2553 if isinstance(ancestor, exp.Window): 2554 ancestor = ancestor.this 2555 if isinstance(ancestor, exp.AggFunc): 2556 self.unsupported( 2557 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2558 ) 2559 nulls_sort_change = "" 2560 elif self.NULL_ORDERING_SUPPORTED is None: 2561 if expression.this.is_int: 2562 self.unsupported( 2563 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2564 ) 2565 elif not isinstance(expression.this, exp.Rand): 2566 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2567 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2568 nulls_sort_change = "" 2569 2570 with_fill = self.sql(expression, "with_fill") 2571 with_fill = f" {with_fill}" if with_fill else "" 2572 2573 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2574 2575 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2576 window_frame = self.sql(expression, "window_frame") 2577 window_frame = f"{window_frame} " if window_frame else "" 2578 2579 this = self.sql(expression, "this") 2580 2581 return f"{window_frame}{this}" 2582 2583 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2584 partition = self.partition_by_sql(expression) 2585 order = self.sql(expression, "order") 2586 measures = self.expressions(expression, key="measures") 2587 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2588 rows = self.sql(expression, "rows") 2589 rows = self.seg(rows) if rows else "" 2590 after = self.sql(expression, "after") 2591 after = self.seg(after) if after else "" 2592 pattern = self.sql(expression, "pattern") 2593 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2594 definition_sqls = [ 2595 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2596 for definition in expression.args.get("define", []) 2597 ] 2598 definitions = self.expressions(sqls=definition_sqls) 2599 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2600 body = "".join( 2601 ( 2602 partition, 2603 order, 2604 measures, 2605 rows, 2606 after, 2607 pattern, 2608 define, 2609 ) 2610 ) 2611 alias = self.sql(expression, "alias") 2612 alias = f" {alias}" if alias else "" 2613 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2614 2615 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2616 limit = expression.args.get("limit") 2617 2618 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2619 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2620 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2621 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2622 2623 return csv( 2624 *sqls, 2625 *[self.sql(join) for join in expression.args.get("joins") or []], 2626 self.sql(expression, "match"), 2627 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2628 self.sql(expression, "prewhere"), 2629 self.sql(expression, "where"), 2630 self.sql(expression, "connect"), 2631 self.sql(expression, "group"), 2632 self.sql(expression, "having"), 2633 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2634 self.sql(expression, "order"), 2635 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2636 *self.after_limit_modifiers(expression), 2637 self.options_modifier(expression), 2638 self.for_modifiers(expression), 2639 sep="", 2640 ) 2641 2642 def options_modifier(self, expression: exp.Expression) -> str: 2643 options = self.expressions(expression, key="options") 2644 return f" {options}" if options else "" 2645 2646 def for_modifiers(self, expression: exp.Expression) -> str: 2647 for_modifiers = self.expressions(expression, key="for") 2648 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2649 2650 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2651 self.unsupported("Unsupported query option.") 2652 return "" 2653 2654 def offset_limit_modifiers( 2655 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2656 ) -> t.List[str]: 2657 return [ 2658 self.sql(expression, "offset") if fetch else self.sql(limit), 2659 self.sql(limit) if fetch else self.sql(expression, "offset"), 2660 ] 2661 2662 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2663 locks = self.expressions(expression, key="locks", sep=" ") 2664 locks = f" {locks}" if locks else "" 2665 return [locks, self.sql(expression, "sample")] 2666 2667 def select_sql(self, expression: exp.Select) -> str: 2668 into = expression.args.get("into") 2669 if not self.SUPPORTS_SELECT_INTO and into: 2670 into.pop() 2671 2672 hint = self.sql(expression, "hint") 2673 distinct = self.sql(expression, "distinct") 2674 distinct = f" {distinct}" if distinct else "" 2675 kind = self.sql(expression, "kind") 2676 2677 limit = expression.args.get("limit") 2678 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2679 top = self.limit_sql(limit, top=True) 2680 limit.pop() 2681 else: 2682 top = "" 2683 2684 expressions = self.expressions(expression) 2685 2686 if kind: 2687 if kind in self.SELECT_KINDS: 2688 kind = f" AS {kind}" 2689 else: 2690 if kind == "STRUCT": 2691 expressions = self.expressions( 2692 sqls=[ 2693 self.sql( 2694 exp.Struct( 2695 expressions=[ 2696 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2697 if isinstance(e, exp.Alias) 2698 else e 2699 for e in expression.expressions 2700 ] 2701 ) 2702 ) 2703 ] 2704 ) 2705 kind = "" 2706 2707 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2708 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2709 2710 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2711 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2712 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2713 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2714 sql = self.query_modifiers( 2715 expression, 2716 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2717 self.sql(expression, "into", comment=False), 2718 self.sql(expression, "from", comment=False), 2719 ) 2720 2721 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2722 if expression.args.get("with"): 2723 sql = self.maybe_comment(sql, expression) 2724 expression.pop_comments() 2725 2726 sql = self.prepend_ctes(expression, sql) 2727 2728 if not self.SUPPORTS_SELECT_INTO and into: 2729 if into.args.get("temporary"): 2730 table_kind = " TEMPORARY" 2731 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2732 table_kind = " UNLOGGED" 2733 else: 2734 table_kind = "" 2735 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2736 2737 return sql 2738 2739 def schema_sql(self, expression: exp.Schema) -> str: 2740 this = self.sql(expression, "this") 2741 sql = self.schema_columns_sql(expression) 2742 return f"{this} {sql}" if this and sql else this or sql 2743 2744 def schema_columns_sql(self, expression: exp.Schema) -> str: 2745 if expression.expressions: 2746 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2747 return "" 2748 2749 def star_sql(self, expression: exp.Star) -> str: 2750 except_ = self.expressions(expression, key="except", flat=True) 2751 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2752 replace = self.expressions(expression, key="replace", flat=True) 2753 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2754 rename = self.expressions(expression, key="rename", flat=True) 2755 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2756 return f"*{except_}{replace}{rename}" 2757 2758 def parameter_sql(self, expression: exp.Parameter) -> str: 2759 this = self.sql(expression, "this") 2760 return f"{self.PARAMETER_TOKEN}{this}" 2761 2762 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2763 this = self.sql(expression, "this") 2764 kind = expression.text("kind") 2765 if kind: 2766 kind = f"{kind}." 2767 return f"@@{kind}{this}" 2768 2769 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2770 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2771 2772 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2773 alias = self.sql(expression, "alias") 2774 alias = f"{sep}{alias}" if alias else "" 2775 sample = self.sql(expression, "sample") 2776 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2777 alias = f"{sample}{alias}" 2778 2779 # Set to None so it's not generated again by self.query_modifiers() 2780 expression.set("sample", None) 2781 2782 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2783 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2784 return self.prepend_ctes(expression, sql) 2785 2786 def qualify_sql(self, expression: exp.Qualify) -> str: 2787 this = self.indent(self.sql(expression, "this")) 2788 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2789 2790 def unnest_sql(self, expression: exp.Unnest) -> str: 2791 args = self.expressions(expression, flat=True) 2792 2793 alias = expression.args.get("alias") 2794 offset = expression.args.get("offset") 2795 2796 if self.UNNEST_WITH_ORDINALITY: 2797 if alias and isinstance(offset, exp.Expression): 2798 alias.append("columns", offset) 2799 2800 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2801 columns = alias.columns 2802 alias = self.sql(columns[0]) if columns else "" 2803 else: 2804 alias = self.sql(alias) 2805 2806 alias = f" AS {alias}" if alias else alias 2807 if self.UNNEST_WITH_ORDINALITY: 2808 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2809 else: 2810 if isinstance(offset, exp.Expression): 2811 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2812 elif offset: 2813 suffix = f"{alias} WITH OFFSET" 2814 else: 2815 suffix = alias 2816 2817 return f"UNNEST({args}){suffix}" 2818 2819 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2820 return "" 2821 2822 def where_sql(self, expression: exp.Where) -> str: 2823 this = self.indent(self.sql(expression, "this")) 2824 return f"{self.seg('WHERE')}{self.sep()}{this}" 2825 2826 def window_sql(self, expression: exp.Window) -> str: 2827 this = self.sql(expression, "this") 2828 partition = self.partition_by_sql(expression) 2829 order = expression.args.get("order") 2830 order = self.order_sql(order, flat=True) if order else "" 2831 spec = self.sql(expression, "spec") 2832 alias = self.sql(expression, "alias") 2833 over = self.sql(expression, "over") or "OVER" 2834 2835 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2836 2837 first = expression.args.get("first") 2838 if first is None: 2839 first = "" 2840 else: 2841 first = "FIRST" if first else "LAST" 2842 2843 if not partition and not order and not spec and alias: 2844 return f"{this} {alias}" 2845 2846 args = self.format_args( 2847 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2848 ) 2849 return f"{this} ({args})" 2850 2851 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2852 partition = self.expressions(expression, key="partition_by", flat=True) 2853 return f"PARTITION BY {partition}" if partition else "" 2854 2855 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2856 kind = self.sql(expression, "kind") 2857 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2858 end = ( 2859 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2860 or "CURRENT ROW" 2861 ) 2862 2863 window_spec = f"{kind} BETWEEN {start} AND {end}" 2864 2865 exclude = self.sql(expression, "exclude") 2866 if exclude: 2867 if self.SUPPORTS_WINDOW_EXCLUDE: 2868 window_spec += f" EXCLUDE {exclude}" 2869 else: 2870 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2871 2872 return window_spec 2873 2874 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2875 this = self.sql(expression, "this") 2876 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2877 return f"{this} WITHIN GROUP ({expression_sql})" 2878 2879 def between_sql(self, expression: exp.Between) -> str: 2880 this = self.sql(expression, "this") 2881 low = self.sql(expression, "low") 2882 high = self.sql(expression, "high") 2883 symmetric = expression.args.get("symmetric") 2884 2885 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2886 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2887 2888 flag = ( 2889 " SYMMETRIC" 2890 if symmetric 2891 else " ASYMMETRIC" 2892 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2893 else "" # silently drop ASYMMETRIC – semantics identical 2894 ) 2895 return f"{this} BETWEEN{flag} {low} AND {high}" 2896 2897 def bracket_offset_expressions( 2898 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2899 ) -> t.List[exp.Expression]: 2900 return apply_index_offset( 2901 expression.this, 2902 expression.expressions, 2903 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2904 dialect=self.dialect, 2905 ) 2906 2907 def bracket_sql(self, expression: exp.Bracket) -> str: 2908 expressions = self.bracket_offset_expressions(expression) 2909 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2910 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2911 2912 def all_sql(self, expression: exp.All) -> str: 2913 this = self.sql(expression, "this") 2914 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2915 this = self.wrap(this) 2916 return f"ALL {this}" 2917 2918 def any_sql(self, expression: exp.Any) -> str: 2919 this = self.sql(expression, "this") 2920 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2921 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2922 this = self.wrap(this) 2923 return f"ANY{this}" 2924 return f"ANY {this}" 2925 2926 def exists_sql(self, expression: exp.Exists) -> str: 2927 return f"EXISTS{self.wrap(expression)}" 2928 2929 def case_sql(self, expression: exp.Case) -> str: 2930 this = self.sql(expression, "this") 2931 statements = [f"CASE {this}" if this else "CASE"] 2932 2933 for e in expression.args["ifs"]: 2934 statements.append(f"WHEN {self.sql(e, 'this')}") 2935 statements.append(f"THEN {self.sql(e, 'true')}") 2936 2937 default = self.sql(expression, "default") 2938 2939 if default: 2940 statements.append(f"ELSE {default}") 2941 2942 statements.append("END") 2943 2944 if self.pretty and self.too_wide(statements): 2945 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2946 2947 return " ".join(statements) 2948 2949 def constraint_sql(self, expression: exp.Constraint) -> str: 2950 this = self.sql(expression, "this") 2951 expressions = self.expressions(expression, flat=True) 2952 return f"CONSTRAINT {this} {expressions}" 2953 2954 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2955 order = expression.args.get("order") 2956 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2957 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2958 2959 def extract_sql(self, expression: exp.Extract) -> str: 2960 from sqlglot.dialects.dialect import map_date_part 2961 2962 this = ( 2963 map_date_part(expression.this, self.dialect) 2964 if self.NORMALIZE_EXTRACT_DATE_PARTS 2965 else expression.this 2966 ) 2967 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2968 expression_sql = self.sql(expression, "expression") 2969 2970 return f"EXTRACT({this_sql} FROM {expression_sql})" 2971 2972 def trim_sql(self, expression: exp.Trim) -> str: 2973 trim_type = self.sql(expression, "position") 2974 2975 if trim_type == "LEADING": 2976 func_name = "LTRIM" 2977 elif trim_type == "TRAILING": 2978 func_name = "RTRIM" 2979 else: 2980 func_name = "TRIM" 2981 2982 return self.func(func_name, expression.this, expression.expression) 2983 2984 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2985 args = expression.expressions 2986 if isinstance(expression, exp.ConcatWs): 2987 args = args[1:] # Skip the delimiter 2988 2989 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2990 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2991 2992 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2993 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2994 2995 return args 2996 2997 def concat_sql(self, expression: exp.Concat) -> str: 2998 expressions = self.convert_concat_args(expression) 2999 3000 # Some dialects don't allow a single-argument CONCAT call 3001 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3002 return self.sql(expressions[0]) 3003 3004 return self.func("CONCAT", *expressions) 3005 3006 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3007 return self.func( 3008 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3009 ) 3010 3011 def check_sql(self, expression: exp.Check) -> str: 3012 this = self.sql(expression, key="this") 3013 return f"CHECK ({this})" 3014 3015 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3016 expressions = self.expressions(expression, flat=True) 3017 expressions = f" ({expressions})" if expressions else "" 3018 reference = self.sql(expression, "reference") 3019 reference = f" {reference}" if reference else "" 3020 delete = self.sql(expression, "delete") 3021 delete = f" ON DELETE {delete}" if delete else "" 3022 update = self.sql(expression, "update") 3023 update = f" ON UPDATE {update}" if update else "" 3024 options = self.expressions(expression, key="options", flat=True, sep=" ") 3025 options = f" {options}" if options else "" 3026 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3027 3028 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3029 expressions = self.expressions(expression, flat=True) 3030 include = self.sql(expression, "include") 3031 options = self.expressions(expression, key="options", flat=True, sep=" ") 3032 options = f" {options}" if options else "" 3033 return f"PRIMARY KEY ({expressions}){include}{options}" 3034 3035 def if_sql(self, expression: exp.If) -> str: 3036 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3037 3038 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3039 modifier = expression.args.get("modifier") 3040 modifier = f" {modifier}" if modifier else "" 3041 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3042 3043 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3044 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3045 3046 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3047 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3048 3049 if expression.args.get("escape"): 3050 path = self.escape_str(path) 3051 3052 if self.QUOTE_JSON_PATH: 3053 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3054 3055 return path 3056 3057 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3058 if isinstance(expression, exp.JSONPathPart): 3059 transform = self.TRANSFORMS.get(expression.__class__) 3060 if not callable(transform): 3061 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3062 return "" 3063 3064 return transform(self, expression) 3065 3066 if isinstance(expression, int): 3067 return str(expression) 3068 3069 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3070 escaped = expression.replace("'", "\\'") 3071 escaped = f"\\'{expression}\\'" 3072 else: 3073 escaped = expression.replace('"', '\\"') 3074 escaped = f'"{escaped}"' 3075 3076 return escaped 3077 3078 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3079 return f"{self.sql(expression, 'this')} FORMAT JSON" 3080 3081 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3082 # Output the Teradata column FORMAT override. 3083 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3084 this = self.sql(expression, "this") 3085 fmt = self.sql(expression, "format") 3086 return f"{this} (FORMAT {fmt})" 3087 3088 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3089 null_handling = expression.args.get("null_handling") 3090 null_handling = f" {null_handling}" if null_handling else "" 3091 3092 unique_keys = expression.args.get("unique_keys") 3093 if unique_keys is not None: 3094 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3095 else: 3096 unique_keys = "" 3097 3098 return_type = self.sql(expression, "return_type") 3099 return_type = f" RETURNING {return_type}" if return_type else "" 3100 encoding = self.sql(expression, "encoding") 3101 encoding = f" ENCODING {encoding}" if encoding else "" 3102 3103 return self.func( 3104 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3105 *expression.expressions, 3106 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3107 ) 3108 3109 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3110 return self.jsonobject_sql(expression) 3111 3112 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3113 null_handling = expression.args.get("null_handling") 3114 null_handling = f" {null_handling}" if null_handling else "" 3115 return_type = self.sql(expression, "return_type") 3116 return_type = f" RETURNING {return_type}" if return_type else "" 3117 strict = " STRICT" if expression.args.get("strict") else "" 3118 return self.func( 3119 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3120 ) 3121 3122 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3123 this = self.sql(expression, "this") 3124 order = self.sql(expression, "order") 3125 null_handling = expression.args.get("null_handling") 3126 null_handling = f" {null_handling}" if null_handling else "" 3127 return_type = self.sql(expression, "return_type") 3128 return_type = f" RETURNING {return_type}" if return_type else "" 3129 strict = " STRICT" if expression.args.get("strict") else "" 3130 return self.func( 3131 "JSON_ARRAYAGG", 3132 this, 3133 suffix=f"{order}{null_handling}{return_type}{strict})", 3134 ) 3135 3136 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3137 path = self.sql(expression, "path") 3138 path = f" PATH {path}" if path else "" 3139 nested_schema = self.sql(expression, "nested_schema") 3140 3141 if nested_schema: 3142 return f"NESTED{path} {nested_schema}" 3143 3144 this = self.sql(expression, "this") 3145 kind = self.sql(expression, "kind") 3146 kind = f" {kind}" if kind else "" 3147 return f"{this}{kind}{path}" 3148 3149 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3150 return self.func("COLUMNS", *expression.expressions) 3151 3152 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3153 this = self.sql(expression, "this") 3154 path = self.sql(expression, "path") 3155 path = f", {path}" if path else "" 3156 error_handling = expression.args.get("error_handling") 3157 error_handling = f" {error_handling}" if error_handling else "" 3158 empty_handling = expression.args.get("empty_handling") 3159 empty_handling = f" {empty_handling}" if empty_handling else "" 3160 schema = self.sql(expression, "schema") 3161 return self.func( 3162 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3163 ) 3164 3165 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3166 this = self.sql(expression, "this") 3167 kind = self.sql(expression, "kind") 3168 path = self.sql(expression, "path") 3169 path = f" {path}" if path else "" 3170 as_json = " AS JSON" if expression.args.get("as_json") else "" 3171 return f"{this} {kind}{path}{as_json}" 3172 3173 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3174 this = self.sql(expression, "this") 3175 path = self.sql(expression, "path") 3176 path = f", {path}" if path else "" 3177 expressions = self.expressions(expression) 3178 with_ = ( 3179 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3180 if expressions 3181 else "" 3182 ) 3183 return f"OPENJSON({this}{path}){with_}" 3184 3185 def in_sql(self, expression: exp.In) -> str: 3186 query = expression.args.get("query") 3187 unnest = expression.args.get("unnest") 3188 field = expression.args.get("field") 3189 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3190 3191 if query: 3192 in_sql = self.sql(query) 3193 elif unnest: 3194 in_sql = self.in_unnest_op(unnest) 3195 elif field: 3196 in_sql = self.sql(field) 3197 else: 3198 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3199 3200 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3201 3202 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3203 return f"(SELECT {self.sql(unnest)})" 3204 3205 def interval_sql(self, expression: exp.Interval) -> str: 3206 unit = self.sql(expression, "unit") 3207 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3208 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3209 unit = f" {unit}" if unit else "" 3210 3211 if self.SINGLE_STRING_INTERVAL: 3212 this = expression.this.name if expression.this else "" 3213 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3214 3215 this = self.sql(expression, "this") 3216 if this: 3217 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3218 this = f" {this}" if unwrapped else f" ({this})" 3219 3220 return f"INTERVAL{this}{unit}" 3221 3222 def return_sql(self, expression: exp.Return) -> str: 3223 return f"RETURN {self.sql(expression, 'this')}" 3224 3225 def reference_sql(self, expression: exp.Reference) -> str: 3226 this = self.sql(expression, "this") 3227 expressions = self.expressions(expression, flat=True) 3228 expressions = f"({expressions})" if expressions else "" 3229 options = self.expressions(expression, key="options", flat=True, sep=" ") 3230 options = f" {options}" if options else "" 3231 return f"REFERENCES {this}{expressions}{options}" 3232 3233 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3234 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3235 parent = expression.parent 3236 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3237 return self.func( 3238 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3239 ) 3240 3241 def paren_sql(self, expression: exp.Paren) -> str: 3242 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3243 return f"({sql}{self.seg(')', sep='')}" 3244 3245 def neg_sql(self, expression: exp.Neg) -> str: 3246 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3247 this_sql = self.sql(expression, "this") 3248 sep = " " if this_sql[0] == "-" else "" 3249 return f"-{sep}{this_sql}" 3250 3251 def not_sql(self, expression: exp.Not) -> str: 3252 return f"NOT {self.sql(expression, 'this')}" 3253 3254 def alias_sql(self, expression: exp.Alias) -> str: 3255 alias = self.sql(expression, "alias") 3256 alias = f" AS {alias}" if alias else "" 3257 return f"{self.sql(expression, 'this')}{alias}" 3258 3259 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3260 alias = expression.args["alias"] 3261 3262 parent = expression.parent 3263 pivot = parent and parent.parent 3264 3265 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3266 identifier_alias = isinstance(alias, exp.Identifier) 3267 literal_alias = isinstance(alias, exp.Literal) 3268 3269 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3270 alias.replace(exp.Literal.string(alias.output_name)) 3271 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3272 alias.replace(exp.to_identifier(alias.output_name)) 3273 3274 return self.alias_sql(expression) 3275 3276 def aliases_sql(self, expression: exp.Aliases) -> str: 3277 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3278 3279 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3280 this = self.sql(expression, "this") 3281 index = self.sql(expression, "expression") 3282 return f"{this} AT {index}" 3283 3284 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3285 this = self.sql(expression, "this") 3286 zone = self.sql(expression, "zone") 3287 return f"{this} AT TIME ZONE {zone}" 3288 3289 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3290 this = self.sql(expression, "this") 3291 zone = self.sql(expression, "zone") 3292 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3293 3294 def add_sql(self, expression: exp.Add) -> str: 3295 return self.binary(expression, "+") 3296 3297 def and_sql( 3298 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3299 ) -> str: 3300 return self.connector_sql(expression, "AND", stack) 3301 3302 def or_sql( 3303 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3304 ) -> str: 3305 return self.connector_sql(expression, "OR", stack) 3306 3307 def xor_sql( 3308 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3309 ) -> str: 3310 return self.connector_sql(expression, "XOR", stack) 3311 3312 def connector_sql( 3313 self, 3314 expression: exp.Connector, 3315 op: str, 3316 stack: t.Optional[t.List[str | exp.Expression]] = None, 3317 ) -> str: 3318 if stack is not None: 3319 if expression.expressions: 3320 stack.append(self.expressions(expression, sep=f" {op} ")) 3321 else: 3322 stack.append(expression.right) 3323 if expression.comments and self.comments: 3324 for comment in expression.comments: 3325 if comment: 3326 op += f" /*{self.sanitize_comment(comment)}*/" 3327 stack.extend((op, expression.left)) 3328 return op 3329 3330 stack = [expression] 3331 sqls: t.List[str] = [] 3332 ops = set() 3333 3334 while stack: 3335 node = stack.pop() 3336 if isinstance(node, exp.Connector): 3337 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3338 else: 3339 sql = self.sql(node) 3340 if sqls and sqls[-1] in ops: 3341 sqls[-1] += f" {sql}" 3342 else: 3343 sqls.append(sql) 3344 3345 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3346 return sep.join(sqls) 3347 3348 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3349 return self.binary(expression, "&") 3350 3351 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3352 return self.binary(expression, "<<") 3353 3354 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3355 return f"~{self.sql(expression, 'this')}" 3356 3357 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3358 return self.binary(expression, "|") 3359 3360 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3361 return self.binary(expression, ">>") 3362 3363 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3364 return self.binary(expression, "^") 3365 3366 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3367 format_sql = self.sql(expression, "format") 3368 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3369 to_sql = self.sql(expression, "to") 3370 to_sql = f" {to_sql}" if to_sql else "" 3371 action = self.sql(expression, "action") 3372 action = f" {action}" if action else "" 3373 default = self.sql(expression, "default") 3374 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3375 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3376 3377 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3378 zone = self.sql(expression, "this") 3379 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3380 3381 def collate_sql(self, expression: exp.Collate) -> str: 3382 if self.COLLATE_IS_FUNC: 3383 return self.function_fallback_sql(expression) 3384 return self.binary(expression, "COLLATE") 3385 3386 def command_sql(self, expression: exp.Command) -> str: 3387 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3388 3389 def comment_sql(self, expression: exp.Comment) -> str: 3390 this = self.sql(expression, "this") 3391 kind = expression.args["kind"] 3392 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3393 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3394 expression_sql = self.sql(expression, "expression") 3395 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3396 3397 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3398 this = self.sql(expression, "this") 3399 delete = " DELETE" if expression.args.get("delete") else "" 3400 recompress = self.sql(expression, "recompress") 3401 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3402 to_disk = self.sql(expression, "to_disk") 3403 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3404 to_volume = self.sql(expression, "to_volume") 3405 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3406 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3407 3408 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3409 where = self.sql(expression, "where") 3410 group = self.sql(expression, "group") 3411 aggregates = self.expressions(expression, key="aggregates") 3412 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3413 3414 if not (where or group or aggregates) and len(expression.expressions) == 1: 3415 return f"TTL {self.expressions(expression, flat=True)}" 3416 3417 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3418 3419 def transaction_sql(self, expression: exp.Transaction) -> str: 3420 return "BEGIN" 3421 3422 def commit_sql(self, expression: exp.Commit) -> str: 3423 chain = expression.args.get("chain") 3424 if chain is not None: 3425 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3426 3427 return f"COMMIT{chain or ''}" 3428 3429 def rollback_sql(self, expression: exp.Rollback) -> str: 3430 savepoint = expression.args.get("savepoint") 3431 savepoint = f" TO {savepoint}" if savepoint else "" 3432 return f"ROLLBACK{savepoint}" 3433 3434 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3435 this = self.sql(expression, "this") 3436 3437 dtype = self.sql(expression, "dtype") 3438 if dtype: 3439 collate = self.sql(expression, "collate") 3440 collate = f" COLLATE {collate}" if collate else "" 3441 using = self.sql(expression, "using") 3442 using = f" USING {using}" if using else "" 3443 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3444 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3445 3446 default = self.sql(expression, "default") 3447 if default: 3448 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3449 3450 comment = self.sql(expression, "comment") 3451 if comment: 3452 return f"ALTER COLUMN {this} COMMENT {comment}" 3453 3454 visible = expression.args.get("visible") 3455 if visible: 3456 return f"ALTER COLUMN {this} SET {visible}" 3457 3458 allow_null = expression.args.get("allow_null") 3459 drop = expression.args.get("drop") 3460 3461 if not drop and not allow_null: 3462 self.unsupported("Unsupported ALTER COLUMN syntax") 3463 3464 if allow_null is not None: 3465 keyword = "DROP" if drop else "SET" 3466 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3467 3468 return f"ALTER COLUMN {this} DROP DEFAULT" 3469 3470 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3471 this = self.sql(expression, "this") 3472 3473 visible = expression.args.get("visible") 3474 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3475 3476 return f"ALTER INDEX {this} {visible_sql}" 3477 3478 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3479 this = self.sql(expression, "this") 3480 if not isinstance(expression.this, exp.Var): 3481 this = f"KEY DISTKEY {this}" 3482 return f"ALTER DISTSTYLE {this}" 3483 3484 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3485 compound = " COMPOUND" if expression.args.get("compound") else "" 3486 this = self.sql(expression, "this") 3487 expressions = self.expressions(expression, flat=True) 3488 expressions = f"({expressions})" if expressions else "" 3489 return f"ALTER{compound} SORTKEY {this or expressions}" 3490 3491 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3492 if not self.RENAME_TABLE_WITH_DB: 3493 # Remove db from tables 3494 expression = expression.transform( 3495 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3496 ).assert_is(exp.AlterRename) 3497 this = self.sql(expression, "this") 3498 to_kw = " TO" if include_to else "" 3499 return f"RENAME{to_kw} {this}" 3500 3501 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3502 exists = " IF EXISTS" if expression.args.get("exists") else "" 3503 old_column = self.sql(expression, "this") 3504 new_column = self.sql(expression, "to") 3505 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3506 3507 def alterset_sql(self, expression: exp.AlterSet) -> str: 3508 exprs = self.expressions(expression, flat=True) 3509 if self.ALTER_SET_WRAPPED: 3510 exprs = f"({exprs})" 3511 3512 return f"SET {exprs}" 3513 3514 def alter_sql(self, expression: exp.Alter) -> str: 3515 actions = expression.args["actions"] 3516 3517 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3518 actions[0], exp.ColumnDef 3519 ): 3520 actions_sql = self.expressions(expression, key="actions", flat=True) 3521 actions_sql = f"ADD {actions_sql}" 3522 else: 3523 actions_list = [] 3524 for action in actions: 3525 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3526 action_sql = self.add_column_sql(action) 3527 else: 3528 action_sql = self.sql(action) 3529 if isinstance(action, exp.Query): 3530 action_sql = f"AS {action_sql}" 3531 3532 actions_list.append(action_sql) 3533 3534 actions_sql = self.format_args(*actions_list).lstrip("\n") 3535 3536 exists = " IF EXISTS" if expression.args.get("exists") else "" 3537 on_cluster = self.sql(expression, "cluster") 3538 on_cluster = f" {on_cluster}" if on_cluster else "" 3539 only = " ONLY" if expression.args.get("only") else "" 3540 options = self.expressions(expression, key="options") 3541 options = f", {options}" if options else "" 3542 kind = self.sql(expression, "kind") 3543 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3544 3545 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}" 3546 3547 def add_column_sql(self, expression: exp.Expression) -> str: 3548 sql = self.sql(expression) 3549 if isinstance(expression, exp.Schema): 3550 column_text = " COLUMNS" 3551 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3552 column_text = " COLUMN" 3553 else: 3554 column_text = "" 3555 3556 return f"ADD{column_text} {sql}" 3557 3558 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3559 expressions = self.expressions(expression) 3560 exists = " IF EXISTS " if expression.args.get("exists") else " " 3561 return f"DROP{exists}{expressions}" 3562 3563 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3564 return f"ADD {self.expressions(expression, indent=False)}" 3565 3566 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3567 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3568 location = self.sql(expression, "location") 3569 location = f" {location}" if location else "" 3570 return f"ADD {exists}{self.sql(expression.this)}{location}" 3571 3572 def distinct_sql(self, expression: exp.Distinct) -> str: 3573 this = self.expressions(expression, flat=True) 3574 3575 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3576 case = exp.case() 3577 for arg in expression.expressions: 3578 case = case.when(arg.is_(exp.null()), exp.null()) 3579 this = self.sql(case.else_(f"({this})")) 3580 3581 this = f" {this}" if this else "" 3582 3583 on = self.sql(expression, "on") 3584 on = f" ON {on}" if on else "" 3585 return f"DISTINCT{this}{on}" 3586 3587 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3588 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3589 3590 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3591 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3592 3593 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3594 this_sql = self.sql(expression, "this") 3595 expression_sql = self.sql(expression, "expression") 3596 kind = "MAX" if expression.args.get("max") else "MIN" 3597 return f"{this_sql} HAVING {kind} {expression_sql}" 3598 3599 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3600 return self.sql( 3601 exp.Cast( 3602 this=exp.Div(this=expression.this, expression=expression.expression), 3603 to=exp.DataType(this=exp.DataType.Type.INT), 3604 ) 3605 ) 3606 3607 def dpipe_sql(self, expression: exp.DPipe) -> str: 3608 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3609 return self.func( 3610 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3611 ) 3612 return self.binary(expression, "||") 3613 3614 def div_sql(self, expression: exp.Div) -> str: 3615 l, r = expression.left, expression.right 3616 3617 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3618 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3619 3620 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3621 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3622 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3623 3624 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3625 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3626 return self.sql( 3627 exp.cast( 3628 l / r, 3629 to=exp.DataType.Type.BIGINT, 3630 ) 3631 ) 3632 3633 return self.binary(expression, "/") 3634 3635 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3636 n = exp._wrap(expression.this, exp.Binary) 3637 d = exp._wrap(expression.expression, exp.Binary) 3638 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3639 3640 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3641 return self.binary(expression, "OVERLAPS") 3642 3643 def distance_sql(self, expression: exp.Distance) -> str: 3644 return self.binary(expression, "<->") 3645 3646 def dot_sql(self, expression: exp.Dot) -> str: 3647 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3648 3649 def eq_sql(self, expression: exp.EQ) -> str: 3650 return self.binary(expression, "=") 3651 3652 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3653 return self.binary(expression, ":=") 3654 3655 def escape_sql(self, expression: exp.Escape) -> str: 3656 return self.binary(expression, "ESCAPE") 3657 3658 def glob_sql(self, expression: exp.Glob) -> str: 3659 return self.binary(expression, "GLOB") 3660 3661 def gt_sql(self, expression: exp.GT) -> str: 3662 return self.binary(expression, ">") 3663 3664 def gte_sql(self, expression: exp.GTE) -> str: 3665 return self.binary(expression, ">=") 3666 3667 def is_sql(self, expression: exp.Is) -> str: 3668 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3669 return self.sql( 3670 expression.this if expression.expression.this else exp.not_(expression.this) 3671 ) 3672 return self.binary(expression, "IS") 3673 3674 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3675 this = expression.this 3676 rhs = expression.expression 3677 3678 if isinstance(expression, exp.Like): 3679 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3680 op = "LIKE" 3681 else: 3682 exp_class = exp.ILike 3683 op = "ILIKE" 3684 3685 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3686 exprs = rhs.this.unnest() 3687 3688 if isinstance(exprs, exp.Tuple): 3689 exprs = exprs.expressions 3690 3691 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3692 3693 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3694 for expr in exprs[1:]: 3695 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3696 3697 return self.sql(like_expr) 3698 3699 return self.binary(expression, op) 3700 3701 def like_sql(self, expression: exp.Like) -> str: 3702 return self._like_sql(expression) 3703 3704 def ilike_sql(self, expression: exp.ILike) -> str: 3705 return self._like_sql(expression) 3706 3707 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3708 return self.binary(expression, "SIMILAR TO") 3709 3710 def lt_sql(self, expression: exp.LT) -> str: 3711 return self.binary(expression, "<") 3712 3713 def lte_sql(self, expression: exp.LTE) -> str: 3714 return self.binary(expression, "<=") 3715 3716 def mod_sql(self, expression: exp.Mod) -> str: 3717 return self.binary(expression, "%") 3718 3719 def mul_sql(self, expression: exp.Mul) -> str: 3720 return self.binary(expression, "*") 3721 3722 def neq_sql(self, expression: exp.NEQ) -> str: 3723 return self.binary(expression, "<>") 3724 3725 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3726 return self.binary(expression, "IS NOT DISTINCT FROM") 3727 3728 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3729 return self.binary(expression, "IS DISTINCT FROM") 3730 3731 def slice_sql(self, expression: exp.Slice) -> str: 3732 return self.binary(expression, ":") 3733 3734 def sub_sql(self, expression: exp.Sub) -> str: 3735 return self.binary(expression, "-") 3736 3737 def trycast_sql(self, expression: exp.TryCast) -> str: 3738 return self.cast_sql(expression, safe_prefix="TRY_") 3739 3740 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3741 return self.cast_sql(expression) 3742 3743 def try_sql(self, expression: exp.Try) -> str: 3744 if not self.TRY_SUPPORTED: 3745 self.unsupported("Unsupported TRY function") 3746 return self.sql(expression, "this") 3747 3748 return self.func("TRY", expression.this) 3749 3750 def log_sql(self, expression: exp.Log) -> str: 3751 this = expression.this 3752 expr = expression.expression 3753 3754 if self.dialect.LOG_BASE_FIRST is False: 3755 this, expr = expr, this 3756 elif self.dialect.LOG_BASE_FIRST is None and expr: 3757 if this.name in ("2", "10"): 3758 return self.func(f"LOG{this.name}", expr) 3759 3760 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3761 3762 return self.func("LOG", this, expr) 3763 3764 def use_sql(self, expression: exp.Use) -> str: 3765 kind = self.sql(expression, "kind") 3766 kind = f" {kind}" if kind else "" 3767 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3768 this = f" {this}" if this else "" 3769 return f"USE{kind}{this}" 3770 3771 def binary(self, expression: exp.Binary, op: str) -> str: 3772 sqls: t.List[str] = [] 3773 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3774 binary_type = type(expression) 3775 3776 while stack: 3777 node = stack.pop() 3778 3779 if type(node) is binary_type: 3780 op_func = node.args.get("operator") 3781 if op_func: 3782 op = f"OPERATOR({self.sql(op_func)})" 3783 3784 stack.append(node.right) 3785 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3786 stack.append(node.left) 3787 else: 3788 sqls.append(self.sql(node)) 3789 3790 return "".join(sqls) 3791 3792 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3793 to_clause = self.sql(expression, "to") 3794 if to_clause: 3795 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3796 3797 return self.function_fallback_sql(expression) 3798 3799 def function_fallback_sql(self, expression: exp.Func) -> str: 3800 args = [] 3801 3802 for key in expression.arg_types: 3803 arg_value = expression.args.get(key) 3804 3805 if isinstance(arg_value, list): 3806 for value in arg_value: 3807 args.append(value) 3808 elif arg_value is not None: 3809 args.append(arg_value) 3810 3811 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3812 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3813 else: 3814 name = expression.sql_name() 3815 3816 return self.func(name, *args) 3817 3818 def func( 3819 self, 3820 name: str, 3821 *args: t.Optional[exp.Expression | str], 3822 prefix: str = "(", 3823 suffix: str = ")", 3824 normalize: bool = True, 3825 ) -> str: 3826 name = self.normalize_func(name) if normalize else name 3827 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3828 3829 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3830 arg_sqls = tuple( 3831 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3832 ) 3833 if self.pretty and self.too_wide(arg_sqls): 3834 return self.indent( 3835 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3836 ) 3837 return sep.join(arg_sqls) 3838 3839 def too_wide(self, args: t.Iterable) -> bool: 3840 return sum(len(arg) for arg in args) > self.max_text_width 3841 3842 def format_time( 3843 self, 3844 expression: exp.Expression, 3845 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3846 inverse_time_trie: t.Optional[t.Dict] = None, 3847 ) -> t.Optional[str]: 3848 return format_time( 3849 self.sql(expression, "format"), 3850 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3851 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3852 ) 3853 3854 def expressions( 3855 self, 3856 expression: t.Optional[exp.Expression] = None, 3857 key: t.Optional[str] = None, 3858 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3859 flat: bool = False, 3860 indent: bool = True, 3861 skip_first: bool = False, 3862 skip_last: bool = False, 3863 sep: str = ", ", 3864 prefix: str = "", 3865 dynamic: bool = False, 3866 new_line: bool = False, 3867 ) -> str: 3868 expressions = expression.args.get(key or "expressions") if expression else sqls 3869 3870 if not expressions: 3871 return "" 3872 3873 if flat: 3874 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3875 3876 num_sqls = len(expressions) 3877 result_sqls = [] 3878 3879 for i, e in enumerate(expressions): 3880 sql = self.sql(e, comment=False) 3881 if not sql: 3882 continue 3883 3884 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3885 3886 if self.pretty: 3887 if self.leading_comma: 3888 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3889 else: 3890 result_sqls.append( 3891 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3892 ) 3893 else: 3894 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3895 3896 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3897 if new_line: 3898 result_sqls.insert(0, "") 3899 result_sqls.append("") 3900 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3901 else: 3902 result_sql = "".join(result_sqls) 3903 3904 return ( 3905 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3906 if indent 3907 else result_sql 3908 ) 3909 3910 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3911 flat = flat or isinstance(expression.parent, exp.Properties) 3912 expressions_sql = self.expressions(expression, flat=flat) 3913 if flat: 3914 return f"{op} {expressions_sql}" 3915 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3916 3917 def naked_property(self, expression: exp.Property) -> str: 3918 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3919 if not property_name: 3920 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3921 return f"{property_name} {self.sql(expression, 'this')}" 3922 3923 def tag_sql(self, expression: exp.Tag) -> str: 3924 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3925 3926 def token_sql(self, token_type: TokenType) -> str: 3927 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3928 3929 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3930 this = self.sql(expression, "this") 3931 expressions = self.no_identify(self.expressions, expression) 3932 expressions = ( 3933 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3934 ) 3935 return f"{this}{expressions}" if expressions.strip() != "" else this 3936 3937 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3938 this = self.sql(expression, "this") 3939 expressions = self.expressions(expression, flat=True) 3940 return f"{this}({expressions})" 3941 3942 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3943 return self.binary(expression, "=>") 3944 3945 def when_sql(self, expression: exp.When) -> str: 3946 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3947 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3948 condition = self.sql(expression, "condition") 3949 condition = f" AND {condition}" if condition else "" 3950 3951 then_expression = expression.args.get("then") 3952 if isinstance(then_expression, exp.Insert): 3953 this = self.sql(then_expression, "this") 3954 this = f"INSERT {this}" if this else "INSERT" 3955 then = self.sql(then_expression, "expression") 3956 then = f"{this} VALUES {then}" if then else this 3957 elif isinstance(then_expression, exp.Update): 3958 if isinstance(then_expression.args.get("expressions"), exp.Star): 3959 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3960 else: 3961 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3962 else: 3963 then = self.sql(then_expression) 3964 return f"WHEN {matched}{source}{condition} THEN {then}" 3965 3966 def whens_sql(self, expression: exp.Whens) -> str: 3967 return self.expressions(expression, sep=" ", indent=False) 3968 3969 def merge_sql(self, expression: exp.Merge) -> str: 3970 table = expression.this 3971 table_alias = "" 3972 3973 hints = table.args.get("hints") 3974 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3975 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3976 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3977 3978 this = self.sql(table) 3979 using = f"USING {self.sql(expression, 'using')}" 3980 on = f"ON {self.sql(expression, 'on')}" 3981 whens = self.sql(expression, "whens") 3982 3983 returning = self.sql(expression, "returning") 3984 if returning: 3985 whens = f"{whens}{returning}" 3986 3987 sep = self.sep() 3988 3989 return self.prepend_ctes( 3990 expression, 3991 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3992 ) 3993 3994 @unsupported_args("format") 3995 def tochar_sql(self, expression: exp.ToChar) -> str: 3996 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3997 3998 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3999 if not self.SUPPORTS_TO_NUMBER: 4000 self.unsupported("Unsupported TO_NUMBER function") 4001 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4002 4003 fmt = expression.args.get("format") 4004 if not fmt: 4005 self.unsupported("Conversion format is required for TO_NUMBER") 4006 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4007 4008 return self.func("TO_NUMBER", expression.this, fmt) 4009 4010 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4011 this = self.sql(expression, "this") 4012 kind = self.sql(expression, "kind") 4013 settings_sql = self.expressions(expression, key="settings", sep=" ") 4014 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4015 return f"{this}({kind}{args})" 4016 4017 def dictrange_sql(self, expression: exp.DictRange) -> str: 4018 this = self.sql(expression, "this") 4019 max = self.sql(expression, "max") 4020 min = self.sql(expression, "min") 4021 return f"{this}(MIN {min} MAX {max})" 4022 4023 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4024 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4025 4026 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4027 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4028 4029 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4030 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 4031 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 4032 4033 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4034 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4035 expressions = self.expressions(expression, flat=True) 4036 expressions = f" {self.wrap(expressions)}" if expressions else "" 4037 buckets = self.sql(expression, "buckets") 4038 kind = self.sql(expression, "kind") 4039 buckets = f" BUCKETS {buckets}" if buckets else "" 4040 order = self.sql(expression, "order") 4041 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4042 4043 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4044 return "" 4045 4046 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4047 expressions = self.expressions(expression, key="expressions", flat=True) 4048 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4049 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4050 buckets = self.sql(expression, "buckets") 4051 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4052 4053 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4054 this = self.sql(expression, "this") 4055 having = self.sql(expression, "having") 4056 4057 if having: 4058 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4059 4060 return self.func("ANY_VALUE", this) 4061 4062 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4063 transform = self.func("TRANSFORM", *expression.expressions) 4064 row_format_before = self.sql(expression, "row_format_before") 4065 row_format_before = f" {row_format_before}" if row_format_before else "" 4066 record_writer = self.sql(expression, "record_writer") 4067 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4068 using = f" USING {self.sql(expression, 'command_script')}" 4069 schema = self.sql(expression, "schema") 4070 schema = f" AS {schema}" if schema else "" 4071 row_format_after = self.sql(expression, "row_format_after") 4072 row_format_after = f" {row_format_after}" if row_format_after else "" 4073 record_reader = self.sql(expression, "record_reader") 4074 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4075 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4076 4077 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4078 key_block_size = self.sql(expression, "key_block_size") 4079 if key_block_size: 4080 return f"KEY_BLOCK_SIZE = {key_block_size}" 4081 4082 using = self.sql(expression, "using") 4083 if using: 4084 return f"USING {using}" 4085 4086 parser = self.sql(expression, "parser") 4087 if parser: 4088 return f"WITH PARSER {parser}" 4089 4090 comment = self.sql(expression, "comment") 4091 if comment: 4092 return f"COMMENT {comment}" 4093 4094 visible = expression.args.get("visible") 4095 if visible is not None: 4096 return "VISIBLE" if visible else "INVISIBLE" 4097 4098 engine_attr = self.sql(expression, "engine_attr") 4099 if engine_attr: 4100 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4101 4102 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4103 if secondary_engine_attr: 4104 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4105 4106 self.unsupported("Unsupported index constraint option.") 4107 return "" 4108 4109 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4110 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4111 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4112 4113 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4114 kind = self.sql(expression, "kind") 4115 kind = f"{kind} INDEX" if kind else "INDEX" 4116 this = self.sql(expression, "this") 4117 this = f" {this}" if this else "" 4118 index_type = self.sql(expression, "index_type") 4119 index_type = f" USING {index_type}" if index_type else "" 4120 expressions = self.expressions(expression, flat=True) 4121 expressions = f" ({expressions})" if expressions else "" 4122 options = self.expressions(expression, key="options", sep=" ") 4123 options = f" {options}" if options else "" 4124 return f"{kind}{this}{index_type}{expressions}{options}" 4125 4126 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4127 if self.NVL2_SUPPORTED: 4128 return self.function_fallback_sql(expression) 4129 4130 case = exp.Case().when( 4131 expression.this.is_(exp.null()).not_(copy=False), 4132 expression.args["true"], 4133 copy=False, 4134 ) 4135 else_cond = expression.args.get("false") 4136 if else_cond: 4137 case.else_(else_cond, copy=False) 4138 4139 return self.sql(case) 4140 4141 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4142 this = self.sql(expression, "this") 4143 expr = self.sql(expression, "expression") 4144 iterator = self.sql(expression, "iterator") 4145 condition = self.sql(expression, "condition") 4146 condition = f" IF {condition}" if condition else "" 4147 return f"{this} FOR {expr} IN {iterator}{condition}" 4148 4149 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4150 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4151 4152 def opclass_sql(self, expression: exp.Opclass) -> str: 4153 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4154 4155 def predict_sql(self, expression: exp.Predict) -> str: 4156 model = self.sql(expression, "this") 4157 model = f"MODEL {model}" 4158 table = self.sql(expression, "expression") 4159 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4160 parameters = self.sql(expression, "params_struct") 4161 return self.func("PREDICT", model, table, parameters or None) 4162 4163 def forin_sql(self, expression: exp.ForIn) -> str: 4164 this = self.sql(expression, "this") 4165 expression_sql = self.sql(expression, "expression") 4166 return f"FOR {this} DO {expression_sql}" 4167 4168 def refresh_sql(self, expression: exp.Refresh) -> str: 4169 this = self.sql(expression, "this") 4170 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4171 return f"REFRESH {table}{this}" 4172 4173 def toarray_sql(self, expression: exp.ToArray) -> str: 4174 arg = expression.this 4175 if not arg.type: 4176 from sqlglot.optimizer.annotate_types import annotate_types 4177 4178 arg = annotate_types(arg, dialect=self.dialect) 4179 4180 if arg.is_type(exp.DataType.Type.ARRAY): 4181 return self.sql(arg) 4182 4183 cond_for_null = arg.is_(exp.null()) 4184 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4185 4186 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4187 this = expression.this 4188 time_format = self.format_time(expression) 4189 4190 if time_format: 4191 return self.sql( 4192 exp.cast( 4193 exp.StrToTime(this=this, format=expression.args["format"]), 4194 exp.DataType.Type.TIME, 4195 ) 4196 ) 4197 4198 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4199 return self.sql(this) 4200 4201 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4202 4203 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4204 this = expression.this 4205 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4206 return self.sql(this) 4207 4208 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4209 4210 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4211 this = expression.this 4212 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4213 return self.sql(this) 4214 4215 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4216 4217 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4218 this = expression.this 4219 time_format = self.format_time(expression) 4220 4221 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4222 return self.sql( 4223 exp.cast( 4224 exp.StrToTime(this=this, format=expression.args["format"]), 4225 exp.DataType.Type.DATE, 4226 ) 4227 ) 4228 4229 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4230 return self.sql(this) 4231 4232 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4233 4234 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4235 return self.sql( 4236 exp.func( 4237 "DATEDIFF", 4238 expression.this, 4239 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4240 "day", 4241 ) 4242 ) 4243 4244 def lastday_sql(self, expression: exp.LastDay) -> str: 4245 if self.LAST_DAY_SUPPORTS_DATE_PART: 4246 return self.function_fallback_sql(expression) 4247 4248 unit = expression.text("unit") 4249 if unit and unit != "MONTH": 4250 self.unsupported("Date parts are not supported in LAST_DAY.") 4251 4252 return self.func("LAST_DAY", expression.this) 4253 4254 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4255 from sqlglot.dialects.dialect import unit_to_str 4256 4257 return self.func( 4258 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4259 ) 4260 4261 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4262 if self.CAN_IMPLEMENT_ARRAY_ANY: 4263 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4264 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4265 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4266 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4267 4268 from sqlglot.dialects import Dialect 4269 4270 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4271 if self.dialect.__class__ != Dialect: 4272 self.unsupported("ARRAY_ANY is unsupported") 4273 4274 return self.function_fallback_sql(expression) 4275 4276 def struct_sql(self, expression: exp.Struct) -> str: 4277 expression.set( 4278 "expressions", 4279 [ 4280 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4281 if isinstance(e, exp.PropertyEQ) 4282 else e 4283 for e in expression.expressions 4284 ], 4285 ) 4286 4287 return self.function_fallback_sql(expression) 4288 4289 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4290 low = self.sql(expression, "this") 4291 high = self.sql(expression, "expression") 4292 4293 return f"{low} TO {high}" 4294 4295 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4296 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4297 tables = f" {self.expressions(expression)}" 4298 4299 exists = " IF EXISTS" if expression.args.get("exists") else "" 4300 4301 on_cluster = self.sql(expression, "cluster") 4302 on_cluster = f" {on_cluster}" if on_cluster else "" 4303 4304 identity = self.sql(expression, "identity") 4305 identity = f" {identity} IDENTITY" if identity else "" 4306 4307 option = self.sql(expression, "option") 4308 option = f" {option}" if option else "" 4309 4310 partition = self.sql(expression, "partition") 4311 partition = f" {partition}" if partition else "" 4312 4313 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4314 4315 # This transpiles T-SQL's CONVERT function 4316 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4317 def convert_sql(self, expression: exp.Convert) -> str: 4318 to = expression.this 4319 value = expression.expression 4320 style = expression.args.get("style") 4321 safe = expression.args.get("safe") 4322 strict = expression.args.get("strict") 4323 4324 if not to or not value: 4325 return "" 4326 4327 # Retrieve length of datatype and override to default if not specified 4328 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4329 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4330 4331 transformed: t.Optional[exp.Expression] = None 4332 cast = exp.Cast if strict else exp.TryCast 4333 4334 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4335 if isinstance(style, exp.Literal) and style.is_int: 4336 from sqlglot.dialects.tsql import TSQL 4337 4338 style_value = style.name 4339 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4340 if not converted_style: 4341 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4342 4343 fmt = exp.Literal.string(converted_style) 4344 4345 if to.this == exp.DataType.Type.DATE: 4346 transformed = exp.StrToDate(this=value, format=fmt) 4347 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4348 transformed = exp.StrToTime(this=value, format=fmt) 4349 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4350 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4351 elif to.this == exp.DataType.Type.TEXT: 4352 transformed = exp.TimeToStr(this=value, format=fmt) 4353 4354 if not transformed: 4355 transformed = cast(this=value, to=to, safe=safe) 4356 4357 return self.sql(transformed) 4358 4359 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4360 this = expression.this 4361 if isinstance(this, exp.JSONPathWildcard): 4362 this = self.json_path_part(this) 4363 return f".{this}" if this else "" 4364 4365 if exp.SAFE_IDENTIFIER_RE.match(this): 4366 return f".{this}" 4367 4368 this = self.json_path_part(this) 4369 return ( 4370 f"[{this}]" 4371 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4372 else f".{this}" 4373 ) 4374 4375 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4376 this = self.json_path_part(expression.this) 4377 return f"[{this}]" if this else "" 4378 4379 def _simplify_unless_literal(self, expression: E) -> E: 4380 if not isinstance(expression, exp.Literal): 4381 from sqlglot.optimizer.simplify import simplify 4382 4383 expression = simplify(expression, dialect=self.dialect) 4384 4385 return expression 4386 4387 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4388 this = expression.this 4389 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4390 self.unsupported( 4391 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4392 ) 4393 return self.sql(this) 4394 4395 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4396 # The first modifier here will be the one closest to the AggFunc's arg 4397 mods = sorted( 4398 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4399 key=lambda x: 0 4400 if isinstance(x, exp.HavingMax) 4401 else (1 if isinstance(x, exp.Order) else 2), 4402 ) 4403 4404 if mods: 4405 mod = mods[0] 4406 this = expression.__class__(this=mod.this.copy()) 4407 this.meta["inline"] = True 4408 mod.this.replace(this) 4409 return self.sql(expression.this) 4410 4411 agg_func = expression.find(exp.AggFunc) 4412 4413 if agg_func: 4414 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4415 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4416 4417 return f"{self.sql(expression, 'this')} {text}" 4418 4419 def _replace_line_breaks(self, string: str) -> str: 4420 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4421 if self.pretty: 4422 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4423 return string 4424 4425 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4426 option = self.sql(expression, "this") 4427 4428 if expression.expressions: 4429 upper = option.upper() 4430 4431 # Snowflake FILE_FORMAT options are separated by whitespace 4432 sep = " " if upper == "FILE_FORMAT" else ", " 4433 4434 # Databricks copy/format options do not set their list of values with EQ 4435 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4436 values = self.expressions(expression, flat=True, sep=sep) 4437 return f"{option}{op}({values})" 4438 4439 value = self.sql(expression, "expression") 4440 4441 if not value: 4442 return option 4443 4444 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4445 4446 return f"{option}{op}{value}" 4447 4448 def credentials_sql(self, expression: exp.Credentials) -> str: 4449 cred_expr = expression.args.get("credentials") 4450 if isinstance(cred_expr, exp.Literal): 4451 # Redshift case: CREDENTIALS <string> 4452 credentials = self.sql(expression, "credentials") 4453 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4454 else: 4455 # Snowflake case: CREDENTIALS = (...) 4456 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4457 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4458 4459 storage = self.sql(expression, "storage") 4460 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4461 4462 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4463 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4464 4465 iam_role = self.sql(expression, "iam_role") 4466 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4467 4468 region = self.sql(expression, "region") 4469 region = f" REGION {region}" if region else "" 4470 4471 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4472 4473 def copy_sql(self, expression: exp.Copy) -> str: 4474 this = self.sql(expression, "this") 4475 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4476 4477 credentials = self.sql(expression, "credentials") 4478 credentials = self.seg(credentials) if credentials else "" 4479 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4480 files = self.expressions(expression, key="files", flat=True) 4481 4482 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4483 params = self.expressions( 4484 expression, 4485 key="params", 4486 sep=sep, 4487 new_line=True, 4488 skip_last=True, 4489 skip_first=True, 4490 indent=self.COPY_PARAMS_ARE_WRAPPED, 4491 ) 4492 4493 if params: 4494 if self.COPY_PARAMS_ARE_WRAPPED: 4495 params = f" WITH ({params})" 4496 elif not self.pretty: 4497 params = f" {params}" 4498 4499 return f"COPY{this}{kind} {files}{credentials}{params}" 4500 4501 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4502 return "" 4503 4504 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4505 on_sql = "ON" if expression.args.get("on") else "OFF" 4506 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4507 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4508 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4509 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4510 4511 if filter_col or retention_period: 4512 on_sql = self.func("ON", filter_col, retention_period) 4513 4514 return f"DATA_DELETION={on_sql}" 4515 4516 def maskingpolicycolumnconstraint_sql( 4517 self, expression: exp.MaskingPolicyColumnConstraint 4518 ) -> str: 4519 this = self.sql(expression, "this") 4520 expressions = self.expressions(expression, flat=True) 4521 expressions = f" USING ({expressions})" if expressions else "" 4522 return f"MASKING POLICY {this}{expressions}" 4523 4524 def gapfill_sql(self, expression: exp.GapFill) -> str: 4525 this = self.sql(expression, "this") 4526 this = f"TABLE {this}" 4527 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4528 4529 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4530 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4531 4532 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4533 this = self.sql(expression, "this") 4534 expr = expression.expression 4535 4536 if isinstance(expr, exp.Func): 4537 # T-SQL's CLR functions are case sensitive 4538 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4539 else: 4540 expr = self.sql(expression, "expression") 4541 4542 return self.scope_resolution(expr, this) 4543 4544 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4545 if self.PARSE_JSON_NAME is None: 4546 return self.sql(expression.this) 4547 4548 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4549 4550 def rand_sql(self, expression: exp.Rand) -> str: 4551 lower = self.sql(expression, "lower") 4552 upper = self.sql(expression, "upper") 4553 4554 if lower and upper: 4555 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4556 return self.func("RAND", expression.this) 4557 4558 def changes_sql(self, expression: exp.Changes) -> str: 4559 information = self.sql(expression, "information") 4560 information = f"INFORMATION => {information}" 4561 at_before = self.sql(expression, "at_before") 4562 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4563 end = self.sql(expression, "end") 4564 end = f"{self.seg('')}{end}" if end else "" 4565 4566 return f"CHANGES ({information}){at_before}{end}" 4567 4568 def pad_sql(self, expression: exp.Pad) -> str: 4569 prefix = "L" if expression.args.get("is_left") else "R" 4570 4571 fill_pattern = self.sql(expression, "fill_pattern") or None 4572 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4573 fill_pattern = "' '" 4574 4575 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4576 4577 def summarize_sql(self, expression: exp.Summarize) -> str: 4578 table = " TABLE" if expression.args.get("table") else "" 4579 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4580 4581 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4582 generate_series = exp.GenerateSeries(**expression.args) 4583 4584 parent = expression.parent 4585 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4586 parent = parent.parent 4587 4588 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4589 return self.sql(exp.Unnest(expressions=[generate_series])) 4590 4591 if isinstance(parent, exp.Select): 4592 self.unsupported("GenerateSeries projection unnesting is not supported.") 4593 4594 return self.sql(generate_series) 4595 4596 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4597 exprs = expression.expressions 4598 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4599 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4600 else: 4601 rhs = self.expressions(expression) 4602 4603 return self.func(name, expression.this, rhs or None) 4604 4605 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4606 if self.SUPPORTS_CONVERT_TIMEZONE: 4607 return self.function_fallback_sql(expression) 4608 4609 source_tz = expression.args.get("source_tz") 4610 target_tz = expression.args.get("target_tz") 4611 timestamp = expression.args.get("timestamp") 4612 4613 if source_tz and timestamp: 4614 timestamp = exp.AtTimeZone( 4615 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4616 ) 4617 4618 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4619 4620 return self.sql(expr) 4621 4622 def json_sql(self, expression: exp.JSON) -> str: 4623 this = self.sql(expression, "this") 4624 this = f" {this}" if this else "" 4625 4626 _with = expression.args.get("with") 4627 4628 if _with is None: 4629 with_sql = "" 4630 elif not _with: 4631 with_sql = " WITHOUT" 4632 else: 4633 with_sql = " WITH" 4634 4635 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4636 4637 return f"JSON{this}{with_sql}{unique_sql}" 4638 4639 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4640 def _generate_on_options(arg: t.Any) -> str: 4641 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4642 4643 path = self.sql(expression, "path") 4644 returning = self.sql(expression, "returning") 4645 returning = f" RETURNING {returning}" if returning else "" 4646 4647 on_condition = self.sql(expression, "on_condition") 4648 on_condition = f" {on_condition}" if on_condition else "" 4649 4650 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4651 4652 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4653 else_ = "ELSE " if expression.args.get("else_") else "" 4654 condition = self.sql(expression, "expression") 4655 condition = f"WHEN {condition} THEN " if condition else else_ 4656 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4657 return f"{condition}{insert}" 4658 4659 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4660 kind = self.sql(expression, "kind") 4661 expressions = self.seg(self.expressions(expression, sep=" ")) 4662 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4663 return res 4664 4665 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4666 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4667 empty = expression.args.get("empty") 4668 empty = ( 4669 f"DEFAULT {empty} ON EMPTY" 4670 if isinstance(empty, exp.Expression) 4671 else self.sql(expression, "empty") 4672 ) 4673 4674 error = expression.args.get("error") 4675 error = ( 4676 f"DEFAULT {error} ON ERROR" 4677 if isinstance(error, exp.Expression) 4678 else self.sql(expression, "error") 4679 ) 4680 4681 if error and empty: 4682 error = ( 4683 f"{empty} {error}" 4684 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4685 else f"{error} {empty}" 4686 ) 4687 empty = "" 4688 4689 null = self.sql(expression, "null") 4690 4691 return f"{empty}{error}{null}" 4692 4693 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4694 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4695 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4696 4697 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4698 this = self.sql(expression, "this") 4699 path = self.sql(expression, "path") 4700 4701 passing = self.expressions(expression, "passing") 4702 passing = f" PASSING {passing}" if passing else "" 4703 4704 on_condition = self.sql(expression, "on_condition") 4705 on_condition = f" {on_condition}" if on_condition else "" 4706 4707 path = f"{path}{passing}{on_condition}" 4708 4709 return self.func("JSON_EXISTS", this, path) 4710 4711 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4712 array_agg = self.function_fallback_sql(expression) 4713 4714 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4715 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4716 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4717 parent = expression.parent 4718 if isinstance(parent, exp.Filter): 4719 parent_cond = parent.expression.this 4720 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4721 else: 4722 this = expression.this 4723 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4724 if this.find(exp.Column): 4725 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4726 this_sql = ( 4727 self.expressions(this) 4728 if isinstance(this, exp.Distinct) 4729 else self.sql(expression, "this") 4730 ) 4731 4732 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4733 4734 return array_agg 4735 4736 def apply_sql(self, expression: exp.Apply) -> str: 4737 this = self.sql(expression, "this") 4738 expr = self.sql(expression, "expression") 4739 4740 return f"{this} APPLY({expr})" 4741 4742 def grant_sql(self, expression: exp.Grant) -> str: 4743 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4744 4745 kind = self.sql(expression, "kind") 4746 kind = f" {kind}" if kind else "" 4747 4748 securable = self.sql(expression, "securable") 4749 securable = f" {securable}" if securable else "" 4750 4751 principals = self.expressions(expression, key="principals", flat=True) 4752 4753 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4754 4755 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4756 4757 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4758 this = self.sql(expression, "this") 4759 columns = self.expressions(expression, flat=True) 4760 columns = f"({columns})" if columns else "" 4761 4762 return f"{this}{columns}" 4763 4764 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4765 this = self.sql(expression, "this") 4766 4767 kind = self.sql(expression, "kind") 4768 kind = f"{kind} " if kind else "" 4769 4770 return f"{kind}{this}" 4771 4772 def columns_sql(self, expression: exp.Columns): 4773 func = self.function_fallback_sql(expression) 4774 if expression.args.get("unpack"): 4775 func = f"*{func}" 4776 4777 return func 4778 4779 def overlay_sql(self, expression: exp.Overlay): 4780 this = self.sql(expression, "this") 4781 expr = self.sql(expression, "expression") 4782 from_sql = self.sql(expression, "from") 4783 for_sql = self.sql(expression, "for") 4784 for_sql = f" FOR {for_sql}" if for_sql else "" 4785 4786 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4787 4788 @unsupported_args("format") 4789 def todouble_sql(self, expression: exp.ToDouble) -> str: 4790 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4791 4792 def string_sql(self, expression: exp.String) -> str: 4793 this = expression.this 4794 zone = expression.args.get("zone") 4795 4796 if zone: 4797 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4798 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4799 # set for source_tz to transpile the time conversion before the STRING cast 4800 this = exp.ConvertTimezone( 4801 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4802 ) 4803 4804 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4805 4806 def median_sql(self, expression: exp.Median): 4807 if not self.SUPPORTS_MEDIAN: 4808 return self.sql( 4809 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4810 ) 4811 4812 return self.function_fallback_sql(expression) 4813 4814 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4815 filler = self.sql(expression, "this") 4816 filler = f" {filler}" if filler else "" 4817 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4818 return f"TRUNCATE{filler} {with_count}" 4819 4820 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4821 if self.SUPPORTS_UNIX_SECONDS: 4822 return self.function_fallback_sql(expression) 4823 4824 start_ts = exp.cast( 4825 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4826 ) 4827 4828 return self.sql( 4829 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4830 ) 4831 4832 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4833 dim = expression.expression 4834 4835 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4836 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4837 if not (dim.is_int and dim.name == "1"): 4838 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4839 dim = None 4840 4841 # If dimension is required but not specified, default initialize it 4842 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4843 dim = exp.Literal.number(1) 4844 4845 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4846 4847 def attach_sql(self, expression: exp.Attach) -> str: 4848 this = self.sql(expression, "this") 4849 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4850 expressions = self.expressions(expression) 4851 expressions = f" ({expressions})" if expressions else "" 4852 4853 return f"ATTACH{exists_sql} {this}{expressions}" 4854 4855 def detach_sql(self, expression: exp.Detach) -> str: 4856 this = self.sql(expression, "this") 4857 # the DATABASE keyword is required if IF EXISTS is set 4858 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4859 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4860 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4861 4862 return f"DETACH{exists_sql} {this}" 4863 4864 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4865 this = self.sql(expression, "this") 4866 value = self.sql(expression, "expression") 4867 value = f" {value}" if value else "" 4868 return f"{this}{value}" 4869 4870 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4871 this_sql = self.sql(expression, "this") 4872 if isinstance(expression.this, exp.Table): 4873 this_sql = f"TABLE {this_sql}" 4874 4875 return self.func( 4876 "FEATURES_AT_TIME", 4877 this_sql, 4878 expression.args.get("time"), 4879 expression.args.get("num_rows"), 4880 expression.args.get("ignore_feature_nulls"), 4881 ) 4882 4883 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4884 return ( 4885 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4886 ) 4887 4888 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4889 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4890 encode = f"{encode} {self.sql(expression, 'this')}" 4891 4892 properties = expression.args.get("properties") 4893 if properties: 4894 encode = f"{encode} {self.properties(properties)}" 4895 4896 return encode 4897 4898 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4899 this = self.sql(expression, "this") 4900 include = f"INCLUDE {this}" 4901 4902 column_def = self.sql(expression, "column_def") 4903 if column_def: 4904 include = f"{include} {column_def}" 4905 4906 alias = self.sql(expression, "alias") 4907 if alias: 4908 include = f"{include} AS {alias}" 4909 4910 return include 4911 4912 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4913 name = f"NAME {self.sql(expression, 'this')}" 4914 return self.func("XMLELEMENT", name, *expression.expressions) 4915 4916 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 4917 this = self.sql(expression, "this") 4918 expr = self.sql(expression, "expression") 4919 expr = f"({expr})" if expr else "" 4920 return f"{this}{expr}" 4921 4922 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4923 partitions = self.expressions(expression, "partition_expressions") 4924 create = self.expressions(expression, "create_expressions") 4925 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4926 4927 def partitionbyrangepropertydynamic_sql( 4928 self, expression: exp.PartitionByRangePropertyDynamic 4929 ) -> str: 4930 start = self.sql(expression, "start") 4931 end = self.sql(expression, "end") 4932 4933 every = expression.args["every"] 4934 if isinstance(every, exp.Interval) and every.this.is_string: 4935 every.this.replace(exp.Literal.number(every.name)) 4936 4937 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4938 4939 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4940 name = self.sql(expression, "this") 4941 values = self.expressions(expression, flat=True) 4942 4943 return f"NAME {name} VALUE {values}" 4944 4945 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4946 kind = self.sql(expression, "kind") 4947 sample = self.sql(expression, "sample") 4948 return f"SAMPLE {sample} {kind}" 4949 4950 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4951 kind = self.sql(expression, "kind") 4952 option = self.sql(expression, "option") 4953 option = f" {option}" if option else "" 4954 this = self.sql(expression, "this") 4955 this = f" {this}" if this else "" 4956 columns = self.expressions(expression) 4957 columns = f" {columns}" if columns else "" 4958 return f"{kind}{option} STATISTICS{this}{columns}" 4959 4960 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4961 this = self.sql(expression, "this") 4962 columns = self.expressions(expression) 4963 inner_expression = self.sql(expression, "expression") 4964 inner_expression = f" {inner_expression}" if inner_expression else "" 4965 update_options = self.sql(expression, "update_options") 4966 update_options = f" {update_options} UPDATE" if update_options else "" 4967 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4968 4969 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4970 kind = self.sql(expression, "kind") 4971 kind = f" {kind}" if kind else "" 4972 return f"DELETE{kind} STATISTICS" 4973 4974 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4975 inner_expression = self.sql(expression, "expression") 4976 return f"LIST CHAINED ROWS{inner_expression}" 4977 4978 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4979 kind = self.sql(expression, "kind") 4980 this = self.sql(expression, "this") 4981 this = f" {this}" if this else "" 4982 inner_expression = self.sql(expression, "expression") 4983 return f"VALIDATE {kind}{this}{inner_expression}" 4984 4985 def analyze_sql(self, expression: exp.Analyze) -> str: 4986 options = self.expressions(expression, key="options", sep=" ") 4987 options = f" {options}" if options else "" 4988 kind = self.sql(expression, "kind") 4989 kind = f" {kind}" if kind else "" 4990 this = self.sql(expression, "this") 4991 this = f" {this}" if this else "" 4992 mode = self.sql(expression, "mode") 4993 mode = f" {mode}" if mode else "" 4994 properties = self.sql(expression, "properties") 4995 properties = f" {properties}" if properties else "" 4996 partition = self.sql(expression, "partition") 4997 partition = f" {partition}" if partition else "" 4998 inner_expression = self.sql(expression, "expression") 4999 inner_expression = f" {inner_expression}" if inner_expression else "" 5000 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5001 5002 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5003 this = self.sql(expression, "this") 5004 namespaces = self.expressions(expression, key="namespaces") 5005 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5006 passing = self.expressions(expression, key="passing") 5007 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5008 columns = self.expressions(expression, key="columns") 5009 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5010 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5011 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5012 5013 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5014 this = self.sql(expression, "this") 5015 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5016 5017 def export_sql(self, expression: exp.Export) -> str: 5018 this = self.sql(expression, "this") 5019 connection = self.sql(expression, "connection") 5020 connection = f"WITH CONNECTION {connection} " if connection else "" 5021 options = self.sql(expression, "options") 5022 return f"EXPORT DATA {connection}{options} AS {this}" 5023 5024 def declare_sql(self, expression: exp.Declare) -> str: 5025 return f"DECLARE {self.expressions(expression, flat=True)}" 5026 5027 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5028 variable = self.sql(expression, "this") 5029 default = self.sql(expression, "default") 5030 default = f" = {default}" if default else "" 5031 5032 kind = self.sql(expression, "kind") 5033 if isinstance(expression.args.get("kind"), exp.Schema): 5034 kind = f"TABLE {kind}" 5035 5036 return f"{variable} AS {kind}{default}" 5037 5038 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5039 kind = self.sql(expression, "kind") 5040 this = self.sql(expression, "this") 5041 set = self.sql(expression, "expression") 5042 using = self.sql(expression, "using") 5043 using = f" USING {using}" if using else "" 5044 5045 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5046 5047 return f"{kind_sql} {this} SET {set}{using}" 5048 5049 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5050 params = self.expressions(expression, key="params", flat=True) 5051 return self.func(expression.name, *expression.expressions) + f"({params})" 5052 5053 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5054 return self.func(expression.name, *expression.expressions) 5055 5056 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5057 return self.anonymousaggfunc_sql(expression) 5058 5059 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5060 return self.parameterizedagg_sql(expression) 5061 5062 def show_sql(self, expression: exp.Show) -> str: 5063 self.unsupported("Unsupported SHOW statement") 5064 return "" 5065 5066 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5067 # Snowflake GET/PUT statements: 5068 # PUT <file> <internalStage> <properties> 5069 # GET <internalStage> <file> <properties> 5070 props = expression.args.get("properties") 5071 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5072 this = self.sql(expression, "this") 5073 target = self.sql(expression, "target") 5074 5075 if isinstance(expression, exp.Put): 5076 return f"PUT {this} {target}{props_sql}" 5077 else: 5078 return f"GET {target} {this}{props_sql}" 5079 5080 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5081 this = self.sql(expression, "this") 5082 expr = self.sql(expression, "expression") 5083 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5084 return f"TRANSLATE({this} USING {expr}{with_error})" 5085 5086 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5087 if self.SUPPORTS_DECODE_CASE: 5088 return self.func("DECODE", *expression.expressions) 5089 5090 expression, *expressions = expression.expressions 5091 5092 ifs = [] 5093 for search, result in zip(expressions[::2], expressions[1::2]): 5094 if isinstance(search, exp.Literal): 5095 ifs.append(exp.If(this=expression.eq(search), true=result)) 5096 elif isinstance(search, exp.Null): 5097 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5098 else: 5099 if isinstance(search, exp.Binary): 5100 search = exp.paren(search) 5101 5102 cond = exp.or_( 5103 expression.eq(search), 5104 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5105 copy=False, 5106 ) 5107 ifs.append(exp.If(this=cond, true=result)) 5108 5109 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5110 return self.sql(case) 5111 5112 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5113 this = self.sql(expression, "this") 5114 this = self.seg(this, sep="") 5115 dimensions = self.expressions( 5116 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5117 ) 5118 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5119 metrics = self.expressions( 5120 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5121 ) 5122 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5123 where = self.sql(expression, "where") 5124 where = self.seg(f"WHERE {where}") if where else "" 5125 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}" 5126 5127 def getextract_sql(self, expression: exp.GetExtract) -> str: 5128 this = expression.this 5129 expr = expression.expression 5130 5131 if not this.type or not expression.type: 5132 from sqlglot.optimizer.annotate_types import annotate_types 5133 5134 this = annotate_types(this, dialect=self.dialect) 5135 5136 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5137 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5138 5139 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5140 5141 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5142 return self.sql( 5143 exp.DateAdd( 5144 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5145 expression=expression.this, 5146 unit=exp.var("DAY"), 5147 ) 5148 )
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: Union[str, Tuple[str, str]]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
30def unsupported_args( 31 *args: t.Union[str, t.Tuple[str, str]], 32) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 33 """ 34 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 35 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 36 """ 37 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 38 for arg in args: 39 if isinstance(arg, str): 40 diagnostic_by_arg[arg] = None 41 else: 42 diagnostic_by_arg[arg[0]] = arg[1] 43 44 def decorator(func: GeneratorMethod) -> GeneratorMethod: 45 @wraps(func) 46 def _func(generator: G, expression: E) -> str: 47 expression_name = expression.__class__.__name__ 48 dialect_name = generator.dialect.__class__.__name__ 49 50 for arg_name, diagnostic in diagnostic_by_arg.items(): 51 if expression.args.get(arg_name): 52 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 53 arg_name, expression_name, dialect_name 54 ) 55 generator.unsupported(diagnostic) 56 57 return func(generator, expression) 58 59 return _func 60 61 return decorator
Decorator that can be used to mark certain args of an Expression subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
class
Generator:
75class Generator(metaclass=_Generator): 76 """ 77 Generator converts a given syntax tree to the corresponding SQL string. 78 79 Args: 80 pretty: Whether to format the produced SQL string. 81 Default: False. 82 identify: Determines when an identifier should be quoted. Possible values are: 83 False (default): Never quote, except in cases where it's mandatory by the dialect. 84 True or 'always': Always quote. 85 'safe': Only quote identifiers that are case insensitive. 86 normalize: Whether to normalize identifiers to lowercase. 87 Default: False. 88 pad: The pad size in a formatted string. For example, this affects the indentation of 89 a projection in a query, relative to its nesting level. 90 Default: 2. 91 indent: The indentation size in a formatted string. For example, this affects the 92 indentation of subqueries and filters under a `WHERE` clause. 93 Default: 2. 94 normalize_functions: How to normalize function names. Possible values are: 95 "upper" or True (default): Convert names to uppercase. 96 "lower": Convert names to lowercase. 97 False: Disables function name normalization. 98 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 99 Default ErrorLevel.WARN. 100 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 101 This is only relevant if unsupported_level is ErrorLevel.RAISE. 102 Default: 3 103 leading_comma: Whether the comma is leading or trailing in select expressions. 104 This is only relevant when generating in pretty mode. 105 Default: False 106 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 107 The default is on the smaller end because the length only represents a segment and not the true 108 line length. 109 Default: 80 110 comments: Whether to preserve comments in the output SQL code. 111 Default: True 112 """ 113 114 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 115 **JSON_PATH_PART_TRANSFORMS, 116 exp.AllowedValuesProperty: lambda self, 117 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 118 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 119 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 120 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 121 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 122 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 123 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 124 exp.CaseSpecificColumnConstraint: lambda _, 125 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 126 exp.Ceil: lambda self, e: self.ceil_floor(e), 127 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 128 exp.CharacterSetProperty: lambda self, 129 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 130 exp.ClusteredColumnConstraint: lambda self, 131 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 132 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 133 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 134 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 135 exp.ConvertToCharset: lambda self, e: self.func( 136 "CONVERT", e.this, e.args["dest"], e.args.get("source") 137 ), 138 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 139 exp.CredentialsProperty: lambda self, 140 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 141 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 142 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 143 exp.DynamicProperty: lambda *_: "DYNAMIC", 144 exp.EmptyProperty: lambda *_: "EMPTY", 145 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 146 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 147 exp.EphemeralColumnConstraint: lambda self, 148 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 149 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 150 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 151 exp.Except: lambda self, e: self.set_operations(e), 152 exp.ExternalProperty: lambda *_: "EXTERNAL", 153 exp.Floor: lambda self, e: self.ceil_floor(e), 154 exp.Get: lambda self, e: self.get_put_sql(e), 155 exp.GlobalProperty: lambda *_: "GLOBAL", 156 exp.HeapProperty: lambda *_: "HEAP", 157 exp.IcebergProperty: lambda *_: "ICEBERG", 158 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 159 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 160 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 161 exp.Intersect: lambda self, e: self.set_operations(e), 162 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 163 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 164 exp.LanguageProperty: lambda self, e: self.naked_property(e), 165 exp.LocationProperty: lambda self, e: self.naked_property(e), 166 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 167 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 168 exp.NonClusteredColumnConstraint: lambda self, 169 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 170 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 171 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 172 exp.OnCommitProperty: lambda _, 173 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 174 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 175 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 176 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 177 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 178 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 179 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 180 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 181 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 182 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 183 exp.ProjectionPolicyColumnConstraint: lambda self, 184 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 185 exp.Put: lambda self, e: self.get_put_sql(e), 186 exp.RemoteWithConnectionModelProperty: lambda self, 187 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 188 exp.ReturnsProperty: lambda self, e: ( 189 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 190 ), 191 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 192 exp.SecureProperty: lambda *_: "SECURE", 193 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 194 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 195 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 196 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 197 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 198 exp.SqlReadWriteProperty: lambda _, e: e.name, 199 exp.SqlSecurityProperty: lambda _, 200 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 201 exp.StabilityProperty: lambda _, e: e.name, 202 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 203 exp.StreamingTableProperty: lambda *_: "STREAMING", 204 exp.StrictProperty: lambda *_: "STRICT", 205 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 206 exp.TableColumn: lambda self, e: self.sql(e.this), 207 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 208 exp.TemporaryProperty: lambda *_: "TEMPORARY", 209 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 210 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 211 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 212 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 213 exp.TransientProperty: lambda *_: "TRANSIENT", 214 exp.Union: lambda self, e: self.set_operations(e), 215 exp.UnloggedProperty: lambda *_: "UNLOGGED", 216 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 217 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 218 exp.Uuid: lambda *_: "UUID()", 219 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 220 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 221 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 222 exp.VolatileProperty: lambda *_: "VOLATILE", 223 exp.WeekStart: lambda self, e: f"WEEK({self.sql(e, 'this')})", 224 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 225 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 226 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 227 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 228 exp.ForceProperty: lambda *_: "FORCE", 229 } 230 231 # Whether null ordering is supported in order by 232 # True: Full Support, None: No support, False: No support for certain cases 233 # such as window specifications, aggregate functions etc 234 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 235 236 # Whether ignore nulls is inside the agg or outside. 237 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 238 IGNORE_NULLS_IN_FUNC = False 239 240 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 241 LOCKING_READS_SUPPORTED = False 242 243 # Whether the EXCEPT and INTERSECT operations can return duplicates 244 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 245 246 # Wrap derived values in parens, usually standard but spark doesn't support it 247 WRAP_DERIVED_VALUES = True 248 249 # Whether create function uses an AS before the RETURN 250 CREATE_FUNCTION_RETURN_AS = True 251 252 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 253 MATCHED_BY_SOURCE = True 254 255 # Whether the INTERVAL expression works only with values like '1 day' 256 SINGLE_STRING_INTERVAL = False 257 258 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 259 INTERVAL_ALLOWS_PLURAL_FORM = True 260 261 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 262 LIMIT_FETCH = "ALL" 263 264 # Whether limit and fetch allows expresions or just limits 265 LIMIT_ONLY_LITERALS = False 266 267 # Whether a table is allowed to be renamed with a db 268 RENAME_TABLE_WITH_DB = True 269 270 # The separator for grouping sets and rollups 271 GROUPINGS_SEP = "," 272 273 # The string used for creating an index on a table 274 INDEX_ON = "ON" 275 276 # Whether join hints should be generated 277 JOIN_HINTS = True 278 279 # Whether table hints should be generated 280 TABLE_HINTS = True 281 282 # Whether query hints should be generated 283 QUERY_HINTS = True 284 285 # What kind of separator to use for query hints 286 QUERY_HINT_SEP = ", " 287 288 # Whether comparing against booleans (e.g. x IS TRUE) is supported 289 IS_BOOL_ALLOWED = True 290 291 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 292 DUPLICATE_KEY_UPDATE_WITH_SET = True 293 294 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 295 LIMIT_IS_TOP = False 296 297 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 298 RETURNING_END = True 299 300 # Whether to generate an unquoted value for EXTRACT's date part argument 301 EXTRACT_ALLOWS_QUOTES = True 302 303 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 304 TZ_TO_WITH_TIME_ZONE = False 305 306 # Whether the NVL2 function is supported 307 NVL2_SUPPORTED = True 308 309 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 310 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 311 312 # Whether VALUES statements can be used as derived tables. 313 # MySQL 5 and Redshift do not allow this, so when False, it will convert 314 # SELECT * VALUES into SELECT UNION 315 VALUES_AS_TABLE = True 316 317 # Whether the word COLUMN is included when adding a column with ALTER TABLE 318 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 319 320 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 321 UNNEST_WITH_ORDINALITY = True 322 323 # Whether FILTER (WHERE cond) can be used for conditional aggregation 324 AGGREGATE_FILTER_SUPPORTED = True 325 326 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 327 SEMI_ANTI_JOIN_WITH_SIDE = True 328 329 # Whether to include the type of a computed column in the CREATE DDL 330 COMPUTED_COLUMN_WITH_TYPE = True 331 332 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 333 SUPPORTS_TABLE_COPY = True 334 335 # Whether parentheses are required around the table sample's expression 336 TABLESAMPLE_REQUIRES_PARENS = True 337 338 # Whether a table sample clause's size needs to be followed by the ROWS keyword 339 TABLESAMPLE_SIZE_IS_ROWS = True 340 341 # The keyword(s) to use when generating a sample clause 342 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 343 344 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 345 TABLESAMPLE_WITH_METHOD = True 346 347 # The keyword to use when specifying the seed of a sample clause 348 TABLESAMPLE_SEED_KEYWORD = "SEED" 349 350 # Whether COLLATE is a function instead of a binary operator 351 COLLATE_IS_FUNC = False 352 353 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 354 DATA_TYPE_SPECIFIERS_ALLOWED = False 355 356 # Whether conditions require booleans WHERE x = 0 vs WHERE x 357 ENSURE_BOOLS = False 358 359 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 360 CTE_RECURSIVE_KEYWORD_REQUIRED = True 361 362 # Whether CONCAT requires >1 arguments 363 SUPPORTS_SINGLE_ARG_CONCAT = True 364 365 # Whether LAST_DAY function supports a date part argument 366 LAST_DAY_SUPPORTS_DATE_PART = True 367 368 # Whether named columns are allowed in table aliases 369 SUPPORTS_TABLE_ALIAS_COLUMNS = True 370 371 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 372 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 373 374 # What delimiter to use for separating JSON key/value pairs 375 JSON_KEY_VALUE_PAIR_SEP = ":" 376 377 # INSERT OVERWRITE TABLE x override 378 INSERT_OVERWRITE = " OVERWRITE TABLE" 379 380 # Whether the SELECT .. INTO syntax is used instead of CTAS 381 SUPPORTS_SELECT_INTO = False 382 383 # Whether UNLOGGED tables can be created 384 SUPPORTS_UNLOGGED_TABLES = False 385 386 # Whether the CREATE TABLE LIKE statement is supported 387 SUPPORTS_CREATE_TABLE_LIKE = True 388 389 # Whether the LikeProperty needs to be specified inside of the schema clause 390 LIKE_PROPERTY_INSIDE_SCHEMA = False 391 392 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 393 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 394 MULTI_ARG_DISTINCT = True 395 396 # Whether the JSON extraction operators expect a value of type JSON 397 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 398 399 # Whether bracketed keys like ["foo"] are supported in JSON paths 400 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 401 402 # Whether to escape keys using single quotes in JSON paths 403 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 404 405 # The JSONPathPart expressions supported by this dialect 406 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 407 408 # Whether any(f(x) for x in array) can be implemented by this dialect 409 CAN_IMPLEMENT_ARRAY_ANY = False 410 411 # Whether the function TO_NUMBER is supported 412 SUPPORTS_TO_NUMBER = True 413 414 # Whether EXCLUDE in window specification is supported 415 SUPPORTS_WINDOW_EXCLUDE = False 416 417 # Whether or not set op modifiers apply to the outer set op or select. 418 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 419 # True means limit 1 happens after the set op, False means it it happens on y. 420 SET_OP_MODIFIERS = True 421 422 # Whether parameters from COPY statement are wrapped in parentheses 423 COPY_PARAMS_ARE_WRAPPED = True 424 425 # Whether values of params are set with "=" token or empty space 426 COPY_PARAMS_EQ_REQUIRED = False 427 428 # Whether COPY statement has INTO keyword 429 COPY_HAS_INTO_KEYWORD = True 430 431 # Whether the conditional TRY(expression) function is supported 432 TRY_SUPPORTED = True 433 434 # Whether the UESCAPE syntax in unicode strings is supported 435 SUPPORTS_UESCAPE = True 436 437 # The keyword to use when generating a star projection with excluded columns 438 STAR_EXCEPT = "EXCEPT" 439 440 # The HEX function name 441 HEX_FUNC = "HEX" 442 443 # The keywords to use when prefixing & separating WITH based properties 444 WITH_PROPERTIES_PREFIX = "WITH" 445 446 # Whether to quote the generated expression of exp.JsonPath 447 QUOTE_JSON_PATH = True 448 449 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 450 PAD_FILL_PATTERN_IS_REQUIRED = False 451 452 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 453 SUPPORTS_EXPLODING_PROJECTIONS = True 454 455 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 456 ARRAY_CONCAT_IS_VAR_LEN = True 457 458 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 459 SUPPORTS_CONVERT_TIMEZONE = False 460 461 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 462 SUPPORTS_MEDIAN = True 463 464 # Whether UNIX_SECONDS(timestamp) is supported 465 SUPPORTS_UNIX_SECONDS = False 466 467 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 468 ALTER_SET_WRAPPED = False 469 470 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 471 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 472 # TODO: The normalization should be done by default once we've tested it across all dialects. 473 NORMALIZE_EXTRACT_DATE_PARTS = False 474 475 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 476 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 477 478 # The function name of the exp.ArraySize expression 479 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 480 481 # The syntax to use when altering the type of a column 482 ALTER_SET_TYPE = "SET DATA TYPE" 483 484 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 485 # None -> Doesn't support it at all 486 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 487 # True (Postgres) -> Explicitly requires it 488 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 489 490 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 491 SUPPORTS_DECODE_CASE = True 492 493 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 494 SUPPORTS_BETWEEN_FLAGS = False 495 496 # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME 497 SUPPORTS_LIKE_QUANTIFIERS = True 498 499 TYPE_MAPPING = { 500 exp.DataType.Type.DATETIME2: "TIMESTAMP", 501 exp.DataType.Type.NCHAR: "CHAR", 502 exp.DataType.Type.NVARCHAR: "VARCHAR", 503 exp.DataType.Type.MEDIUMTEXT: "TEXT", 504 exp.DataType.Type.LONGTEXT: "TEXT", 505 exp.DataType.Type.TINYTEXT: "TEXT", 506 exp.DataType.Type.BLOB: "VARBINARY", 507 exp.DataType.Type.MEDIUMBLOB: "BLOB", 508 exp.DataType.Type.LONGBLOB: "BLOB", 509 exp.DataType.Type.TINYBLOB: "BLOB", 510 exp.DataType.Type.INET: "INET", 511 exp.DataType.Type.ROWVERSION: "VARBINARY", 512 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 513 } 514 515 TIME_PART_SINGULARS = { 516 "MICROSECONDS": "MICROSECOND", 517 "SECONDS": "SECOND", 518 "MINUTES": "MINUTE", 519 "HOURS": "HOUR", 520 "DAYS": "DAY", 521 "WEEKS": "WEEK", 522 "MONTHS": "MONTH", 523 "QUARTERS": "QUARTER", 524 "YEARS": "YEAR", 525 } 526 527 AFTER_HAVING_MODIFIER_TRANSFORMS = { 528 "cluster": lambda self, e: self.sql(e, "cluster"), 529 "distribute": lambda self, e: self.sql(e, "distribute"), 530 "sort": lambda self, e: self.sql(e, "sort"), 531 "windows": lambda self, e: ( 532 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 533 if e.args.get("windows") 534 else "" 535 ), 536 "qualify": lambda self, e: self.sql(e, "qualify"), 537 } 538 539 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 540 541 STRUCT_DELIMITER = ("<", ">") 542 543 PARAMETER_TOKEN = "@" 544 NAMED_PLACEHOLDER_TOKEN = ":" 545 546 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 547 548 PROPERTIES_LOCATION = { 549 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 550 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 551 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 552 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 555 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 556 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 557 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 559 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 560 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 561 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 562 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 564 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 566 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 567 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 569 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 573 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 577 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 578 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 579 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 580 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 581 exp.HeapProperty: exp.Properties.Location.POST_WITH, 582 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 584 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 587 exp.JournalProperty: exp.Properties.Location.POST_NAME, 588 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 593 exp.LogProperty: exp.Properties.Location.POST_NAME, 594 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 595 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 596 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 597 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 599 exp.Order: exp.Properties.Location.POST_SCHEMA, 600 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 602 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 604 exp.Property: exp.Properties.Location.POST_WITH, 605 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 611 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 613 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 615 exp.Set: exp.Properties.Location.POST_SCHEMA, 616 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.SetProperty: exp.Properties.Location.POST_CREATE, 618 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 620 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 621 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 624 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 627 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.Tags: exp.Properties.Location.POST_WITH, 629 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 630 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 631 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 632 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 634 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 635 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 636 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 638 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 639 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 640 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 641 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 642 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 644 } 645 646 # Keywords that can't be used as unquoted identifier names 647 RESERVED_KEYWORDS: t.Set[str] = set() 648 649 # Expressions whose comments are separated from them for better formatting 650 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 651 exp.Command, 652 exp.Create, 653 exp.Describe, 654 exp.Delete, 655 exp.Drop, 656 exp.From, 657 exp.Insert, 658 exp.Join, 659 exp.MultitableInserts, 660 exp.Order, 661 exp.Group, 662 exp.Having, 663 exp.Select, 664 exp.SetOperation, 665 exp.Update, 666 exp.Where, 667 exp.With, 668 ) 669 670 # Expressions that should not have their comments generated in maybe_comment 671 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 672 exp.Binary, 673 exp.SetOperation, 674 ) 675 676 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 677 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 678 exp.Column, 679 exp.Literal, 680 exp.Neg, 681 exp.Paren, 682 ) 683 684 PARAMETERIZABLE_TEXT_TYPES = { 685 exp.DataType.Type.NVARCHAR, 686 exp.DataType.Type.VARCHAR, 687 exp.DataType.Type.CHAR, 688 exp.DataType.Type.NCHAR, 689 } 690 691 # Expressions that need to have all CTEs under them bubbled up to them 692 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 693 694 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 695 696 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 697 698 __slots__ = ( 699 "pretty", 700 "identify", 701 "normalize", 702 "pad", 703 "_indent", 704 "normalize_functions", 705 "unsupported_level", 706 "max_unsupported", 707 "leading_comma", 708 "max_text_width", 709 "comments", 710 "dialect", 711 "unsupported_messages", 712 "_escaped_quote_end", 713 "_escaped_identifier_end", 714 "_next_name", 715 "_identifier_start", 716 "_identifier_end", 717 "_quote_json_path_key_using_brackets", 718 ) 719 720 def __init__( 721 self, 722 pretty: t.Optional[bool] = None, 723 identify: str | bool = False, 724 normalize: bool = False, 725 pad: int = 2, 726 indent: int = 2, 727 normalize_functions: t.Optional[str | bool] = None, 728 unsupported_level: ErrorLevel = ErrorLevel.WARN, 729 max_unsupported: int = 3, 730 leading_comma: bool = False, 731 max_text_width: int = 80, 732 comments: bool = True, 733 dialect: DialectType = None, 734 ): 735 import sqlglot 736 from sqlglot.dialects import Dialect 737 738 self.pretty = pretty if pretty is not None else sqlglot.pretty 739 self.identify = identify 740 self.normalize = normalize 741 self.pad = pad 742 self._indent = indent 743 self.unsupported_level = unsupported_level 744 self.max_unsupported = max_unsupported 745 self.leading_comma = leading_comma 746 self.max_text_width = max_text_width 747 self.comments = comments 748 self.dialect = Dialect.get_or_raise(dialect) 749 750 # This is both a Dialect property and a Generator argument, so we prioritize the latter 751 self.normalize_functions = ( 752 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 753 ) 754 755 self.unsupported_messages: t.List[str] = [] 756 self._escaped_quote_end: str = ( 757 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 758 ) 759 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 760 761 self._next_name = name_sequence("_t") 762 763 self._identifier_start = self.dialect.IDENTIFIER_START 764 self._identifier_end = self.dialect.IDENTIFIER_END 765 766 self._quote_json_path_key_using_brackets = True 767 768 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 769 """ 770 Generates the SQL string corresponding to the given syntax tree. 771 772 Args: 773 expression: The syntax tree. 774 copy: Whether to copy the expression. The generator performs mutations so 775 it is safer to copy. 776 777 Returns: 778 The SQL string corresponding to `expression`. 779 """ 780 if copy: 781 expression = expression.copy() 782 783 expression = self.preprocess(expression) 784 785 self.unsupported_messages = [] 786 sql = self.sql(expression).strip() 787 788 if self.pretty: 789 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 790 791 if self.unsupported_level == ErrorLevel.IGNORE: 792 return sql 793 794 if self.unsupported_level == ErrorLevel.WARN: 795 for msg in self.unsupported_messages: 796 logger.warning(msg) 797 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 798 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 799 800 return sql 801 802 def preprocess(self, expression: exp.Expression) -> exp.Expression: 803 """Apply generic preprocessing transformations to a given expression.""" 804 expression = self._move_ctes_to_top_level(expression) 805 806 if self.ENSURE_BOOLS: 807 from sqlglot.transforms import ensure_bools 808 809 expression = ensure_bools(expression) 810 811 return expression 812 813 def _move_ctes_to_top_level(self, expression: E) -> E: 814 if ( 815 not expression.parent 816 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 817 and any(node.parent is not expression for node in expression.find_all(exp.With)) 818 ): 819 from sqlglot.transforms import move_ctes_to_top_level 820 821 expression = move_ctes_to_top_level(expression) 822 return expression 823 824 def unsupported(self, message: str) -> None: 825 if self.unsupported_level == ErrorLevel.IMMEDIATE: 826 raise UnsupportedError(message) 827 self.unsupported_messages.append(message) 828 829 def sep(self, sep: str = " ") -> str: 830 return f"{sep.strip()}\n" if self.pretty else sep 831 832 def seg(self, sql: str, sep: str = " ") -> str: 833 return f"{self.sep(sep)}{sql}" 834 835 def sanitize_comment(self, comment: str) -> str: 836 comment = " " + comment if comment[0].strip() else comment 837 comment = comment + " " if comment[-1].strip() else comment 838 839 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 840 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 841 comment = comment.replace("*/", "* /") 842 843 return comment 844 845 def maybe_comment( 846 self, 847 sql: str, 848 expression: t.Optional[exp.Expression] = None, 849 comments: t.Optional[t.List[str]] = None, 850 separated: bool = False, 851 ) -> str: 852 comments = ( 853 ((expression and expression.comments) if comments is None else comments) # type: ignore 854 if self.comments 855 else None 856 ) 857 858 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 859 return sql 860 861 comments_sql = " ".join( 862 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 863 ) 864 865 if not comments_sql: 866 return sql 867 868 comments_sql = self._replace_line_breaks(comments_sql) 869 870 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 871 return ( 872 f"{self.sep()}{comments_sql}{sql}" 873 if not sql or sql[0].isspace() 874 else f"{comments_sql}{self.sep()}{sql}" 875 ) 876 877 return f"{sql} {comments_sql}" 878 879 def wrap(self, expression: exp.Expression | str) -> str: 880 this_sql = ( 881 self.sql(expression) 882 if isinstance(expression, exp.UNWRAPPED_QUERIES) 883 else self.sql(expression, "this") 884 ) 885 if not this_sql: 886 return "()" 887 888 this_sql = self.indent(this_sql, level=1, pad=0) 889 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 890 891 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 892 original = self.identify 893 self.identify = False 894 result = func(*args, **kwargs) 895 self.identify = original 896 return result 897 898 def normalize_func(self, name: str) -> str: 899 if self.normalize_functions == "upper" or self.normalize_functions is True: 900 return name.upper() 901 if self.normalize_functions == "lower": 902 return name.lower() 903 return name 904 905 def indent( 906 self, 907 sql: str, 908 level: int = 0, 909 pad: t.Optional[int] = None, 910 skip_first: bool = False, 911 skip_last: bool = False, 912 ) -> str: 913 if not self.pretty or not sql: 914 return sql 915 916 pad = self.pad if pad is None else pad 917 lines = sql.split("\n") 918 919 return "\n".join( 920 ( 921 line 922 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 923 else f"{' ' * (level * self._indent + pad)}{line}" 924 ) 925 for i, line in enumerate(lines) 926 ) 927 928 def sql( 929 self, 930 expression: t.Optional[str | exp.Expression], 931 key: t.Optional[str] = None, 932 comment: bool = True, 933 ) -> str: 934 if not expression: 935 return "" 936 937 if isinstance(expression, str): 938 return expression 939 940 if key: 941 value = expression.args.get(key) 942 if value: 943 return self.sql(value) 944 return "" 945 946 transform = self.TRANSFORMS.get(expression.__class__) 947 948 if callable(transform): 949 sql = transform(self, expression) 950 elif isinstance(expression, exp.Expression): 951 exp_handler_name = f"{expression.key}_sql" 952 953 if hasattr(self, exp_handler_name): 954 sql = getattr(self, exp_handler_name)(expression) 955 elif isinstance(expression, exp.Func): 956 sql = self.function_fallback_sql(expression) 957 elif isinstance(expression, exp.Property): 958 sql = self.property_sql(expression) 959 else: 960 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 961 else: 962 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 963 964 return self.maybe_comment(sql, expression) if self.comments and comment else sql 965 966 def uncache_sql(self, expression: exp.Uncache) -> str: 967 table = self.sql(expression, "this") 968 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 969 return f"UNCACHE TABLE{exists_sql} {table}" 970 971 def cache_sql(self, expression: exp.Cache) -> str: 972 lazy = " LAZY" if expression.args.get("lazy") else "" 973 table = self.sql(expression, "this") 974 options = expression.args.get("options") 975 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 976 sql = self.sql(expression, "expression") 977 sql = f" AS{self.sep()}{sql}" if sql else "" 978 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 979 return self.prepend_ctes(expression, sql) 980 981 def characterset_sql(self, expression: exp.CharacterSet) -> str: 982 if isinstance(expression.parent, exp.Cast): 983 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 984 default = "DEFAULT " if expression.args.get("default") else "" 985 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 986 987 def column_parts(self, expression: exp.Column) -> str: 988 return ".".join( 989 self.sql(part) 990 for part in ( 991 expression.args.get("catalog"), 992 expression.args.get("db"), 993 expression.args.get("table"), 994 expression.args.get("this"), 995 ) 996 if part 997 ) 998 999 def column_sql(self, expression: exp.Column) -> str: 1000 join_mark = " (+)" if expression.args.get("join_mark") else "" 1001 1002 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1003 join_mark = "" 1004 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1005 1006 return f"{self.column_parts(expression)}{join_mark}" 1007 1008 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1009 this = self.sql(expression, "this") 1010 this = f" {this}" if this else "" 1011 position = self.sql(expression, "position") 1012 return f"{position}{this}" 1013 1014 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1015 column = self.sql(expression, "this") 1016 kind = self.sql(expression, "kind") 1017 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1018 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1019 kind = f"{sep}{kind}" if kind else "" 1020 constraints = f" {constraints}" if constraints else "" 1021 position = self.sql(expression, "position") 1022 position = f" {position}" if position else "" 1023 1024 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1025 kind = "" 1026 1027 return f"{exists}{column}{kind}{constraints}{position}" 1028 1029 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1030 this = self.sql(expression, "this") 1031 kind_sql = self.sql(expression, "kind").strip() 1032 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1033 1034 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1035 this = self.sql(expression, "this") 1036 if expression.args.get("not_null"): 1037 persisted = " PERSISTED NOT NULL" 1038 elif expression.args.get("persisted"): 1039 persisted = " PERSISTED" 1040 else: 1041 persisted = "" 1042 1043 return f"AS {this}{persisted}" 1044 1045 def autoincrementcolumnconstraint_sql(self, _) -> str: 1046 return self.token_sql(TokenType.AUTO_INCREMENT) 1047 1048 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1049 if isinstance(expression.this, list): 1050 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1051 else: 1052 this = self.sql(expression, "this") 1053 1054 return f"COMPRESS {this}" 1055 1056 def generatedasidentitycolumnconstraint_sql( 1057 self, expression: exp.GeneratedAsIdentityColumnConstraint 1058 ) -> str: 1059 this = "" 1060 if expression.this is not None: 1061 on_null = " ON NULL" if expression.args.get("on_null") else "" 1062 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1063 1064 start = expression.args.get("start") 1065 start = f"START WITH {start}" if start else "" 1066 increment = expression.args.get("increment") 1067 increment = f" INCREMENT BY {increment}" if increment else "" 1068 minvalue = expression.args.get("minvalue") 1069 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1070 maxvalue = expression.args.get("maxvalue") 1071 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1072 cycle = expression.args.get("cycle") 1073 cycle_sql = "" 1074 1075 if cycle is not None: 1076 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1077 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1078 1079 sequence_opts = "" 1080 if start or increment or cycle_sql: 1081 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1082 sequence_opts = f" ({sequence_opts.strip()})" 1083 1084 expr = self.sql(expression, "expression") 1085 expr = f"({expr})" if expr else "IDENTITY" 1086 1087 return f"GENERATED{this} AS {expr}{sequence_opts}" 1088 1089 def generatedasrowcolumnconstraint_sql( 1090 self, expression: exp.GeneratedAsRowColumnConstraint 1091 ) -> str: 1092 start = "START" if expression.args.get("start") else "END" 1093 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1094 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1095 1096 def periodforsystemtimeconstraint_sql( 1097 self, expression: exp.PeriodForSystemTimeConstraint 1098 ) -> str: 1099 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1100 1101 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1102 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1103 1104 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1105 desc = expression.args.get("desc") 1106 if desc is not None: 1107 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1108 options = self.expressions(expression, key="options", flat=True, sep=" ") 1109 options = f" {options}" if options else "" 1110 return f"PRIMARY KEY{options}" 1111 1112 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1113 this = self.sql(expression, "this") 1114 this = f" {this}" if this else "" 1115 index_type = expression.args.get("index_type") 1116 index_type = f" USING {index_type}" if index_type else "" 1117 on_conflict = self.sql(expression, "on_conflict") 1118 on_conflict = f" {on_conflict}" if on_conflict else "" 1119 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1120 options = self.expressions(expression, key="options", flat=True, sep=" ") 1121 options = f" {options}" if options else "" 1122 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1123 1124 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1125 return self.sql(expression, "this") 1126 1127 def create_sql(self, expression: exp.Create) -> str: 1128 kind = self.sql(expression, "kind") 1129 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1130 properties = expression.args.get("properties") 1131 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1132 1133 this = self.createable_sql(expression, properties_locs) 1134 1135 properties_sql = "" 1136 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1137 exp.Properties.Location.POST_WITH 1138 ): 1139 properties_sql = self.sql( 1140 exp.Properties( 1141 expressions=[ 1142 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1143 *properties_locs[exp.Properties.Location.POST_WITH], 1144 ] 1145 ) 1146 ) 1147 1148 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1149 properties_sql = self.sep() + properties_sql 1150 elif not self.pretty: 1151 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1152 properties_sql = f" {properties_sql}" 1153 1154 begin = " BEGIN" if expression.args.get("begin") else "" 1155 end = " END" if expression.args.get("end") else "" 1156 1157 expression_sql = self.sql(expression, "expression") 1158 if expression_sql: 1159 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1160 1161 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1162 postalias_props_sql = "" 1163 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1164 postalias_props_sql = self.properties( 1165 exp.Properties( 1166 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1167 ), 1168 wrapped=False, 1169 ) 1170 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1171 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1172 1173 postindex_props_sql = "" 1174 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1175 postindex_props_sql = self.properties( 1176 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1177 wrapped=False, 1178 prefix=" ", 1179 ) 1180 1181 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1182 indexes = f" {indexes}" if indexes else "" 1183 index_sql = indexes + postindex_props_sql 1184 1185 replace = " OR REPLACE" if expression.args.get("replace") else "" 1186 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1187 unique = " UNIQUE" if expression.args.get("unique") else "" 1188 1189 clustered = expression.args.get("clustered") 1190 if clustered is None: 1191 clustered_sql = "" 1192 elif clustered: 1193 clustered_sql = " CLUSTERED COLUMNSTORE" 1194 else: 1195 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1196 1197 postcreate_props_sql = "" 1198 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1199 postcreate_props_sql = self.properties( 1200 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1201 sep=" ", 1202 prefix=" ", 1203 wrapped=False, 1204 ) 1205 1206 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1207 1208 postexpression_props_sql = "" 1209 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1210 postexpression_props_sql = self.properties( 1211 exp.Properties( 1212 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1213 ), 1214 sep=" ", 1215 prefix=" ", 1216 wrapped=False, 1217 ) 1218 1219 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1220 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1221 no_schema_binding = ( 1222 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1223 ) 1224 1225 clone = self.sql(expression, "clone") 1226 clone = f" {clone}" if clone else "" 1227 1228 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1229 properties_expression = f"{expression_sql}{properties_sql}" 1230 else: 1231 properties_expression = f"{properties_sql}{expression_sql}" 1232 1233 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1234 return self.prepend_ctes(expression, expression_sql) 1235 1236 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1237 start = self.sql(expression, "start") 1238 start = f"START WITH {start}" if start else "" 1239 increment = self.sql(expression, "increment") 1240 increment = f" INCREMENT BY {increment}" if increment else "" 1241 minvalue = self.sql(expression, "minvalue") 1242 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1243 maxvalue = self.sql(expression, "maxvalue") 1244 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1245 owned = self.sql(expression, "owned") 1246 owned = f" OWNED BY {owned}" if owned else "" 1247 1248 cache = expression.args.get("cache") 1249 if cache is None: 1250 cache_str = "" 1251 elif cache is True: 1252 cache_str = " CACHE" 1253 else: 1254 cache_str = f" CACHE {cache}" 1255 1256 options = self.expressions(expression, key="options", flat=True, sep=" ") 1257 options = f" {options}" if options else "" 1258 1259 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1260 1261 def clone_sql(self, expression: exp.Clone) -> str: 1262 this = self.sql(expression, "this") 1263 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1264 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1265 return f"{shallow}{keyword} {this}" 1266 1267 def describe_sql(self, expression: exp.Describe) -> str: 1268 style = expression.args.get("style") 1269 style = f" {style}" if style else "" 1270 partition = self.sql(expression, "partition") 1271 partition = f" {partition}" if partition else "" 1272 format = self.sql(expression, "format") 1273 format = f" {format}" if format else "" 1274 1275 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1276 1277 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1278 tag = self.sql(expression, "tag") 1279 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1280 1281 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1282 with_ = self.sql(expression, "with") 1283 if with_: 1284 sql = f"{with_}{self.sep()}{sql}" 1285 return sql 1286 1287 def with_sql(self, expression: exp.With) -> str: 1288 sql = self.expressions(expression, flat=True) 1289 recursive = ( 1290 "RECURSIVE " 1291 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1292 else "" 1293 ) 1294 search = self.sql(expression, "search") 1295 search = f" {search}" if search else "" 1296 1297 return f"WITH {recursive}{sql}{search}" 1298 1299 def cte_sql(self, expression: exp.CTE) -> str: 1300 alias = expression.args.get("alias") 1301 if alias: 1302 alias.add_comments(expression.pop_comments()) 1303 1304 alias_sql = self.sql(expression, "alias") 1305 1306 materialized = expression.args.get("materialized") 1307 if materialized is False: 1308 materialized = "NOT MATERIALIZED " 1309 elif materialized: 1310 materialized = "MATERIALIZED " 1311 1312 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1313 1314 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1315 alias = self.sql(expression, "this") 1316 columns = self.expressions(expression, key="columns", flat=True) 1317 columns = f"({columns})" if columns else "" 1318 1319 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1320 columns = "" 1321 self.unsupported("Named columns are not supported in table alias.") 1322 1323 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1324 alias = self._next_name() 1325 1326 return f"{alias}{columns}" 1327 1328 def bitstring_sql(self, expression: exp.BitString) -> str: 1329 this = self.sql(expression, "this") 1330 if self.dialect.BIT_START: 1331 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1332 return f"{int(this, 2)}" 1333 1334 def hexstring_sql( 1335 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1336 ) -> str: 1337 this = self.sql(expression, "this") 1338 is_integer_type = expression.args.get("is_integer") 1339 1340 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1341 not self.dialect.HEX_START and not binary_function_repr 1342 ): 1343 # Integer representation will be returned if: 1344 # - The read dialect treats the hex value as integer literal but not the write 1345 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1346 return f"{int(this, 16)}" 1347 1348 if not is_integer_type: 1349 # Read dialect treats the hex value as BINARY/BLOB 1350 if binary_function_repr: 1351 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1352 return self.func(binary_function_repr, exp.Literal.string(this)) 1353 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1354 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1355 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1356 1357 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1358 1359 def bytestring_sql(self, expression: exp.ByteString) -> str: 1360 this = self.sql(expression, "this") 1361 if self.dialect.BYTE_START: 1362 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1363 return this 1364 1365 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1366 this = self.sql(expression, "this") 1367 escape = expression.args.get("escape") 1368 1369 if self.dialect.UNICODE_START: 1370 escape_substitute = r"\\\1" 1371 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1372 else: 1373 escape_substitute = r"\\u\1" 1374 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1375 1376 if escape: 1377 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1378 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1379 else: 1380 escape_pattern = ESCAPED_UNICODE_RE 1381 escape_sql = "" 1382 1383 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1384 this = escape_pattern.sub(escape_substitute, this) 1385 1386 return f"{left_quote}{this}{right_quote}{escape_sql}" 1387 1388 def rawstring_sql(self, expression: exp.RawString) -> str: 1389 string = expression.this 1390 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1391 string = string.replace("\\", "\\\\") 1392 1393 string = self.escape_str(string, escape_backslash=False) 1394 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1395 1396 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1397 this = self.sql(expression, "this") 1398 specifier = self.sql(expression, "expression") 1399 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1400 return f"{this}{specifier}" 1401 1402 def datatype_sql(self, expression: exp.DataType) -> str: 1403 nested = "" 1404 values = "" 1405 interior = self.expressions(expression, flat=True) 1406 1407 type_value = expression.this 1408 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1409 type_sql = self.sql(expression, "kind") 1410 else: 1411 type_sql = ( 1412 self.TYPE_MAPPING.get(type_value, type_value.value) 1413 if isinstance(type_value, exp.DataType.Type) 1414 else type_value 1415 ) 1416 1417 if interior: 1418 if expression.args.get("nested"): 1419 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1420 if expression.args.get("values") is not None: 1421 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1422 values = self.expressions(expression, key="values", flat=True) 1423 values = f"{delimiters[0]}{values}{delimiters[1]}" 1424 elif type_value == exp.DataType.Type.INTERVAL: 1425 nested = f" {interior}" 1426 else: 1427 nested = f"({interior})" 1428 1429 type_sql = f"{type_sql}{nested}{values}" 1430 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1431 exp.DataType.Type.TIMETZ, 1432 exp.DataType.Type.TIMESTAMPTZ, 1433 ): 1434 type_sql = f"{type_sql} WITH TIME ZONE" 1435 1436 return type_sql 1437 1438 def directory_sql(self, expression: exp.Directory) -> str: 1439 local = "LOCAL " if expression.args.get("local") else "" 1440 row_format = self.sql(expression, "row_format") 1441 row_format = f" {row_format}" if row_format else "" 1442 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1443 1444 def delete_sql(self, expression: exp.Delete) -> str: 1445 this = self.sql(expression, "this") 1446 this = f" FROM {this}" if this else "" 1447 using = self.sql(expression, "using") 1448 using = f" USING {using}" if using else "" 1449 cluster = self.sql(expression, "cluster") 1450 cluster = f" {cluster}" if cluster else "" 1451 where = self.sql(expression, "where") 1452 returning = self.sql(expression, "returning") 1453 limit = self.sql(expression, "limit") 1454 tables = self.expressions(expression, key="tables") 1455 tables = f" {tables}" if tables else "" 1456 if self.RETURNING_END: 1457 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1458 else: 1459 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1460 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1461 1462 def drop_sql(self, expression: exp.Drop) -> str: 1463 this = self.sql(expression, "this") 1464 expressions = self.expressions(expression, flat=True) 1465 expressions = f" ({expressions})" if expressions else "" 1466 kind = expression.args["kind"] 1467 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1468 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1469 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1470 on_cluster = self.sql(expression, "cluster") 1471 on_cluster = f" {on_cluster}" if on_cluster else "" 1472 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1473 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1474 cascade = " CASCADE" if expression.args.get("cascade") else "" 1475 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1476 purge = " PURGE" if expression.args.get("purge") else "" 1477 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1478 1479 def set_operation(self, expression: exp.SetOperation) -> str: 1480 op_type = type(expression) 1481 op_name = op_type.key.upper() 1482 1483 distinct = expression.args.get("distinct") 1484 if ( 1485 distinct is False 1486 and op_type in (exp.Except, exp.Intersect) 1487 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1488 ): 1489 self.unsupported(f"{op_name} ALL is not supported") 1490 1491 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1492 1493 if distinct is None: 1494 distinct = default_distinct 1495 if distinct is None: 1496 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1497 1498 if distinct is default_distinct: 1499 distinct_or_all = "" 1500 else: 1501 distinct_or_all = " DISTINCT" if distinct else " ALL" 1502 1503 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1504 side_kind = f"{side_kind} " if side_kind else "" 1505 1506 by_name = " BY NAME" if expression.args.get("by_name") else "" 1507 on = self.expressions(expression, key="on", flat=True) 1508 on = f" ON ({on})" if on else "" 1509 1510 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1511 1512 def set_operations(self, expression: exp.SetOperation) -> str: 1513 if not self.SET_OP_MODIFIERS: 1514 limit = expression.args.get("limit") 1515 order = expression.args.get("order") 1516 1517 if limit or order: 1518 select = self._move_ctes_to_top_level( 1519 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1520 ) 1521 1522 if limit: 1523 select = select.limit(limit.pop(), copy=False) 1524 if order: 1525 select = select.order_by(order.pop(), copy=False) 1526 return self.sql(select) 1527 1528 sqls: t.List[str] = [] 1529 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1530 1531 while stack: 1532 node = stack.pop() 1533 1534 if isinstance(node, exp.SetOperation): 1535 stack.append(node.expression) 1536 stack.append( 1537 self.maybe_comment( 1538 self.set_operation(node), comments=node.comments, separated=True 1539 ) 1540 ) 1541 stack.append(node.this) 1542 else: 1543 sqls.append(self.sql(node)) 1544 1545 this = self.sep().join(sqls) 1546 this = self.query_modifiers(expression, this) 1547 return self.prepend_ctes(expression, this) 1548 1549 def fetch_sql(self, expression: exp.Fetch) -> str: 1550 direction = expression.args.get("direction") 1551 direction = f" {direction}" if direction else "" 1552 count = self.sql(expression, "count") 1553 count = f" {count}" if count else "" 1554 limit_options = self.sql(expression, "limit_options") 1555 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1556 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1557 1558 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1559 percent = " PERCENT" if expression.args.get("percent") else "" 1560 rows = " ROWS" if expression.args.get("rows") else "" 1561 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1562 if not with_ties and rows: 1563 with_ties = " ONLY" 1564 return f"{percent}{rows}{with_ties}" 1565 1566 def filter_sql(self, expression: exp.Filter) -> str: 1567 if self.AGGREGATE_FILTER_SUPPORTED: 1568 this = self.sql(expression, "this") 1569 where = self.sql(expression, "expression").strip() 1570 return f"{this} FILTER({where})" 1571 1572 agg = expression.this 1573 agg_arg = agg.this 1574 cond = expression.expression.this 1575 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1576 return self.sql(agg) 1577 1578 def hint_sql(self, expression: exp.Hint) -> str: 1579 if not self.QUERY_HINTS: 1580 self.unsupported("Hints are not supported") 1581 return "" 1582 1583 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1584 1585 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1586 using = self.sql(expression, "using") 1587 using = f" USING {using}" if using else "" 1588 columns = self.expressions(expression, key="columns", flat=True) 1589 columns = f"({columns})" if columns else "" 1590 partition_by = self.expressions(expression, key="partition_by", flat=True) 1591 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1592 where = self.sql(expression, "where") 1593 include = self.expressions(expression, key="include", flat=True) 1594 if include: 1595 include = f" INCLUDE ({include})" 1596 with_storage = self.expressions(expression, key="with_storage", flat=True) 1597 with_storage = f" WITH ({with_storage})" if with_storage else "" 1598 tablespace = self.sql(expression, "tablespace") 1599 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1600 on = self.sql(expression, "on") 1601 on = f" ON {on}" if on else "" 1602 1603 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1604 1605 def index_sql(self, expression: exp.Index) -> str: 1606 unique = "UNIQUE " if expression.args.get("unique") else "" 1607 primary = "PRIMARY " if expression.args.get("primary") else "" 1608 amp = "AMP " if expression.args.get("amp") else "" 1609 name = self.sql(expression, "this") 1610 name = f"{name} " if name else "" 1611 table = self.sql(expression, "table") 1612 table = f"{self.INDEX_ON} {table}" if table else "" 1613 1614 index = "INDEX " if not table else "" 1615 1616 params = self.sql(expression, "params") 1617 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1618 1619 def identifier_sql(self, expression: exp.Identifier) -> str: 1620 text = expression.name 1621 lower = text.lower() 1622 text = lower if self.normalize and not expression.quoted else text 1623 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1624 if ( 1625 expression.quoted 1626 or self.dialect.can_identify(text, self.identify) 1627 or lower in self.RESERVED_KEYWORDS 1628 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1629 ): 1630 text = f"{self._identifier_start}{text}{self._identifier_end}" 1631 return text 1632 1633 def hex_sql(self, expression: exp.Hex) -> str: 1634 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1635 if self.dialect.HEX_LOWERCASE: 1636 text = self.func("LOWER", text) 1637 1638 return text 1639 1640 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1641 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1642 if not self.dialect.HEX_LOWERCASE: 1643 text = self.func("LOWER", text) 1644 return text 1645 1646 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1647 input_format = self.sql(expression, "input_format") 1648 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1649 output_format = self.sql(expression, "output_format") 1650 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1651 return self.sep().join((input_format, output_format)) 1652 1653 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1654 string = self.sql(exp.Literal.string(expression.name)) 1655 return f"{prefix}{string}" 1656 1657 def partition_sql(self, expression: exp.Partition) -> str: 1658 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1659 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1660 1661 def properties_sql(self, expression: exp.Properties) -> str: 1662 root_properties = [] 1663 with_properties = [] 1664 1665 for p in expression.expressions: 1666 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1667 if p_loc == exp.Properties.Location.POST_WITH: 1668 with_properties.append(p) 1669 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1670 root_properties.append(p) 1671 1672 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1673 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1674 1675 if root_props and with_props and not self.pretty: 1676 with_props = " " + with_props 1677 1678 return root_props + with_props 1679 1680 def root_properties(self, properties: exp.Properties) -> str: 1681 if properties.expressions: 1682 return self.expressions(properties, indent=False, sep=" ") 1683 return "" 1684 1685 def properties( 1686 self, 1687 properties: exp.Properties, 1688 prefix: str = "", 1689 sep: str = ", ", 1690 suffix: str = "", 1691 wrapped: bool = True, 1692 ) -> str: 1693 if properties.expressions: 1694 expressions = self.expressions(properties, sep=sep, indent=False) 1695 if expressions: 1696 expressions = self.wrap(expressions) if wrapped else expressions 1697 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1698 return "" 1699 1700 def with_properties(self, properties: exp.Properties) -> str: 1701 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1702 1703 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1704 properties_locs = defaultdict(list) 1705 for p in properties.expressions: 1706 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1707 if p_loc != exp.Properties.Location.UNSUPPORTED: 1708 properties_locs[p_loc].append(p) 1709 else: 1710 self.unsupported(f"Unsupported property {p.key}") 1711 1712 return properties_locs 1713 1714 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1715 if isinstance(expression.this, exp.Dot): 1716 return self.sql(expression, "this") 1717 return f"'{expression.name}'" if string_key else expression.name 1718 1719 def property_sql(self, expression: exp.Property) -> str: 1720 property_cls = expression.__class__ 1721 if property_cls == exp.Property: 1722 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1723 1724 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1725 if not property_name: 1726 self.unsupported(f"Unsupported property {expression.key}") 1727 1728 return f"{property_name}={self.sql(expression, 'this')}" 1729 1730 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1731 if self.SUPPORTS_CREATE_TABLE_LIKE: 1732 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1733 options = f" {options}" if options else "" 1734 1735 like = f"LIKE {self.sql(expression, 'this')}{options}" 1736 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1737 like = f"({like})" 1738 1739 return like 1740 1741 if expression.expressions: 1742 self.unsupported("Transpilation of LIKE property options is unsupported") 1743 1744 select = exp.select("*").from_(expression.this).limit(0) 1745 return f"AS {self.sql(select)}" 1746 1747 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1748 no = "NO " if expression.args.get("no") else "" 1749 protection = " PROTECTION" if expression.args.get("protection") else "" 1750 return f"{no}FALLBACK{protection}" 1751 1752 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1753 no = "NO " if expression.args.get("no") else "" 1754 local = expression.args.get("local") 1755 local = f"{local} " if local else "" 1756 dual = "DUAL " if expression.args.get("dual") else "" 1757 before = "BEFORE " if expression.args.get("before") else "" 1758 after = "AFTER " if expression.args.get("after") else "" 1759 return f"{no}{local}{dual}{before}{after}JOURNAL" 1760 1761 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1762 freespace = self.sql(expression, "this") 1763 percent = " PERCENT" if expression.args.get("percent") else "" 1764 return f"FREESPACE={freespace}{percent}" 1765 1766 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1767 if expression.args.get("default"): 1768 property = "DEFAULT" 1769 elif expression.args.get("on"): 1770 property = "ON" 1771 else: 1772 property = "OFF" 1773 return f"CHECKSUM={property}" 1774 1775 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1776 if expression.args.get("no"): 1777 return "NO MERGEBLOCKRATIO" 1778 if expression.args.get("default"): 1779 return "DEFAULT MERGEBLOCKRATIO" 1780 1781 percent = " PERCENT" if expression.args.get("percent") else "" 1782 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1783 1784 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1785 default = expression.args.get("default") 1786 minimum = expression.args.get("minimum") 1787 maximum = expression.args.get("maximum") 1788 if default or minimum or maximum: 1789 if default: 1790 prop = "DEFAULT" 1791 elif minimum: 1792 prop = "MINIMUM" 1793 else: 1794 prop = "MAXIMUM" 1795 return f"{prop} DATABLOCKSIZE" 1796 units = expression.args.get("units") 1797 units = f" {units}" if units else "" 1798 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1799 1800 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1801 autotemp = expression.args.get("autotemp") 1802 always = expression.args.get("always") 1803 default = expression.args.get("default") 1804 manual = expression.args.get("manual") 1805 never = expression.args.get("never") 1806 1807 if autotemp is not None: 1808 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1809 elif always: 1810 prop = "ALWAYS" 1811 elif default: 1812 prop = "DEFAULT" 1813 elif manual: 1814 prop = "MANUAL" 1815 elif never: 1816 prop = "NEVER" 1817 return f"BLOCKCOMPRESSION={prop}" 1818 1819 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1820 no = expression.args.get("no") 1821 no = " NO" if no else "" 1822 concurrent = expression.args.get("concurrent") 1823 concurrent = " CONCURRENT" if concurrent else "" 1824 target = self.sql(expression, "target") 1825 target = f" {target}" if target else "" 1826 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1827 1828 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1829 if isinstance(expression.this, list): 1830 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1831 if expression.this: 1832 modulus = self.sql(expression, "this") 1833 remainder = self.sql(expression, "expression") 1834 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1835 1836 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1837 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1838 return f"FROM ({from_expressions}) TO ({to_expressions})" 1839 1840 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1841 this = self.sql(expression, "this") 1842 1843 for_values_or_default = expression.expression 1844 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1845 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1846 else: 1847 for_values_or_default = " DEFAULT" 1848 1849 return f"PARTITION OF {this}{for_values_or_default}" 1850 1851 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1852 kind = expression.args.get("kind") 1853 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1854 for_or_in = expression.args.get("for_or_in") 1855 for_or_in = f" {for_or_in}" if for_or_in else "" 1856 lock_type = expression.args.get("lock_type") 1857 override = " OVERRIDE" if expression.args.get("override") else "" 1858 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1859 1860 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1861 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1862 statistics = expression.args.get("statistics") 1863 statistics_sql = "" 1864 if statistics is not None: 1865 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1866 return f"{data_sql}{statistics_sql}" 1867 1868 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1869 this = self.sql(expression, "this") 1870 this = f"HISTORY_TABLE={this}" if this else "" 1871 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1872 data_consistency = ( 1873 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1874 ) 1875 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1876 retention_period = ( 1877 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1878 ) 1879 1880 if this: 1881 on_sql = self.func("ON", this, data_consistency, retention_period) 1882 else: 1883 on_sql = "ON" if expression.args.get("on") else "OFF" 1884 1885 sql = f"SYSTEM_VERSIONING={on_sql}" 1886 1887 return f"WITH({sql})" if expression.args.get("with") else sql 1888 1889 def insert_sql(self, expression: exp.Insert) -> str: 1890 hint = self.sql(expression, "hint") 1891 overwrite = expression.args.get("overwrite") 1892 1893 if isinstance(expression.this, exp.Directory): 1894 this = " OVERWRITE" if overwrite else " INTO" 1895 else: 1896 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1897 1898 stored = self.sql(expression, "stored") 1899 stored = f" {stored}" if stored else "" 1900 alternative = expression.args.get("alternative") 1901 alternative = f" OR {alternative}" if alternative else "" 1902 ignore = " IGNORE" if expression.args.get("ignore") else "" 1903 is_function = expression.args.get("is_function") 1904 if is_function: 1905 this = f"{this} FUNCTION" 1906 this = f"{this} {self.sql(expression, 'this')}" 1907 1908 exists = " IF EXISTS" if expression.args.get("exists") else "" 1909 where = self.sql(expression, "where") 1910 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1911 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1912 on_conflict = self.sql(expression, "conflict") 1913 on_conflict = f" {on_conflict}" if on_conflict else "" 1914 by_name = " BY NAME" if expression.args.get("by_name") else "" 1915 returning = self.sql(expression, "returning") 1916 1917 if self.RETURNING_END: 1918 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1919 else: 1920 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1921 1922 partition_by = self.sql(expression, "partition") 1923 partition_by = f" {partition_by}" if partition_by else "" 1924 settings = self.sql(expression, "settings") 1925 settings = f" {settings}" if settings else "" 1926 1927 source = self.sql(expression, "source") 1928 source = f"TABLE {source}" if source else "" 1929 1930 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1931 return self.prepend_ctes(expression, sql) 1932 1933 def introducer_sql(self, expression: exp.Introducer) -> str: 1934 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1935 1936 def kill_sql(self, expression: exp.Kill) -> str: 1937 kind = self.sql(expression, "kind") 1938 kind = f" {kind}" if kind else "" 1939 this = self.sql(expression, "this") 1940 this = f" {this}" if this else "" 1941 return f"KILL{kind}{this}" 1942 1943 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1944 return expression.name 1945 1946 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1947 return expression.name 1948 1949 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1950 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1951 1952 constraint = self.sql(expression, "constraint") 1953 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1954 1955 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1956 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1957 action = self.sql(expression, "action") 1958 1959 expressions = self.expressions(expression, flat=True) 1960 if expressions: 1961 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1962 expressions = f" {set_keyword}{expressions}" 1963 1964 where = self.sql(expression, "where") 1965 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1966 1967 def returning_sql(self, expression: exp.Returning) -> str: 1968 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1969 1970 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1971 fields = self.sql(expression, "fields") 1972 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1973 escaped = self.sql(expression, "escaped") 1974 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1975 items = self.sql(expression, "collection_items") 1976 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1977 keys = self.sql(expression, "map_keys") 1978 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1979 lines = self.sql(expression, "lines") 1980 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1981 null = self.sql(expression, "null") 1982 null = f" NULL DEFINED AS {null}" if null else "" 1983 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1984 1985 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1986 return f"WITH ({self.expressions(expression, flat=True)})" 1987 1988 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1989 this = f"{self.sql(expression, 'this')} INDEX" 1990 target = self.sql(expression, "target") 1991 target = f" FOR {target}" if target else "" 1992 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1993 1994 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1995 this = self.sql(expression, "this") 1996 kind = self.sql(expression, "kind") 1997 expr = self.sql(expression, "expression") 1998 return f"{this} ({kind} => {expr})" 1999 2000 def table_parts(self, expression: exp.Table) -> str: 2001 return ".".join( 2002 self.sql(part) 2003 for part in ( 2004 expression.args.get("catalog"), 2005 expression.args.get("db"), 2006 expression.args.get("this"), 2007 ) 2008 if part is not None 2009 ) 2010 2011 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2012 table = self.table_parts(expression) 2013 only = "ONLY " if expression.args.get("only") else "" 2014 partition = self.sql(expression, "partition") 2015 partition = f" {partition}" if partition else "" 2016 version = self.sql(expression, "version") 2017 version = f" {version}" if version else "" 2018 alias = self.sql(expression, "alias") 2019 alias = f"{sep}{alias}" if alias else "" 2020 2021 sample = self.sql(expression, "sample") 2022 if self.dialect.ALIAS_POST_TABLESAMPLE: 2023 sample_pre_alias = sample 2024 sample_post_alias = "" 2025 else: 2026 sample_pre_alias = "" 2027 sample_post_alias = sample 2028 2029 hints = self.expressions(expression, key="hints", sep=" ") 2030 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2031 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2032 joins = self.indent( 2033 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2034 ) 2035 laterals = self.expressions(expression, key="laterals", sep="") 2036 2037 file_format = self.sql(expression, "format") 2038 if file_format: 2039 pattern = self.sql(expression, "pattern") 2040 pattern = f", PATTERN => {pattern}" if pattern else "" 2041 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2042 2043 ordinality = expression.args.get("ordinality") or "" 2044 if ordinality: 2045 ordinality = f" WITH ORDINALITY{alias}" 2046 alias = "" 2047 2048 when = self.sql(expression, "when") 2049 if when: 2050 table = f"{table} {when}" 2051 2052 changes = self.sql(expression, "changes") 2053 changes = f" {changes}" if changes else "" 2054 2055 rows_from = self.expressions(expression, key="rows_from") 2056 if rows_from: 2057 table = f"ROWS FROM {self.wrap(rows_from)}" 2058 2059 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2060 2061 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2062 table = self.func("TABLE", expression.this) 2063 alias = self.sql(expression, "alias") 2064 alias = f" AS {alias}" if alias else "" 2065 sample = self.sql(expression, "sample") 2066 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2067 joins = self.indent( 2068 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2069 ) 2070 return f"{table}{alias}{pivots}{sample}{joins}" 2071 2072 def tablesample_sql( 2073 self, 2074 expression: exp.TableSample, 2075 tablesample_keyword: t.Optional[str] = None, 2076 ) -> str: 2077 method = self.sql(expression, "method") 2078 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2079 numerator = self.sql(expression, "bucket_numerator") 2080 denominator = self.sql(expression, "bucket_denominator") 2081 field = self.sql(expression, "bucket_field") 2082 field = f" ON {field}" if field else "" 2083 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2084 seed = self.sql(expression, "seed") 2085 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2086 2087 size = self.sql(expression, "size") 2088 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2089 size = f"{size} ROWS" 2090 2091 percent = self.sql(expression, "percent") 2092 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2093 percent = f"{percent} PERCENT" 2094 2095 expr = f"{bucket}{percent}{size}" 2096 if self.TABLESAMPLE_REQUIRES_PARENS: 2097 expr = f"({expr})" 2098 2099 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2100 2101 def pivot_sql(self, expression: exp.Pivot) -> str: 2102 expressions = self.expressions(expression, flat=True) 2103 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2104 2105 group = self.sql(expression, "group") 2106 2107 if expression.this: 2108 this = self.sql(expression, "this") 2109 if not expressions: 2110 return f"UNPIVOT {this}" 2111 2112 on = f"{self.seg('ON')} {expressions}" 2113 into = self.sql(expression, "into") 2114 into = f"{self.seg('INTO')} {into}" if into else "" 2115 using = self.expressions(expression, key="using", flat=True) 2116 using = f"{self.seg('USING')} {using}" if using else "" 2117 return f"{direction} {this}{on}{into}{using}{group}" 2118 2119 alias = self.sql(expression, "alias") 2120 alias = f" AS {alias}" if alias else "" 2121 2122 fields = self.expressions( 2123 expression, 2124 "fields", 2125 sep=" ", 2126 dynamic=True, 2127 new_line=True, 2128 skip_first=True, 2129 skip_last=True, 2130 ) 2131 2132 include_nulls = expression.args.get("include_nulls") 2133 if include_nulls is not None: 2134 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2135 else: 2136 nulls = "" 2137 2138 default_on_null = self.sql(expression, "default_on_null") 2139 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2140 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2141 2142 def version_sql(self, expression: exp.Version) -> str: 2143 this = f"FOR {expression.name}" 2144 kind = expression.text("kind") 2145 expr = self.sql(expression, "expression") 2146 return f"{this} {kind} {expr}" 2147 2148 def tuple_sql(self, expression: exp.Tuple) -> str: 2149 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2150 2151 def update_sql(self, expression: exp.Update) -> str: 2152 this = self.sql(expression, "this") 2153 set_sql = self.expressions(expression, flat=True) 2154 from_sql = self.sql(expression, "from") 2155 where_sql = self.sql(expression, "where") 2156 returning = self.sql(expression, "returning") 2157 order = self.sql(expression, "order") 2158 limit = self.sql(expression, "limit") 2159 if self.RETURNING_END: 2160 expression_sql = f"{from_sql}{where_sql}{returning}" 2161 else: 2162 expression_sql = f"{returning}{from_sql}{where_sql}" 2163 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2164 return self.prepend_ctes(expression, sql) 2165 2166 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2167 values_as_table = values_as_table and self.VALUES_AS_TABLE 2168 2169 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2170 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2171 args = self.expressions(expression) 2172 alias = self.sql(expression, "alias") 2173 values = f"VALUES{self.seg('')}{args}" 2174 values = ( 2175 f"({values})" 2176 if self.WRAP_DERIVED_VALUES 2177 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2178 else values 2179 ) 2180 return f"{values} AS {alias}" if alias else values 2181 2182 # Converts `VALUES...` expression into a series of select unions. 2183 alias_node = expression.args.get("alias") 2184 column_names = alias_node and alias_node.columns 2185 2186 selects: t.List[exp.Query] = [] 2187 2188 for i, tup in enumerate(expression.expressions): 2189 row = tup.expressions 2190 2191 if i == 0 and column_names: 2192 row = [ 2193 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2194 ] 2195 2196 selects.append(exp.Select(expressions=row)) 2197 2198 if self.pretty: 2199 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2200 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2201 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2202 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2203 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2204 2205 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2206 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2207 return f"({unions}){alias}" 2208 2209 def var_sql(self, expression: exp.Var) -> str: 2210 return self.sql(expression, "this") 2211 2212 @unsupported_args("expressions") 2213 def into_sql(self, expression: exp.Into) -> str: 2214 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2215 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2216 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2217 2218 def from_sql(self, expression: exp.From) -> str: 2219 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2220 2221 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2222 grouping_sets = self.expressions(expression, indent=False) 2223 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2224 2225 def rollup_sql(self, expression: exp.Rollup) -> str: 2226 expressions = self.expressions(expression, indent=False) 2227 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2228 2229 def cube_sql(self, expression: exp.Cube) -> str: 2230 expressions = self.expressions(expression, indent=False) 2231 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2232 2233 def group_sql(self, expression: exp.Group) -> str: 2234 group_by_all = expression.args.get("all") 2235 if group_by_all is True: 2236 modifier = " ALL" 2237 elif group_by_all is False: 2238 modifier = " DISTINCT" 2239 else: 2240 modifier = "" 2241 2242 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2243 2244 grouping_sets = self.expressions(expression, key="grouping_sets") 2245 cube = self.expressions(expression, key="cube") 2246 rollup = self.expressions(expression, key="rollup") 2247 2248 groupings = csv( 2249 self.seg(grouping_sets) if grouping_sets else "", 2250 self.seg(cube) if cube else "", 2251 self.seg(rollup) if rollup else "", 2252 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2253 sep=self.GROUPINGS_SEP, 2254 ) 2255 2256 if ( 2257 expression.expressions 2258 and groupings 2259 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2260 ): 2261 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2262 2263 return f"{group_by}{groupings}" 2264 2265 def having_sql(self, expression: exp.Having) -> str: 2266 this = self.indent(self.sql(expression, "this")) 2267 return f"{self.seg('HAVING')}{self.sep()}{this}" 2268 2269 def connect_sql(self, expression: exp.Connect) -> str: 2270 start = self.sql(expression, "start") 2271 start = self.seg(f"START WITH {start}") if start else "" 2272 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2273 connect = self.sql(expression, "connect") 2274 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2275 return start + connect 2276 2277 def prior_sql(self, expression: exp.Prior) -> str: 2278 return f"PRIOR {self.sql(expression, 'this')}" 2279 2280 def join_sql(self, expression: exp.Join) -> str: 2281 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2282 side = None 2283 else: 2284 side = expression.side 2285 2286 op_sql = " ".join( 2287 op 2288 for op in ( 2289 expression.method, 2290 "GLOBAL" if expression.args.get("global") else None, 2291 side, 2292 expression.kind, 2293 expression.hint if self.JOIN_HINTS else None, 2294 ) 2295 if op 2296 ) 2297 match_cond = self.sql(expression, "match_condition") 2298 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2299 on_sql = self.sql(expression, "on") 2300 using = expression.args.get("using") 2301 2302 if not on_sql and using: 2303 on_sql = csv(*(self.sql(column) for column in using)) 2304 2305 this = expression.this 2306 this_sql = self.sql(this) 2307 2308 exprs = self.expressions(expression) 2309 if exprs: 2310 this_sql = f"{this_sql},{self.seg(exprs)}" 2311 2312 if on_sql: 2313 on_sql = self.indent(on_sql, skip_first=True) 2314 space = self.seg(" " * self.pad) if self.pretty else " " 2315 if using: 2316 on_sql = f"{space}USING ({on_sql})" 2317 else: 2318 on_sql = f"{space}ON {on_sql}" 2319 elif not op_sql: 2320 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2321 return f" {this_sql}" 2322 2323 return f", {this_sql}" 2324 2325 if op_sql != "STRAIGHT_JOIN": 2326 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2327 2328 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2329 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2330 2331 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2332 args = self.expressions(expression, flat=True) 2333 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2334 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2335 2336 def lateral_op(self, expression: exp.Lateral) -> str: 2337 cross_apply = expression.args.get("cross_apply") 2338 2339 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2340 if cross_apply is True: 2341 op = "INNER JOIN " 2342 elif cross_apply is False: 2343 op = "LEFT JOIN " 2344 else: 2345 op = "" 2346 2347 return f"{op}LATERAL" 2348 2349 def lateral_sql(self, expression: exp.Lateral) -> str: 2350 this = self.sql(expression, "this") 2351 2352 if expression.args.get("view"): 2353 alias = expression.args["alias"] 2354 columns = self.expressions(alias, key="columns", flat=True) 2355 table = f" {alias.name}" if alias.name else "" 2356 columns = f" AS {columns}" if columns else "" 2357 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2358 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2359 2360 alias = self.sql(expression, "alias") 2361 alias = f" AS {alias}" if alias else "" 2362 2363 ordinality = expression.args.get("ordinality") or "" 2364 if ordinality: 2365 ordinality = f" WITH ORDINALITY{alias}" 2366 alias = "" 2367 2368 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2369 2370 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2371 this = self.sql(expression, "this") 2372 2373 args = [ 2374 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2375 for e in (expression.args.get(k) for k in ("offset", "expression")) 2376 if e 2377 ] 2378 2379 args_sql = ", ".join(self.sql(e) for e in args) 2380 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2381 expressions = self.expressions(expression, flat=True) 2382 limit_options = self.sql(expression, "limit_options") 2383 expressions = f" BY {expressions}" if expressions else "" 2384 2385 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2386 2387 def offset_sql(self, expression: exp.Offset) -> str: 2388 this = self.sql(expression, "this") 2389 value = expression.expression 2390 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2391 expressions = self.expressions(expression, flat=True) 2392 expressions = f" BY {expressions}" if expressions else "" 2393 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2394 2395 def setitem_sql(self, expression: exp.SetItem) -> str: 2396 kind = self.sql(expression, "kind") 2397 kind = f"{kind} " if kind else "" 2398 this = self.sql(expression, "this") 2399 expressions = self.expressions(expression) 2400 collate = self.sql(expression, "collate") 2401 collate = f" COLLATE {collate}" if collate else "" 2402 global_ = "GLOBAL " if expression.args.get("global") else "" 2403 return f"{global_}{kind}{this}{expressions}{collate}" 2404 2405 def set_sql(self, expression: exp.Set) -> str: 2406 expressions = f" {self.expressions(expression, flat=True)}" 2407 tag = " TAG" if expression.args.get("tag") else "" 2408 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2409 2410 def queryband_sql(self, expression: exp.QueryBand) -> str: 2411 this = self.sql(expression, "this") 2412 update = " UPDATE" if expression.args.get("update") else "" 2413 scope = self.sql(expression, "scope") 2414 scope = f" FOR {scope}" if scope else "" 2415 2416 return f"QUERY_BAND = {this}{update}{scope}" 2417 2418 def pragma_sql(self, expression: exp.Pragma) -> str: 2419 return f"PRAGMA {self.sql(expression, 'this')}" 2420 2421 def lock_sql(self, expression: exp.Lock) -> str: 2422 if not self.LOCKING_READS_SUPPORTED: 2423 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2424 return "" 2425 2426 update = expression.args["update"] 2427 key = expression.args.get("key") 2428 if update: 2429 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2430 else: 2431 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2432 expressions = self.expressions(expression, flat=True) 2433 expressions = f" OF {expressions}" if expressions else "" 2434 wait = expression.args.get("wait") 2435 2436 if wait is not None: 2437 if isinstance(wait, exp.Literal): 2438 wait = f" WAIT {self.sql(wait)}" 2439 else: 2440 wait = " NOWAIT" if wait else " SKIP LOCKED" 2441 2442 return f"{lock_type}{expressions}{wait or ''}" 2443 2444 def literal_sql(self, expression: exp.Literal) -> str: 2445 text = expression.this or "" 2446 if expression.is_string: 2447 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2448 return text 2449 2450 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2451 if self.dialect.ESCAPED_SEQUENCES: 2452 to_escaped = self.dialect.ESCAPED_SEQUENCES 2453 text = "".join( 2454 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2455 ) 2456 2457 return self._replace_line_breaks(text).replace( 2458 self.dialect.QUOTE_END, self._escaped_quote_end 2459 ) 2460 2461 def loaddata_sql(self, expression: exp.LoadData) -> str: 2462 local = " LOCAL" if expression.args.get("local") else "" 2463 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2464 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2465 this = f" INTO TABLE {self.sql(expression, 'this')}" 2466 partition = self.sql(expression, "partition") 2467 partition = f" {partition}" if partition else "" 2468 input_format = self.sql(expression, "input_format") 2469 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2470 serde = self.sql(expression, "serde") 2471 serde = f" SERDE {serde}" if serde else "" 2472 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2473 2474 def null_sql(self, *_) -> str: 2475 return "NULL" 2476 2477 def boolean_sql(self, expression: exp.Boolean) -> str: 2478 return "TRUE" if expression.this else "FALSE" 2479 2480 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2481 this = self.sql(expression, "this") 2482 this = f"{this} " if this else this 2483 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2484 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2485 2486 def withfill_sql(self, expression: exp.WithFill) -> str: 2487 from_sql = self.sql(expression, "from") 2488 from_sql = f" FROM {from_sql}" if from_sql else "" 2489 to_sql = self.sql(expression, "to") 2490 to_sql = f" TO {to_sql}" if to_sql else "" 2491 step_sql = self.sql(expression, "step") 2492 step_sql = f" STEP {step_sql}" if step_sql else "" 2493 interpolated_values = [ 2494 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2495 if isinstance(e, exp.Alias) 2496 else self.sql(e, "this") 2497 for e in expression.args.get("interpolate") or [] 2498 ] 2499 interpolate = ( 2500 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2501 ) 2502 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2503 2504 def cluster_sql(self, expression: exp.Cluster) -> str: 2505 return self.op_expressions("CLUSTER BY", expression) 2506 2507 def distribute_sql(self, expression: exp.Distribute) -> str: 2508 return self.op_expressions("DISTRIBUTE BY", expression) 2509 2510 def sort_sql(self, expression: exp.Sort) -> str: 2511 return self.op_expressions("SORT BY", expression) 2512 2513 def ordered_sql(self, expression: exp.Ordered) -> str: 2514 desc = expression.args.get("desc") 2515 asc = not desc 2516 2517 nulls_first = expression.args.get("nulls_first") 2518 nulls_last = not nulls_first 2519 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2520 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2521 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2522 2523 this = self.sql(expression, "this") 2524 2525 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2526 nulls_sort_change = "" 2527 if nulls_first and ( 2528 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2529 ): 2530 nulls_sort_change = " NULLS FIRST" 2531 elif ( 2532 nulls_last 2533 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2534 and not nulls_are_last 2535 ): 2536 nulls_sort_change = " NULLS LAST" 2537 2538 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2539 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2540 window = expression.find_ancestor(exp.Window, exp.Select) 2541 if isinstance(window, exp.Window) and window.args.get("spec"): 2542 self.unsupported( 2543 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2544 ) 2545 nulls_sort_change = "" 2546 elif self.NULL_ORDERING_SUPPORTED is False and ( 2547 (asc and nulls_sort_change == " NULLS LAST") 2548 or (desc and nulls_sort_change == " NULLS FIRST") 2549 ): 2550 # BigQuery does not allow these ordering/nulls combinations when used under 2551 # an aggregation func or under a window containing one 2552 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2553 2554 if isinstance(ancestor, exp.Window): 2555 ancestor = ancestor.this 2556 if isinstance(ancestor, exp.AggFunc): 2557 self.unsupported( 2558 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2559 ) 2560 nulls_sort_change = "" 2561 elif self.NULL_ORDERING_SUPPORTED is None: 2562 if expression.this.is_int: 2563 self.unsupported( 2564 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2565 ) 2566 elif not isinstance(expression.this, exp.Rand): 2567 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2568 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2569 nulls_sort_change = "" 2570 2571 with_fill = self.sql(expression, "with_fill") 2572 with_fill = f" {with_fill}" if with_fill else "" 2573 2574 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2575 2576 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2577 window_frame = self.sql(expression, "window_frame") 2578 window_frame = f"{window_frame} " if window_frame else "" 2579 2580 this = self.sql(expression, "this") 2581 2582 return f"{window_frame}{this}" 2583 2584 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2585 partition = self.partition_by_sql(expression) 2586 order = self.sql(expression, "order") 2587 measures = self.expressions(expression, key="measures") 2588 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2589 rows = self.sql(expression, "rows") 2590 rows = self.seg(rows) if rows else "" 2591 after = self.sql(expression, "after") 2592 after = self.seg(after) if after else "" 2593 pattern = self.sql(expression, "pattern") 2594 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2595 definition_sqls = [ 2596 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2597 for definition in expression.args.get("define", []) 2598 ] 2599 definitions = self.expressions(sqls=definition_sqls) 2600 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2601 body = "".join( 2602 ( 2603 partition, 2604 order, 2605 measures, 2606 rows, 2607 after, 2608 pattern, 2609 define, 2610 ) 2611 ) 2612 alias = self.sql(expression, "alias") 2613 alias = f" {alias}" if alias else "" 2614 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2615 2616 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2617 limit = expression.args.get("limit") 2618 2619 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2620 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2621 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2622 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2623 2624 return csv( 2625 *sqls, 2626 *[self.sql(join) for join in expression.args.get("joins") or []], 2627 self.sql(expression, "match"), 2628 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2629 self.sql(expression, "prewhere"), 2630 self.sql(expression, "where"), 2631 self.sql(expression, "connect"), 2632 self.sql(expression, "group"), 2633 self.sql(expression, "having"), 2634 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2635 self.sql(expression, "order"), 2636 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2637 *self.after_limit_modifiers(expression), 2638 self.options_modifier(expression), 2639 self.for_modifiers(expression), 2640 sep="", 2641 ) 2642 2643 def options_modifier(self, expression: exp.Expression) -> str: 2644 options = self.expressions(expression, key="options") 2645 return f" {options}" if options else "" 2646 2647 def for_modifiers(self, expression: exp.Expression) -> str: 2648 for_modifiers = self.expressions(expression, key="for") 2649 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2650 2651 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2652 self.unsupported("Unsupported query option.") 2653 return "" 2654 2655 def offset_limit_modifiers( 2656 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2657 ) -> t.List[str]: 2658 return [ 2659 self.sql(expression, "offset") if fetch else self.sql(limit), 2660 self.sql(limit) if fetch else self.sql(expression, "offset"), 2661 ] 2662 2663 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2664 locks = self.expressions(expression, key="locks", sep=" ") 2665 locks = f" {locks}" if locks else "" 2666 return [locks, self.sql(expression, "sample")] 2667 2668 def select_sql(self, expression: exp.Select) -> str: 2669 into = expression.args.get("into") 2670 if not self.SUPPORTS_SELECT_INTO and into: 2671 into.pop() 2672 2673 hint = self.sql(expression, "hint") 2674 distinct = self.sql(expression, "distinct") 2675 distinct = f" {distinct}" if distinct else "" 2676 kind = self.sql(expression, "kind") 2677 2678 limit = expression.args.get("limit") 2679 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2680 top = self.limit_sql(limit, top=True) 2681 limit.pop() 2682 else: 2683 top = "" 2684 2685 expressions = self.expressions(expression) 2686 2687 if kind: 2688 if kind in self.SELECT_KINDS: 2689 kind = f" AS {kind}" 2690 else: 2691 if kind == "STRUCT": 2692 expressions = self.expressions( 2693 sqls=[ 2694 self.sql( 2695 exp.Struct( 2696 expressions=[ 2697 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2698 if isinstance(e, exp.Alias) 2699 else e 2700 for e in expression.expressions 2701 ] 2702 ) 2703 ) 2704 ] 2705 ) 2706 kind = "" 2707 2708 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2709 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2710 2711 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2712 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2713 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2714 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2715 sql = self.query_modifiers( 2716 expression, 2717 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2718 self.sql(expression, "into", comment=False), 2719 self.sql(expression, "from", comment=False), 2720 ) 2721 2722 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2723 if expression.args.get("with"): 2724 sql = self.maybe_comment(sql, expression) 2725 expression.pop_comments() 2726 2727 sql = self.prepend_ctes(expression, sql) 2728 2729 if not self.SUPPORTS_SELECT_INTO and into: 2730 if into.args.get("temporary"): 2731 table_kind = " TEMPORARY" 2732 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2733 table_kind = " UNLOGGED" 2734 else: 2735 table_kind = "" 2736 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2737 2738 return sql 2739 2740 def schema_sql(self, expression: exp.Schema) -> str: 2741 this = self.sql(expression, "this") 2742 sql = self.schema_columns_sql(expression) 2743 return f"{this} {sql}" if this and sql else this or sql 2744 2745 def schema_columns_sql(self, expression: exp.Schema) -> str: 2746 if expression.expressions: 2747 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2748 return "" 2749 2750 def star_sql(self, expression: exp.Star) -> str: 2751 except_ = self.expressions(expression, key="except", flat=True) 2752 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2753 replace = self.expressions(expression, key="replace", flat=True) 2754 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2755 rename = self.expressions(expression, key="rename", flat=True) 2756 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2757 return f"*{except_}{replace}{rename}" 2758 2759 def parameter_sql(self, expression: exp.Parameter) -> str: 2760 this = self.sql(expression, "this") 2761 return f"{self.PARAMETER_TOKEN}{this}" 2762 2763 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2764 this = self.sql(expression, "this") 2765 kind = expression.text("kind") 2766 if kind: 2767 kind = f"{kind}." 2768 return f"@@{kind}{this}" 2769 2770 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2771 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2772 2773 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2774 alias = self.sql(expression, "alias") 2775 alias = f"{sep}{alias}" if alias else "" 2776 sample = self.sql(expression, "sample") 2777 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2778 alias = f"{sample}{alias}" 2779 2780 # Set to None so it's not generated again by self.query_modifiers() 2781 expression.set("sample", None) 2782 2783 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2784 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2785 return self.prepend_ctes(expression, sql) 2786 2787 def qualify_sql(self, expression: exp.Qualify) -> str: 2788 this = self.indent(self.sql(expression, "this")) 2789 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2790 2791 def unnest_sql(self, expression: exp.Unnest) -> str: 2792 args = self.expressions(expression, flat=True) 2793 2794 alias = expression.args.get("alias") 2795 offset = expression.args.get("offset") 2796 2797 if self.UNNEST_WITH_ORDINALITY: 2798 if alias and isinstance(offset, exp.Expression): 2799 alias.append("columns", offset) 2800 2801 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2802 columns = alias.columns 2803 alias = self.sql(columns[0]) if columns else "" 2804 else: 2805 alias = self.sql(alias) 2806 2807 alias = f" AS {alias}" if alias else alias 2808 if self.UNNEST_WITH_ORDINALITY: 2809 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2810 else: 2811 if isinstance(offset, exp.Expression): 2812 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2813 elif offset: 2814 suffix = f"{alias} WITH OFFSET" 2815 else: 2816 suffix = alias 2817 2818 return f"UNNEST({args}){suffix}" 2819 2820 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2821 return "" 2822 2823 def where_sql(self, expression: exp.Where) -> str: 2824 this = self.indent(self.sql(expression, "this")) 2825 return f"{self.seg('WHERE')}{self.sep()}{this}" 2826 2827 def window_sql(self, expression: exp.Window) -> str: 2828 this = self.sql(expression, "this") 2829 partition = self.partition_by_sql(expression) 2830 order = expression.args.get("order") 2831 order = self.order_sql(order, flat=True) if order else "" 2832 spec = self.sql(expression, "spec") 2833 alias = self.sql(expression, "alias") 2834 over = self.sql(expression, "over") or "OVER" 2835 2836 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2837 2838 first = expression.args.get("first") 2839 if first is None: 2840 first = "" 2841 else: 2842 first = "FIRST" if first else "LAST" 2843 2844 if not partition and not order and not spec and alias: 2845 return f"{this} {alias}" 2846 2847 args = self.format_args( 2848 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2849 ) 2850 return f"{this} ({args})" 2851 2852 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2853 partition = self.expressions(expression, key="partition_by", flat=True) 2854 return f"PARTITION BY {partition}" if partition else "" 2855 2856 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2857 kind = self.sql(expression, "kind") 2858 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2859 end = ( 2860 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2861 or "CURRENT ROW" 2862 ) 2863 2864 window_spec = f"{kind} BETWEEN {start} AND {end}" 2865 2866 exclude = self.sql(expression, "exclude") 2867 if exclude: 2868 if self.SUPPORTS_WINDOW_EXCLUDE: 2869 window_spec += f" EXCLUDE {exclude}" 2870 else: 2871 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2872 2873 return window_spec 2874 2875 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2876 this = self.sql(expression, "this") 2877 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2878 return f"{this} WITHIN GROUP ({expression_sql})" 2879 2880 def between_sql(self, expression: exp.Between) -> str: 2881 this = self.sql(expression, "this") 2882 low = self.sql(expression, "low") 2883 high = self.sql(expression, "high") 2884 symmetric = expression.args.get("symmetric") 2885 2886 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2887 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2888 2889 flag = ( 2890 " SYMMETRIC" 2891 if symmetric 2892 else " ASYMMETRIC" 2893 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2894 else "" # silently drop ASYMMETRIC – semantics identical 2895 ) 2896 return f"{this} BETWEEN{flag} {low} AND {high}" 2897 2898 def bracket_offset_expressions( 2899 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2900 ) -> t.List[exp.Expression]: 2901 return apply_index_offset( 2902 expression.this, 2903 expression.expressions, 2904 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2905 dialect=self.dialect, 2906 ) 2907 2908 def bracket_sql(self, expression: exp.Bracket) -> str: 2909 expressions = self.bracket_offset_expressions(expression) 2910 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2911 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2912 2913 def all_sql(self, expression: exp.All) -> str: 2914 this = self.sql(expression, "this") 2915 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2916 this = self.wrap(this) 2917 return f"ALL {this}" 2918 2919 def any_sql(self, expression: exp.Any) -> str: 2920 this = self.sql(expression, "this") 2921 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2922 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2923 this = self.wrap(this) 2924 return f"ANY{this}" 2925 return f"ANY {this}" 2926 2927 def exists_sql(self, expression: exp.Exists) -> str: 2928 return f"EXISTS{self.wrap(expression)}" 2929 2930 def case_sql(self, expression: exp.Case) -> str: 2931 this = self.sql(expression, "this") 2932 statements = [f"CASE {this}" if this else "CASE"] 2933 2934 for e in expression.args["ifs"]: 2935 statements.append(f"WHEN {self.sql(e, 'this')}") 2936 statements.append(f"THEN {self.sql(e, 'true')}") 2937 2938 default = self.sql(expression, "default") 2939 2940 if default: 2941 statements.append(f"ELSE {default}") 2942 2943 statements.append("END") 2944 2945 if self.pretty and self.too_wide(statements): 2946 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2947 2948 return " ".join(statements) 2949 2950 def constraint_sql(self, expression: exp.Constraint) -> str: 2951 this = self.sql(expression, "this") 2952 expressions = self.expressions(expression, flat=True) 2953 return f"CONSTRAINT {this} {expressions}" 2954 2955 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2956 order = expression.args.get("order") 2957 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2958 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2959 2960 def extract_sql(self, expression: exp.Extract) -> str: 2961 from sqlglot.dialects.dialect import map_date_part 2962 2963 this = ( 2964 map_date_part(expression.this, self.dialect) 2965 if self.NORMALIZE_EXTRACT_DATE_PARTS 2966 else expression.this 2967 ) 2968 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2969 expression_sql = self.sql(expression, "expression") 2970 2971 return f"EXTRACT({this_sql} FROM {expression_sql})" 2972 2973 def trim_sql(self, expression: exp.Trim) -> str: 2974 trim_type = self.sql(expression, "position") 2975 2976 if trim_type == "LEADING": 2977 func_name = "LTRIM" 2978 elif trim_type == "TRAILING": 2979 func_name = "RTRIM" 2980 else: 2981 func_name = "TRIM" 2982 2983 return self.func(func_name, expression.this, expression.expression) 2984 2985 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2986 args = expression.expressions 2987 if isinstance(expression, exp.ConcatWs): 2988 args = args[1:] # Skip the delimiter 2989 2990 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2991 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2992 2993 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2994 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2995 2996 return args 2997 2998 def concat_sql(self, expression: exp.Concat) -> str: 2999 expressions = self.convert_concat_args(expression) 3000 3001 # Some dialects don't allow a single-argument CONCAT call 3002 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3003 return self.sql(expressions[0]) 3004 3005 return self.func("CONCAT", *expressions) 3006 3007 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3008 return self.func( 3009 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3010 ) 3011 3012 def check_sql(self, expression: exp.Check) -> str: 3013 this = self.sql(expression, key="this") 3014 return f"CHECK ({this})" 3015 3016 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3017 expressions = self.expressions(expression, flat=True) 3018 expressions = f" ({expressions})" if expressions else "" 3019 reference = self.sql(expression, "reference") 3020 reference = f" {reference}" if reference else "" 3021 delete = self.sql(expression, "delete") 3022 delete = f" ON DELETE {delete}" if delete else "" 3023 update = self.sql(expression, "update") 3024 update = f" ON UPDATE {update}" if update else "" 3025 options = self.expressions(expression, key="options", flat=True, sep=" ") 3026 options = f" {options}" if options else "" 3027 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3028 3029 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3030 expressions = self.expressions(expression, flat=True) 3031 include = self.sql(expression, "include") 3032 options = self.expressions(expression, key="options", flat=True, sep=" ") 3033 options = f" {options}" if options else "" 3034 return f"PRIMARY KEY ({expressions}){include}{options}" 3035 3036 def if_sql(self, expression: exp.If) -> str: 3037 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3038 3039 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3040 modifier = expression.args.get("modifier") 3041 modifier = f" {modifier}" if modifier else "" 3042 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3043 3044 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3045 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3046 3047 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3048 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3049 3050 if expression.args.get("escape"): 3051 path = self.escape_str(path) 3052 3053 if self.QUOTE_JSON_PATH: 3054 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3055 3056 return path 3057 3058 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3059 if isinstance(expression, exp.JSONPathPart): 3060 transform = self.TRANSFORMS.get(expression.__class__) 3061 if not callable(transform): 3062 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3063 return "" 3064 3065 return transform(self, expression) 3066 3067 if isinstance(expression, int): 3068 return str(expression) 3069 3070 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3071 escaped = expression.replace("'", "\\'") 3072 escaped = f"\\'{expression}\\'" 3073 else: 3074 escaped = expression.replace('"', '\\"') 3075 escaped = f'"{escaped}"' 3076 3077 return escaped 3078 3079 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3080 return f"{self.sql(expression, 'this')} FORMAT JSON" 3081 3082 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3083 # Output the Teradata column FORMAT override. 3084 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3085 this = self.sql(expression, "this") 3086 fmt = self.sql(expression, "format") 3087 return f"{this} (FORMAT {fmt})" 3088 3089 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3090 null_handling = expression.args.get("null_handling") 3091 null_handling = f" {null_handling}" if null_handling else "" 3092 3093 unique_keys = expression.args.get("unique_keys") 3094 if unique_keys is not None: 3095 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3096 else: 3097 unique_keys = "" 3098 3099 return_type = self.sql(expression, "return_type") 3100 return_type = f" RETURNING {return_type}" if return_type else "" 3101 encoding = self.sql(expression, "encoding") 3102 encoding = f" ENCODING {encoding}" if encoding else "" 3103 3104 return self.func( 3105 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3106 *expression.expressions, 3107 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3108 ) 3109 3110 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3111 return self.jsonobject_sql(expression) 3112 3113 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3114 null_handling = expression.args.get("null_handling") 3115 null_handling = f" {null_handling}" if null_handling else "" 3116 return_type = self.sql(expression, "return_type") 3117 return_type = f" RETURNING {return_type}" if return_type else "" 3118 strict = " STRICT" if expression.args.get("strict") else "" 3119 return self.func( 3120 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3121 ) 3122 3123 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3124 this = self.sql(expression, "this") 3125 order = self.sql(expression, "order") 3126 null_handling = expression.args.get("null_handling") 3127 null_handling = f" {null_handling}" if null_handling else "" 3128 return_type = self.sql(expression, "return_type") 3129 return_type = f" RETURNING {return_type}" if return_type else "" 3130 strict = " STRICT" if expression.args.get("strict") else "" 3131 return self.func( 3132 "JSON_ARRAYAGG", 3133 this, 3134 suffix=f"{order}{null_handling}{return_type}{strict})", 3135 ) 3136 3137 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3138 path = self.sql(expression, "path") 3139 path = f" PATH {path}" if path else "" 3140 nested_schema = self.sql(expression, "nested_schema") 3141 3142 if nested_schema: 3143 return f"NESTED{path} {nested_schema}" 3144 3145 this = self.sql(expression, "this") 3146 kind = self.sql(expression, "kind") 3147 kind = f" {kind}" if kind else "" 3148 return f"{this}{kind}{path}" 3149 3150 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3151 return self.func("COLUMNS", *expression.expressions) 3152 3153 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3154 this = self.sql(expression, "this") 3155 path = self.sql(expression, "path") 3156 path = f", {path}" if path else "" 3157 error_handling = expression.args.get("error_handling") 3158 error_handling = f" {error_handling}" if error_handling else "" 3159 empty_handling = expression.args.get("empty_handling") 3160 empty_handling = f" {empty_handling}" if empty_handling else "" 3161 schema = self.sql(expression, "schema") 3162 return self.func( 3163 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3164 ) 3165 3166 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3167 this = self.sql(expression, "this") 3168 kind = self.sql(expression, "kind") 3169 path = self.sql(expression, "path") 3170 path = f" {path}" if path else "" 3171 as_json = " AS JSON" if expression.args.get("as_json") else "" 3172 return f"{this} {kind}{path}{as_json}" 3173 3174 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3175 this = self.sql(expression, "this") 3176 path = self.sql(expression, "path") 3177 path = f", {path}" if path else "" 3178 expressions = self.expressions(expression) 3179 with_ = ( 3180 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3181 if expressions 3182 else "" 3183 ) 3184 return f"OPENJSON({this}{path}){with_}" 3185 3186 def in_sql(self, expression: exp.In) -> str: 3187 query = expression.args.get("query") 3188 unnest = expression.args.get("unnest") 3189 field = expression.args.get("field") 3190 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3191 3192 if query: 3193 in_sql = self.sql(query) 3194 elif unnest: 3195 in_sql = self.in_unnest_op(unnest) 3196 elif field: 3197 in_sql = self.sql(field) 3198 else: 3199 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3200 3201 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3202 3203 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3204 return f"(SELECT {self.sql(unnest)})" 3205 3206 def interval_sql(self, expression: exp.Interval) -> str: 3207 unit = self.sql(expression, "unit") 3208 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3209 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3210 unit = f" {unit}" if unit else "" 3211 3212 if self.SINGLE_STRING_INTERVAL: 3213 this = expression.this.name if expression.this else "" 3214 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3215 3216 this = self.sql(expression, "this") 3217 if this: 3218 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3219 this = f" {this}" if unwrapped else f" ({this})" 3220 3221 return f"INTERVAL{this}{unit}" 3222 3223 def return_sql(self, expression: exp.Return) -> str: 3224 return f"RETURN {self.sql(expression, 'this')}" 3225 3226 def reference_sql(self, expression: exp.Reference) -> str: 3227 this = self.sql(expression, "this") 3228 expressions = self.expressions(expression, flat=True) 3229 expressions = f"({expressions})" if expressions else "" 3230 options = self.expressions(expression, key="options", flat=True, sep=" ") 3231 options = f" {options}" if options else "" 3232 return f"REFERENCES {this}{expressions}{options}" 3233 3234 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3235 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3236 parent = expression.parent 3237 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3238 return self.func( 3239 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3240 ) 3241 3242 def paren_sql(self, expression: exp.Paren) -> str: 3243 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3244 return f"({sql}{self.seg(')', sep='')}" 3245 3246 def neg_sql(self, expression: exp.Neg) -> str: 3247 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3248 this_sql = self.sql(expression, "this") 3249 sep = " " if this_sql[0] == "-" else "" 3250 return f"-{sep}{this_sql}" 3251 3252 def not_sql(self, expression: exp.Not) -> str: 3253 return f"NOT {self.sql(expression, 'this')}" 3254 3255 def alias_sql(self, expression: exp.Alias) -> str: 3256 alias = self.sql(expression, "alias") 3257 alias = f" AS {alias}" if alias else "" 3258 return f"{self.sql(expression, 'this')}{alias}" 3259 3260 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3261 alias = expression.args["alias"] 3262 3263 parent = expression.parent 3264 pivot = parent and parent.parent 3265 3266 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3267 identifier_alias = isinstance(alias, exp.Identifier) 3268 literal_alias = isinstance(alias, exp.Literal) 3269 3270 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3271 alias.replace(exp.Literal.string(alias.output_name)) 3272 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3273 alias.replace(exp.to_identifier(alias.output_name)) 3274 3275 return self.alias_sql(expression) 3276 3277 def aliases_sql(self, expression: exp.Aliases) -> str: 3278 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3279 3280 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3281 this = self.sql(expression, "this") 3282 index = self.sql(expression, "expression") 3283 return f"{this} AT {index}" 3284 3285 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3286 this = self.sql(expression, "this") 3287 zone = self.sql(expression, "zone") 3288 return f"{this} AT TIME ZONE {zone}" 3289 3290 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3291 this = self.sql(expression, "this") 3292 zone = self.sql(expression, "zone") 3293 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3294 3295 def add_sql(self, expression: exp.Add) -> str: 3296 return self.binary(expression, "+") 3297 3298 def and_sql( 3299 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3300 ) -> str: 3301 return self.connector_sql(expression, "AND", stack) 3302 3303 def or_sql( 3304 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3305 ) -> str: 3306 return self.connector_sql(expression, "OR", stack) 3307 3308 def xor_sql( 3309 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3310 ) -> str: 3311 return self.connector_sql(expression, "XOR", stack) 3312 3313 def connector_sql( 3314 self, 3315 expression: exp.Connector, 3316 op: str, 3317 stack: t.Optional[t.List[str | exp.Expression]] = None, 3318 ) -> str: 3319 if stack is not None: 3320 if expression.expressions: 3321 stack.append(self.expressions(expression, sep=f" {op} ")) 3322 else: 3323 stack.append(expression.right) 3324 if expression.comments and self.comments: 3325 for comment in expression.comments: 3326 if comment: 3327 op += f" /*{self.sanitize_comment(comment)}*/" 3328 stack.extend((op, expression.left)) 3329 return op 3330 3331 stack = [expression] 3332 sqls: t.List[str] = [] 3333 ops = set() 3334 3335 while stack: 3336 node = stack.pop() 3337 if isinstance(node, exp.Connector): 3338 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3339 else: 3340 sql = self.sql(node) 3341 if sqls and sqls[-1] in ops: 3342 sqls[-1] += f" {sql}" 3343 else: 3344 sqls.append(sql) 3345 3346 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3347 return sep.join(sqls) 3348 3349 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3350 return self.binary(expression, "&") 3351 3352 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3353 return self.binary(expression, "<<") 3354 3355 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3356 return f"~{self.sql(expression, 'this')}" 3357 3358 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3359 return self.binary(expression, "|") 3360 3361 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3362 return self.binary(expression, ">>") 3363 3364 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3365 return self.binary(expression, "^") 3366 3367 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3368 format_sql = self.sql(expression, "format") 3369 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3370 to_sql = self.sql(expression, "to") 3371 to_sql = f" {to_sql}" if to_sql else "" 3372 action = self.sql(expression, "action") 3373 action = f" {action}" if action else "" 3374 default = self.sql(expression, "default") 3375 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3376 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3377 3378 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3379 zone = self.sql(expression, "this") 3380 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3381 3382 def collate_sql(self, expression: exp.Collate) -> str: 3383 if self.COLLATE_IS_FUNC: 3384 return self.function_fallback_sql(expression) 3385 return self.binary(expression, "COLLATE") 3386 3387 def command_sql(self, expression: exp.Command) -> str: 3388 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3389 3390 def comment_sql(self, expression: exp.Comment) -> str: 3391 this = self.sql(expression, "this") 3392 kind = expression.args["kind"] 3393 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3394 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3395 expression_sql = self.sql(expression, "expression") 3396 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3397 3398 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3399 this = self.sql(expression, "this") 3400 delete = " DELETE" if expression.args.get("delete") else "" 3401 recompress = self.sql(expression, "recompress") 3402 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3403 to_disk = self.sql(expression, "to_disk") 3404 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3405 to_volume = self.sql(expression, "to_volume") 3406 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3407 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3408 3409 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3410 where = self.sql(expression, "where") 3411 group = self.sql(expression, "group") 3412 aggregates = self.expressions(expression, key="aggregates") 3413 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3414 3415 if not (where or group or aggregates) and len(expression.expressions) == 1: 3416 return f"TTL {self.expressions(expression, flat=True)}" 3417 3418 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3419 3420 def transaction_sql(self, expression: exp.Transaction) -> str: 3421 return "BEGIN" 3422 3423 def commit_sql(self, expression: exp.Commit) -> str: 3424 chain = expression.args.get("chain") 3425 if chain is not None: 3426 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3427 3428 return f"COMMIT{chain or ''}" 3429 3430 def rollback_sql(self, expression: exp.Rollback) -> str: 3431 savepoint = expression.args.get("savepoint") 3432 savepoint = f" TO {savepoint}" if savepoint else "" 3433 return f"ROLLBACK{savepoint}" 3434 3435 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3436 this = self.sql(expression, "this") 3437 3438 dtype = self.sql(expression, "dtype") 3439 if dtype: 3440 collate = self.sql(expression, "collate") 3441 collate = f" COLLATE {collate}" if collate else "" 3442 using = self.sql(expression, "using") 3443 using = f" USING {using}" if using else "" 3444 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3445 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3446 3447 default = self.sql(expression, "default") 3448 if default: 3449 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3450 3451 comment = self.sql(expression, "comment") 3452 if comment: 3453 return f"ALTER COLUMN {this} COMMENT {comment}" 3454 3455 visible = expression.args.get("visible") 3456 if visible: 3457 return f"ALTER COLUMN {this} SET {visible}" 3458 3459 allow_null = expression.args.get("allow_null") 3460 drop = expression.args.get("drop") 3461 3462 if not drop and not allow_null: 3463 self.unsupported("Unsupported ALTER COLUMN syntax") 3464 3465 if allow_null is not None: 3466 keyword = "DROP" if drop else "SET" 3467 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3468 3469 return f"ALTER COLUMN {this} DROP DEFAULT" 3470 3471 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3472 this = self.sql(expression, "this") 3473 3474 visible = expression.args.get("visible") 3475 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3476 3477 return f"ALTER INDEX {this} {visible_sql}" 3478 3479 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3480 this = self.sql(expression, "this") 3481 if not isinstance(expression.this, exp.Var): 3482 this = f"KEY DISTKEY {this}" 3483 return f"ALTER DISTSTYLE {this}" 3484 3485 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3486 compound = " COMPOUND" if expression.args.get("compound") else "" 3487 this = self.sql(expression, "this") 3488 expressions = self.expressions(expression, flat=True) 3489 expressions = f"({expressions})" if expressions else "" 3490 return f"ALTER{compound} SORTKEY {this or expressions}" 3491 3492 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3493 if not self.RENAME_TABLE_WITH_DB: 3494 # Remove db from tables 3495 expression = expression.transform( 3496 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3497 ).assert_is(exp.AlterRename) 3498 this = self.sql(expression, "this") 3499 to_kw = " TO" if include_to else "" 3500 return f"RENAME{to_kw} {this}" 3501 3502 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3503 exists = " IF EXISTS" if expression.args.get("exists") else "" 3504 old_column = self.sql(expression, "this") 3505 new_column = self.sql(expression, "to") 3506 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3507 3508 def alterset_sql(self, expression: exp.AlterSet) -> str: 3509 exprs = self.expressions(expression, flat=True) 3510 if self.ALTER_SET_WRAPPED: 3511 exprs = f"({exprs})" 3512 3513 return f"SET {exprs}" 3514 3515 def alter_sql(self, expression: exp.Alter) -> str: 3516 actions = expression.args["actions"] 3517 3518 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3519 actions[0], exp.ColumnDef 3520 ): 3521 actions_sql = self.expressions(expression, key="actions", flat=True) 3522 actions_sql = f"ADD {actions_sql}" 3523 else: 3524 actions_list = [] 3525 for action in actions: 3526 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3527 action_sql = self.add_column_sql(action) 3528 else: 3529 action_sql = self.sql(action) 3530 if isinstance(action, exp.Query): 3531 action_sql = f"AS {action_sql}" 3532 3533 actions_list.append(action_sql) 3534 3535 actions_sql = self.format_args(*actions_list).lstrip("\n") 3536 3537 exists = " IF EXISTS" if expression.args.get("exists") else "" 3538 on_cluster = self.sql(expression, "cluster") 3539 on_cluster = f" {on_cluster}" if on_cluster else "" 3540 only = " ONLY" if expression.args.get("only") else "" 3541 options = self.expressions(expression, key="options") 3542 options = f", {options}" if options else "" 3543 kind = self.sql(expression, "kind") 3544 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3545 3546 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}" 3547 3548 def add_column_sql(self, expression: exp.Expression) -> str: 3549 sql = self.sql(expression) 3550 if isinstance(expression, exp.Schema): 3551 column_text = " COLUMNS" 3552 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3553 column_text = " COLUMN" 3554 else: 3555 column_text = "" 3556 3557 return f"ADD{column_text} {sql}" 3558 3559 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3560 expressions = self.expressions(expression) 3561 exists = " IF EXISTS " if expression.args.get("exists") else " " 3562 return f"DROP{exists}{expressions}" 3563 3564 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3565 return f"ADD {self.expressions(expression, indent=False)}" 3566 3567 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3568 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3569 location = self.sql(expression, "location") 3570 location = f" {location}" if location else "" 3571 return f"ADD {exists}{self.sql(expression.this)}{location}" 3572 3573 def distinct_sql(self, expression: exp.Distinct) -> str: 3574 this = self.expressions(expression, flat=True) 3575 3576 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3577 case = exp.case() 3578 for arg in expression.expressions: 3579 case = case.when(arg.is_(exp.null()), exp.null()) 3580 this = self.sql(case.else_(f"({this})")) 3581 3582 this = f" {this}" if this else "" 3583 3584 on = self.sql(expression, "on") 3585 on = f" ON {on}" if on else "" 3586 return f"DISTINCT{this}{on}" 3587 3588 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3589 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3590 3591 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3592 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3593 3594 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3595 this_sql = self.sql(expression, "this") 3596 expression_sql = self.sql(expression, "expression") 3597 kind = "MAX" if expression.args.get("max") else "MIN" 3598 return f"{this_sql} HAVING {kind} {expression_sql}" 3599 3600 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3601 return self.sql( 3602 exp.Cast( 3603 this=exp.Div(this=expression.this, expression=expression.expression), 3604 to=exp.DataType(this=exp.DataType.Type.INT), 3605 ) 3606 ) 3607 3608 def dpipe_sql(self, expression: exp.DPipe) -> str: 3609 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3610 return self.func( 3611 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3612 ) 3613 return self.binary(expression, "||") 3614 3615 def div_sql(self, expression: exp.Div) -> str: 3616 l, r = expression.left, expression.right 3617 3618 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3619 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3620 3621 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3622 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3623 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3624 3625 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3626 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3627 return self.sql( 3628 exp.cast( 3629 l / r, 3630 to=exp.DataType.Type.BIGINT, 3631 ) 3632 ) 3633 3634 return self.binary(expression, "/") 3635 3636 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3637 n = exp._wrap(expression.this, exp.Binary) 3638 d = exp._wrap(expression.expression, exp.Binary) 3639 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3640 3641 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3642 return self.binary(expression, "OVERLAPS") 3643 3644 def distance_sql(self, expression: exp.Distance) -> str: 3645 return self.binary(expression, "<->") 3646 3647 def dot_sql(self, expression: exp.Dot) -> str: 3648 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3649 3650 def eq_sql(self, expression: exp.EQ) -> str: 3651 return self.binary(expression, "=") 3652 3653 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3654 return self.binary(expression, ":=") 3655 3656 def escape_sql(self, expression: exp.Escape) -> str: 3657 return self.binary(expression, "ESCAPE") 3658 3659 def glob_sql(self, expression: exp.Glob) -> str: 3660 return self.binary(expression, "GLOB") 3661 3662 def gt_sql(self, expression: exp.GT) -> str: 3663 return self.binary(expression, ">") 3664 3665 def gte_sql(self, expression: exp.GTE) -> str: 3666 return self.binary(expression, ">=") 3667 3668 def is_sql(self, expression: exp.Is) -> str: 3669 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3670 return self.sql( 3671 expression.this if expression.expression.this else exp.not_(expression.this) 3672 ) 3673 return self.binary(expression, "IS") 3674 3675 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3676 this = expression.this 3677 rhs = expression.expression 3678 3679 if isinstance(expression, exp.Like): 3680 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3681 op = "LIKE" 3682 else: 3683 exp_class = exp.ILike 3684 op = "ILIKE" 3685 3686 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3687 exprs = rhs.this.unnest() 3688 3689 if isinstance(exprs, exp.Tuple): 3690 exprs = exprs.expressions 3691 3692 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3693 3694 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3695 for expr in exprs[1:]: 3696 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3697 3698 return self.sql(like_expr) 3699 3700 return self.binary(expression, op) 3701 3702 def like_sql(self, expression: exp.Like) -> str: 3703 return self._like_sql(expression) 3704 3705 def ilike_sql(self, expression: exp.ILike) -> str: 3706 return self._like_sql(expression) 3707 3708 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3709 return self.binary(expression, "SIMILAR TO") 3710 3711 def lt_sql(self, expression: exp.LT) -> str: 3712 return self.binary(expression, "<") 3713 3714 def lte_sql(self, expression: exp.LTE) -> str: 3715 return self.binary(expression, "<=") 3716 3717 def mod_sql(self, expression: exp.Mod) -> str: 3718 return self.binary(expression, "%") 3719 3720 def mul_sql(self, expression: exp.Mul) -> str: 3721 return self.binary(expression, "*") 3722 3723 def neq_sql(self, expression: exp.NEQ) -> str: 3724 return self.binary(expression, "<>") 3725 3726 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3727 return self.binary(expression, "IS NOT DISTINCT FROM") 3728 3729 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3730 return self.binary(expression, "IS DISTINCT FROM") 3731 3732 def slice_sql(self, expression: exp.Slice) -> str: 3733 return self.binary(expression, ":") 3734 3735 def sub_sql(self, expression: exp.Sub) -> str: 3736 return self.binary(expression, "-") 3737 3738 def trycast_sql(self, expression: exp.TryCast) -> str: 3739 return self.cast_sql(expression, safe_prefix="TRY_") 3740 3741 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3742 return self.cast_sql(expression) 3743 3744 def try_sql(self, expression: exp.Try) -> str: 3745 if not self.TRY_SUPPORTED: 3746 self.unsupported("Unsupported TRY function") 3747 return self.sql(expression, "this") 3748 3749 return self.func("TRY", expression.this) 3750 3751 def log_sql(self, expression: exp.Log) -> str: 3752 this = expression.this 3753 expr = expression.expression 3754 3755 if self.dialect.LOG_BASE_FIRST is False: 3756 this, expr = expr, this 3757 elif self.dialect.LOG_BASE_FIRST is None and expr: 3758 if this.name in ("2", "10"): 3759 return self.func(f"LOG{this.name}", expr) 3760 3761 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3762 3763 return self.func("LOG", this, expr) 3764 3765 def use_sql(self, expression: exp.Use) -> str: 3766 kind = self.sql(expression, "kind") 3767 kind = f" {kind}" if kind else "" 3768 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3769 this = f" {this}" if this else "" 3770 return f"USE{kind}{this}" 3771 3772 def binary(self, expression: exp.Binary, op: str) -> str: 3773 sqls: t.List[str] = [] 3774 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3775 binary_type = type(expression) 3776 3777 while stack: 3778 node = stack.pop() 3779 3780 if type(node) is binary_type: 3781 op_func = node.args.get("operator") 3782 if op_func: 3783 op = f"OPERATOR({self.sql(op_func)})" 3784 3785 stack.append(node.right) 3786 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3787 stack.append(node.left) 3788 else: 3789 sqls.append(self.sql(node)) 3790 3791 return "".join(sqls) 3792 3793 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3794 to_clause = self.sql(expression, "to") 3795 if to_clause: 3796 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3797 3798 return self.function_fallback_sql(expression) 3799 3800 def function_fallback_sql(self, expression: exp.Func) -> str: 3801 args = [] 3802 3803 for key in expression.arg_types: 3804 arg_value = expression.args.get(key) 3805 3806 if isinstance(arg_value, list): 3807 for value in arg_value: 3808 args.append(value) 3809 elif arg_value is not None: 3810 args.append(arg_value) 3811 3812 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3813 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3814 else: 3815 name = expression.sql_name() 3816 3817 return self.func(name, *args) 3818 3819 def func( 3820 self, 3821 name: str, 3822 *args: t.Optional[exp.Expression | str], 3823 prefix: str = "(", 3824 suffix: str = ")", 3825 normalize: bool = True, 3826 ) -> str: 3827 name = self.normalize_func(name) if normalize else name 3828 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3829 3830 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3831 arg_sqls = tuple( 3832 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3833 ) 3834 if self.pretty and self.too_wide(arg_sqls): 3835 return self.indent( 3836 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3837 ) 3838 return sep.join(arg_sqls) 3839 3840 def too_wide(self, args: t.Iterable) -> bool: 3841 return sum(len(arg) for arg in args) > self.max_text_width 3842 3843 def format_time( 3844 self, 3845 expression: exp.Expression, 3846 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3847 inverse_time_trie: t.Optional[t.Dict] = None, 3848 ) -> t.Optional[str]: 3849 return format_time( 3850 self.sql(expression, "format"), 3851 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3852 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3853 ) 3854 3855 def expressions( 3856 self, 3857 expression: t.Optional[exp.Expression] = None, 3858 key: t.Optional[str] = None, 3859 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3860 flat: bool = False, 3861 indent: bool = True, 3862 skip_first: bool = False, 3863 skip_last: bool = False, 3864 sep: str = ", ", 3865 prefix: str = "", 3866 dynamic: bool = False, 3867 new_line: bool = False, 3868 ) -> str: 3869 expressions = expression.args.get(key or "expressions") if expression else sqls 3870 3871 if not expressions: 3872 return "" 3873 3874 if flat: 3875 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3876 3877 num_sqls = len(expressions) 3878 result_sqls = [] 3879 3880 for i, e in enumerate(expressions): 3881 sql = self.sql(e, comment=False) 3882 if not sql: 3883 continue 3884 3885 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3886 3887 if self.pretty: 3888 if self.leading_comma: 3889 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3890 else: 3891 result_sqls.append( 3892 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3893 ) 3894 else: 3895 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3896 3897 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3898 if new_line: 3899 result_sqls.insert(0, "") 3900 result_sqls.append("") 3901 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3902 else: 3903 result_sql = "".join(result_sqls) 3904 3905 return ( 3906 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3907 if indent 3908 else result_sql 3909 ) 3910 3911 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3912 flat = flat or isinstance(expression.parent, exp.Properties) 3913 expressions_sql = self.expressions(expression, flat=flat) 3914 if flat: 3915 return f"{op} {expressions_sql}" 3916 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3917 3918 def naked_property(self, expression: exp.Property) -> str: 3919 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3920 if not property_name: 3921 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3922 return f"{property_name} {self.sql(expression, 'this')}" 3923 3924 def tag_sql(self, expression: exp.Tag) -> str: 3925 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3926 3927 def token_sql(self, token_type: TokenType) -> str: 3928 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3929 3930 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3931 this = self.sql(expression, "this") 3932 expressions = self.no_identify(self.expressions, expression) 3933 expressions = ( 3934 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3935 ) 3936 return f"{this}{expressions}" if expressions.strip() != "" else this 3937 3938 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3939 this = self.sql(expression, "this") 3940 expressions = self.expressions(expression, flat=True) 3941 return f"{this}({expressions})" 3942 3943 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3944 return self.binary(expression, "=>") 3945 3946 def when_sql(self, expression: exp.When) -> str: 3947 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3948 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3949 condition = self.sql(expression, "condition") 3950 condition = f" AND {condition}" if condition else "" 3951 3952 then_expression = expression.args.get("then") 3953 if isinstance(then_expression, exp.Insert): 3954 this = self.sql(then_expression, "this") 3955 this = f"INSERT {this}" if this else "INSERT" 3956 then = self.sql(then_expression, "expression") 3957 then = f"{this} VALUES {then}" if then else this 3958 elif isinstance(then_expression, exp.Update): 3959 if isinstance(then_expression.args.get("expressions"), exp.Star): 3960 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3961 else: 3962 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3963 else: 3964 then = self.sql(then_expression) 3965 return f"WHEN {matched}{source}{condition} THEN {then}" 3966 3967 def whens_sql(self, expression: exp.Whens) -> str: 3968 return self.expressions(expression, sep=" ", indent=False) 3969 3970 def merge_sql(self, expression: exp.Merge) -> str: 3971 table = expression.this 3972 table_alias = "" 3973 3974 hints = table.args.get("hints") 3975 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3976 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3977 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3978 3979 this = self.sql(table) 3980 using = f"USING {self.sql(expression, 'using')}" 3981 on = f"ON {self.sql(expression, 'on')}" 3982 whens = self.sql(expression, "whens") 3983 3984 returning = self.sql(expression, "returning") 3985 if returning: 3986 whens = f"{whens}{returning}" 3987 3988 sep = self.sep() 3989 3990 return self.prepend_ctes( 3991 expression, 3992 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3993 ) 3994 3995 @unsupported_args("format") 3996 def tochar_sql(self, expression: exp.ToChar) -> str: 3997 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3998 3999 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4000 if not self.SUPPORTS_TO_NUMBER: 4001 self.unsupported("Unsupported TO_NUMBER function") 4002 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4003 4004 fmt = expression.args.get("format") 4005 if not fmt: 4006 self.unsupported("Conversion format is required for TO_NUMBER") 4007 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4008 4009 return self.func("TO_NUMBER", expression.this, fmt) 4010 4011 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4012 this = self.sql(expression, "this") 4013 kind = self.sql(expression, "kind") 4014 settings_sql = self.expressions(expression, key="settings", sep=" ") 4015 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4016 return f"{this}({kind}{args})" 4017 4018 def dictrange_sql(self, expression: exp.DictRange) -> str: 4019 this = self.sql(expression, "this") 4020 max = self.sql(expression, "max") 4021 min = self.sql(expression, "min") 4022 return f"{this}(MIN {min} MAX {max})" 4023 4024 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4025 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4026 4027 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4028 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4029 4030 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4031 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 4032 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 4033 4034 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4035 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4036 expressions = self.expressions(expression, flat=True) 4037 expressions = f" {self.wrap(expressions)}" if expressions else "" 4038 buckets = self.sql(expression, "buckets") 4039 kind = self.sql(expression, "kind") 4040 buckets = f" BUCKETS {buckets}" if buckets else "" 4041 order = self.sql(expression, "order") 4042 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4043 4044 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4045 return "" 4046 4047 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4048 expressions = self.expressions(expression, key="expressions", flat=True) 4049 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4050 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4051 buckets = self.sql(expression, "buckets") 4052 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4053 4054 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4055 this = self.sql(expression, "this") 4056 having = self.sql(expression, "having") 4057 4058 if having: 4059 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4060 4061 return self.func("ANY_VALUE", this) 4062 4063 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4064 transform = self.func("TRANSFORM", *expression.expressions) 4065 row_format_before = self.sql(expression, "row_format_before") 4066 row_format_before = f" {row_format_before}" if row_format_before else "" 4067 record_writer = self.sql(expression, "record_writer") 4068 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4069 using = f" USING {self.sql(expression, 'command_script')}" 4070 schema = self.sql(expression, "schema") 4071 schema = f" AS {schema}" if schema else "" 4072 row_format_after = self.sql(expression, "row_format_after") 4073 row_format_after = f" {row_format_after}" if row_format_after else "" 4074 record_reader = self.sql(expression, "record_reader") 4075 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4076 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4077 4078 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4079 key_block_size = self.sql(expression, "key_block_size") 4080 if key_block_size: 4081 return f"KEY_BLOCK_SIZE = {key_block_size}" 4082 4083 using = self.sql(expression, "using") 4084 if using: 4085 return f"USING {using}" 4086 4087 parser = self.sql(expression, "parser") 4088 if parser: 4089 return f"WITH PARSER {parser}" 4090 4091 comment = self.sql(expression, "comment") 4092 if comment: 4093 return f"COMMENT {comment}" 4094 4095 visible = expression.args.get("visible") 4096 if visible is not None: 4097 return "VISIBLE" if visible else "INVISIBLE" 4098 4099 engine_attr = self.sql(expression, "engine_attr") 4100 if engine_attr: 4101 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4102 4103 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4104 if secondary_engine_attr: 4105 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4106 4107 self.unsupported("Unsupported index constraint option.") 4108 return "" 4109 4110 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4111 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4112 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4113 4114 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4115 kind = self.sql(expression, "kind") 4116 kind = f"{kind} INDEX" if kind else "INDEX" 4117 this = self.sql(expression, "this") 4118 this = f" {this}" if this else "" 4119 index_type = self.sql(expression, "index_type") 4120 index_type = f" USING {index_type}" if index_type else "" 4121 expressions = self.expressions(expression, flat=True) 4122 expressions = f" ({expressions})" if expressions else "" 4123 options = self.expressions(expression, key="options", sep=" ") 4124 options = f" {options}" if options else "" 4125 return f"{kind}{this}{index_type}{expressions}{options}" 4126 4127 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4128 if self.NVL2_SUPPORTED: 4129 return self.function_fallback_sql(expression) 4130 4131 case = exp.Case().when( 4132 expression.this.is_(exp.null()).not_(copy=False), 4133 expression.args["true"], 4134 copy=False, 4135 ) 4136 else_cond = expression.args.get("false") 4137 if else_cond: 4138 case.else_(else_cond, copy=False) 4139 4140 return self.sql(case) 4141 4142 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4143 this = self.sql(expression, "this") 4144 expr = self.sql(expression, "expression") 4145 iterator = self.sql(expression, "iterator") 4146 condition = self.sql(expression, "condition") 4147 condition = f" IF {condition}" if condition else "" 4148 return f"{this} FOR {expr} IN {iterator}{condition}" 4149 4150 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4151 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4152 4153 def opclass_sql(self, expression: exp.Opclass) -> str: 4154 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4155 4156 def predict_sql(self, expression: exp.Predict) -> str: 4157 model = self.sql(expression, "this") 4158 model = f"MODEL {model}" 4159 table = self.sql(expression, "expression") 4160 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4161 parameters = self.sql(expression, "params_struct") 4162 return self.func("PREDICT", model, table, parameters or None) 4163 4164 def forin_sql(self, expression: exp.ForIn) -> str: 4165 this = self.sql(expression, "this") 4166 expression_sql = self.sql(expression, "expression") 4167 return f"FOR {this} DO {expression_sql}" 4168 4169 def refresh_sql(self, expression: exp.Refresh) -> str: 4170 this = self.sql(expression, "this") 4171 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4172 return f"REFRESH {table}{this}" 4173 4174 def toarray_sql(self, expression: exp.ToArray) -> str: 4175 arg = expression.this 4176 if not arg.type: 4177 from sqlglot.optimizer.annotate_types import annotate_types 4178 4179 arg = annotate_types(arg, dialect=self.dialect) 4180 4181 if arg.is_type(exp.DataType.Type.ARRAY): 4182 return self.sql(arg) 4183 4184 cond_for_null = arg.is_(exp.null()) 4185 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4186 4187 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4188 this = expression.this 4189 time_format = self.format_time(expression) 4190 4191 if time_format: 4192 return self.sql( 4193 exp.cast( 4194 exp.StrToTime(this=this, format=expression.args["format"]), 4195 exp.DataType.Type.TIME, 4196 ) 4197 ) 4198 4199 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4200 return self.sql(this) 4201 4202 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4203 4204 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4205 this = expression.this 4206 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4207 return self.sql(this) 4208 4209 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4210 4211 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4212 this = expression.this 4213 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4214 return self.sql(this) 4215 4216 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4217 4218 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4219 this = expression.this 4220 time_format = self.format_time(expression) 4221 4222 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4223 return self.sql( 4224 exp.cast( 4225 exp.StrToTime(this=this, format=expression.args["format"]), 4226 exp.DataType.Type.DATE, 4227 ) 4228 ) 4229 4230 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4231 return self.sql(this) 4232 4233 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4234 4235 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4236 return self.sql( 4237 exp.func( 4238 "DATEDIFF", 4239 expression.this, 4240 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4241 "day", 4242 ) 4243 ) 4244 4245 def lastday_sql(self, expression: exp.LastDay) -> str: 4246 if self.LAST_DAY_SUPPORTS_DATE_PART: 4247 return self.function_fallback_sql(expression) 4248 4249 unit = expression.text("unit") 4250 if unit and unit != "MONTH": 4251 self.unsupported("Date parts are not supported in LAST_DAY.") 4252 4253 return self.func("LAST_DAY", expression.this) 4254 4255 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4256 from sqlglot.dialects.dialect import unit_to_str 4257 4258 return self.func( 4259 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4260 ) 4261 4262 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4263 if self.CAN_IMPLEMENT_ARRAY_ANY: 4264 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4265 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4266 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4267 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4268 4269 from sqlglot.dialects import Dialect 4270 4271 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4272 if self.dialect.__class__ != Dialect: 4273 self.unsupported("ARRAY_ANY is unsupported") 4274 4275 return self.function_fallback_sql(expression) 4276 4277 def struct_sql(self, expression: exp.Struct) -> str: 4278 expression.set( 4279 "expressions", 4280 [ 4281 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4282 if isinstance(e, exp.PropertyEQ) 4283 else e 4284 for e in expression.expressions 4285 ], 4286 ) 4287 4288 return self.function_fallback_sql(expression) 4289 4290 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4291 low = self.sql(expression, "this") 4292 high = self.sql(expression, "expression") 4293 4294 return f"{low} TO {high}" 4295 4296 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4297 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4298 tables = f" {self.expressions(expression)}" 4299 4300 exists = " IF EXISTS" if expression.args.get("exists") else "" 4301 4302 on_cluster = self.sql(expression, "cluster") 4303 on_cluster = f" {on_cluster}" if on_cluster else "" 4304 4305 identity = self.sql(expression, "identity") 4306 identity = f" {identity} IDENTITY" if identity else "" 4307 4308 option = self.sql(expression, "option") 4309 option = f" {option}" if option else "" 4310 4311 partition = self.sql(expression, "partition") 4312 partition = f" {partition}" if partition else "" 4313 4314 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4315 4316 # This transpiles T-SQL's CONVERT function 4317 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4318 def convert_sql(self, expression: exp.Convert) -> str: 4319 to = expression.this 4320 value = expression.expression 4321 style = expression.args.get("style") 4322 safe = expression.args.get("safe") 4323 strict = expression.args.get("strict") 4324 4325 if not to or not value: 4326 return "" 4327 4328 # Retrieve length of datatype and override to default if not specified 4329 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4330 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4331 4332 transformed: t.Optional[exp.Expression] = None 4333 cast = exp.Cast if strict else exp.TryCast 4334 4335 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4336 if isinstance(style, exp.Literal) and style.is_int: 4337 from sqlglot.dialects.tsql import TSQL 4338 4339 style_value = style.name 4340 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4341 if not converted_style: 4342 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4343 4344 fmt = exp.Literal.string(converted_style) 4345 4346 if to.this == exp.DataType.Type.DATE: 4347 transformed = exp.StrToDate(this=value, format=fmt) 4348 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4349 transformed = exp.StrToTime(this=value, format=fmt) 4350 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4351 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4352 elif to.this == exp.DataType.Type.TEXT: 4353 transformed = exp.TimeToStr(this=value, format=fmt) 4354 4355 if not transformed: 4356 transformed = cast(this=value, to=to, safe=safe) 4357 4358 return self.sql(transformed) 4359 4360 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4361 this = expression.this 4362 if isinstance(this, exp.JSONPathWildcard): 4363 this = self.json_path_part(this) 4364 return f".{this}" if this else "" 4365 4366 if exp.SAFE_IDENTIFIER_RE.match(this): 4367 return f".{this}" 4368 4369 this = self.json_path_part(this) 4370 return ( 4371 f"[{this}]" 4372 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4373 else f".{this}" 4374 ) 4375 4376 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4377 this = self.json_path_part(expression.this) 4378 return f"[{this}]" if this else "" 4379 4380 def _simplify_unless_literal(self, expression: E) -> E: 4381 if not isinstance(expression, exp.Literal): 4382 from sqlglot.optimizer.simplify import simplify 4383 4384 expression = simplify(expression, dialect=self.dialect) 4385 4386 return expression 4387 4388 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4389 this = expression.this 4390 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4391 self.unsupported( 4392 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4393 ) 4394 return self.sql(this) 4395 4396 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4397 # The first modifier here will be the one closest to the AggFunc's arg 4398 mods = sorted( 4399 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4400 key=lambda x: 0 4401 if isinstance(x, exp.HavingMax) 4402 else (1 if isinstance(x, exp.Order) else 2), 4403 ) 4404 4405 if mods: 4406 mod = mods[0] 4407 this = expression.__class__(this=mod.this.copy()) 4408 this.meta["inline"] = True 4409 mod.this.replace(this) 4410 return self.sql(expression.this) 4411 4412 agg_func = expression.find(exp.AggFunc) 4413 4414 if agg_func: 4415 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4416 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4417 4418 return f"{self.sql(expression, 'this')} {text}" 4419 4420 def _replace_line_breaks(self, string: str) -> str: 4421 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4422 if self.pretty: 4423 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4424 return string 4425 4426 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4427 option = self.sql(expression, "this") 4428 4429 if expression.expressions: 4430 upper = option.upper() 4431 4432 # Snowflake FILE_FORMAT options are separated by whitespace 4433 sep = " " if upper == "FILE_FORMAT" else ", " 4434 4435 # Databricks copy/format options do not set their list of values with EQ 4436 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4437 values = self.expressions(expression, flat=True, sep=sep) 4438 return f"{option}{op}({values})" 4439 4440 value = self.sql(expression, "expression") 4441 4442 if not value: 4443 return option 4444 4445 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4446 4447 return f"{option}{op}{value}" 4448 4449 def credentials_sql(self, expression: exp.Credentials) -> str: 4450 cred_expr = expression.args.get("credentials") 4451 if isinstance(cred_expr, exp.Literal): 4452 # Redshift case: CREDENTIALS <string> 4453 credentials = self.sql(expression, "credentials") 4454 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4455 else: 4456 # Snowflake case: CREDENTIALS = (...) 4457 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4458 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4459 4460 storage = self.sql(expression, "storage") 4461 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4462 4463 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4464 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4465 4466 iam_role = self.sql(expression, "iam_role") 4467 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4468 4469 region = self.sql(expression, "region") 4470 region = f" REGION {region}" if region else "" 4471 4472 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4473 4474 def copy_sql(self, expression: exp.Copy) -> str: 4475 this = self.sql(expression, "this") 4476 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4477 4478 credentials = self.sql(expression, "credentials") 4479 credentials = self.seg(credentials) if credentials else "" 4480 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4481 files = self.expressions(expression, key="files", flat=True) 4482 4483 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4484 params = self.expressions( 4485 expression, 4486 key="params", 4487 sep=sep, 4488 new_line=True, 4489 skip_last=True, 4490 skip_first=True, 4491 indent=self.COPY_PARAMS_ARE_WRAPPED, 4492 ) 4493 4494 if params: 4495 if self.COPY_PARAMS_ARE_WRAPPED: 4496 params = f" WITH ({params})" 4497 elif not self.pretty: 4498 params = f" {params}" 4499 4500 return f"COPY{this}{kind} {files}{credentials}{params}" 4501 4502 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4503 return "" 4504 4505 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4506 on_sql = "ON" if expression.args.get("on") else "OFF" 4507 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4508 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4509 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4510 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4511 4512 if filter_col or retention_period: 4513 on_sql = self.func("ON", filter_col, retention_period) 4514 4515 return f"DATA_DELETION={on_sql}" 4516 4517 def maskingpolicycolumnconstraint_sql( 4518 self, expression: exp.MaskingPolicyColumnConstraint 4519 ) -> str: 4520 this = self.sql(expression, "this") 4521 expressions = self.expressions(expression, flat=True) 4522 expressions = f" USING ({expressions})" if expressions else "" 4523 return f"MASKING POLICY {this}{expressions}" 4524 4525 def gapfill_sql(self, expression: exp.GapFill) -> str: 4526 this = self.sql(expression, "this") 4527 this = f"TABLE {this}" 4528 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4529 4530 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4531 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4532 4533 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4534 this = self.sql(expression, "this") 4535 expr = expression.expression 4536 4537 if isinstance(expr, exp.Func): 4538 # T-SQL's CLR functions are case sensitive 4539 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4540 else: 4541 expr = self.sql(expression, "expression") 4542 4543 return self.scope_resolution(expr, this) 4544 4545 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4546 if self.PARSE_JSON_NAME is None: 4547 return self.sql(expression.this) 4548 4549 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4550 4551 def rand_sql(self, expression: exp.Rand) -> str: 4552 lower = self.sql(expression, "lower") 4553 upper = self.sql(expression, "upper") 4554 4555 if lower and upper: 4556 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4557 return self.func("RAND", expression.this) 4558 4559 def changes_sql(self, expression: exp.Changes) -> str: 4560 information = self.sql(expression, "information") 4561 information = f"INFORMATION => {information}" 4562 at_before = self.sql(expression, "at_before") 4563 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4564 end = self.sql(expression, "end") 4565 end = f"{self.seg('')}{end}" if end else "" 4566 4567 return f"CHANGES ({information}){at_before}{end}" 4568 4569 def pad_sql(self, expression: exp.Pad) -> str: 4570 prefix = "L" if expression.args.get("is_left") else "R" 4571 4572 fill_pattern = self.sql(expression, "fill_pattern") or None 4573 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4574 fill_pattern = "' '" 4575 4576 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4577 4578 def summarize_sql(self, expression: exp.Summarize) -> str: 4579 table = " TABLE" if expression.args.get("table") else "" 4580 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4581 4582 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4583 generate_series = exp.GenerateSeries(**expression.args) 4584 4585 parent = expression.parent 4586 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4587 parent = parent.parent 4588 4589 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4590 return self.sql(exp.Unnest(expressions=[generate_series])) 4591 4592 if isinstance(parent, exp.Select): 4593 self.unsupported("GenerateSeries projection unnesting is not supported.") 4594 4595 return self.sql(generate_series) 4596 4597 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4598 exprs = expression.expressions 4599 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4600 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4601 else: 4602 rhs = self.expressions(expression) 4603 4604 return self.func(name, expression.this, rhs or None) 4605 4606 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4607 if self.SUPPORTS_CONVERT_TIMEZONE: 4608 return self.function_fallback_sql(expression) 4609 4610 source_tz = expression.args.get("source_tz") 4611 target_tz = expression.args.get("target_tz") 4612 timestamp = expression.args.get("timestamp") 4613 4614 if source_tz and timestamp: 4615 timestamp = exp.AtTimeZone( 4616 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4617 ) 4618 4619 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4620 4621 return self.sql(expr) 4622 4623 def json_sql(self, expression: exp.JSON) -> str: 4624 this = self.sql(expression, "this") 4625 this = f" {this}" if this else "" 4626 4627 _with = expression.args.get("with") 4628 4629 if _with is None: 4630 with_sql = "" 4631 elif not _with: 4632 with_sql = " WITHOUT" 4633 else: 4634 with_sql = " WITH" 4635 4636 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4637 4638 return f"JSON{this}{with_sql}{unique_sql}" 4639 4640 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4641 def _generate_on_options(arg: t.Any) -> str: 4642 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4643 4644 path = self.sql(expression, "path") 4645 returning = self.sql(expression, "returning") 4646 returning = f" RETURNING {returning}" if returning else "" 4647 4648 on_condition = self.sql(expression, "on_condition") 4649 on_condition = f" {on_condition}" if on_condition else "" 4650 4651 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4652 4653 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4654 else_ = "ELSE " if expression.args.get("else_") else "" 4655 condition = self.sql(expression, "expression") 4656 condition = f"WHEN {condition} THEN " if condition else else_ 4657 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4658 return f"{condition}{insert}" 4659 4660 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4661 kind = self.sql(expression, "kind") 4662 expressions = self.seg(self.expressions(expression, sep=" ")) 4663 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4664 return res 4665 4666 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4667 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4668 empty = expression.args.get("empty") 4669 empty = ( 4670 f"DEFAULT {empty} ON EMPTY" 4671 if isinstance(empty, exp.Expression) 4672 else self.sql(expression, "empty") 4673 ) 4674 4675 error = expression.args.get("error") 4676 error = ( 4677 f"DEFAULT {error} ON ERROR" 4678 if isinstance(error, exp.Expression) 4679 else self.sql(expression, "error") 4680 ) 4681 4682 if error and empty: 4683 error = ( 4684 f"{empty} {error}" 4685 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4686 else f"{error} {empty}" 4687 ) 4688 empty = "" 4689 4690 null = self.sql(expression, "null") 4691 4692 return f"{empty}{error}{null}" 4693 4694 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4695 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4696 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4697 4698 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4699 this = self.sql(expression, "this") 4700 path = self.sql(expression, "path") 4701 4702 passing = self.expressions(expression, "passing") 4703 passing = f" PASSING {passing}" if passing else "" 4704 4705 on_condition = self.sql(expression, "on_condition") 4706 on_condition = f" {on_condition}" if on_condition else "" 4707 4708 path = f"{path}{passing}{on_condition}" 4709 4710 return self.func("JSON_EXISTS", this, path) 4711 4712 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4713 array_agg = self.function_fallback_sql(expression) 4714 4715 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4716 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4717 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4718 parent = expression.parent 4719 if isinstance(parent, exp.Filter): 4720 parent_cond = parent.expression.this 4721 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4722 else: 4723 this = expression.this 4724 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4725 if this.find(exp.Column): 4726 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4727 this_sql = ( 4728 self.expressions(this) 4729 if isinstance(this, exp.Distinct) 4730 else self.sql(expression, "this") 4731 ) 4732 4733 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4734 4735 return array_agg 4736 4737 def apply_sql(self, expression: exp.Apply) -> str: 4738 this = self.sql(expression, "this") 4739 expr = self.sql(expression, "expression") 4740 4741 return f"{this} APPLY({expr})" 4742 4743 def grant_sql(self, expression: exp.Grant) -> str: 4744 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4745 4746 kind = self.sql(expression, "kind") 4747 kind = f" {kind}" if kind else "" 4748 4749 securable = self.sql(expression, "securable") 4750 securable = f" {securable}" if securable else "" 4751 4752 principals = self.expressions(expression, key="principals", flat=True) 4753 4754 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4755 4756 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4757 4758 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4759 this = self.sql(expression, "this") 4760 columns = self.expressions(expression, flat=True) 4761 columns = f"({columns})" if columns else "" 4762 4763 return f"{this}{columns}" 4764 4765 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4766 this = self.sql(expression, "this") 4767 4768 kind = self.sql(expression, "kind") 4769 kind = f"{kind} " if kind else "" 4770 4771 return f"{kind}{this}" 4772 4773 def columns_sql(self, expression: exp.Columns): 4774 func = self.function_fallback_sql(expression) 4775 if expression.args.get("unpack"): 4776 func = f"*{func}" 4777 4778 return func 4779 4780 def overlay_sql(self, expression: exp.Overlay): 4781 this = self.sql(expression, "this") 4782 expr = self.sql(expression, "expression") 4783 from_sql = self.sql(expression, "from") 4784 for_sql = self.sql(expression, "for") 4785 for_sql = f" FOR {for_sql}" if for_sql else "" 4786 4787 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4788 4789 @unsupported_args("format") 4790 def todouble_sql(self, expression: exp.ToDouble) -> str: 4791 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4792 4793 def string_sql(self, expression: exp.String) -> str: 4794 this = expression.this 4795 zone = expression.args.get("zone") 4796 4797 if zone: 4798 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4799 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4800 # set for source_tz to transpile the time conversion before the STRING cast 4801 this = exp.ConvertTimezone( 4802 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4803 ) 4804 4805 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4806 4807 def median_sql(self, expression: exp.Median): 4808 if not self.SUPPORTS_MEDIAN: 4809 return self.sql( 4810 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4811 ) 4812 4813 return self.function_fallback_sql(expression) 4814 4815 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4816 filler = self.sql(expression, "this") 4817 filler = f" {filler}" if filler else "" 4818 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4819 return f"TRUNCATE{filler} {with_count}" 4820 4821 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4822 if self.SUPPORTS_UNIX_SECONDS: 4823 return self.function_fallback_sql(expression) 4824 4825 start_ts = exp.cast( 4826 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4827 ) 4828 4829 return self.sql( 4830 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4831 ) 4832 4833 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4834 dim = expression.expression 4835 4836 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4837 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4838 if not (dim.is_int and dim.name == "1"): 4839 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4840 dim = None 4841 4842 # If dimension is required but not specified, default initialize it 4843 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4844 dim = exp.Literal.number(1) 4845 4846 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4847 4848 def attach_sql(self, expression: exp.Attach) -> str: 4849 this = self.sql(expression, "this") 4850 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4851 expressions = self.expressions(expression) 4852 expressions = f" ({expressions})" if expressions else "" 4853 4854 return f"ATTACH{exists_sql} {this}{expressions}" 4855 4856 def detach_sql(self, expression: exp.Detach) -> str: 4857 this = self.sql(expression, "this") 4858 # the DATABASE keyword is required if IF EXISTS is set 4859 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4860 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4861 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4862 4863 return f"DETACH{exists_sql} {this}" 4864 4865 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4866 this = self.sql(expression, "this") 4867 value = self.sql(expression, "expression") 4868 value = f" {value}" if value else "" 4869 return f"{this}{value}" 4870 4871 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4872 this_sql = self.sql(expression, "this") 4873 if isinstance(expression.this, exp.Table): 4874 this_sql = f"TABLE {this_sql}" 4875 4876 return self.func( 4877 "FEATURES_AT_TIME", 4878 this_sql, 4879 expression.args.get("time"), 4880 expression.args.get("num_rows"), 4881 expression.args.get("ignore_feature_nulls"), 4882 ) 4883 4884 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4885 return ( 4886 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4887 ) 4888 4889 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4890 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4891 encode = f"{encode} {self.sql(expression, 'this')}" 4892 4893 properties = expression.args.get("properties") 4894 if properties: 4895 encode = f"{encode} {self.properties(properties)}" 4896 4897 return encode 4898 4899 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4900 this = self.sql(expression, "this") 4901 include = f"INCLUDE {this}" 4902 4903 column_def = self.sql(expression, "column_def") 4904 if column_def: 4905 include = f"{include} {column_def}" 4906 4907 alias = self.sql(expression, "alias") 4908 if alias: 4909 include = f"{include} AS {alias}" 4910 4911 return include 4912 4913 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4914 name = f"NAME {self.sql(expression, 'this')}" 4915 return self.func("XMLELEMENT", name, *expression.expressions) 4916 4917 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 4918 this = self.sql(expression, "this") 4919 expr = self.sql(expression, "expression") 4920 expr = f"({expr})" if expr else "" 4921 return f"{this}{expr}" 4922 4923 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4924 partitions = self.expressions(expression, "partition_expressions") 4925 create = self.expressions(expression, "create_expressions") 4926 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4927 4928 def partitionbyrangepropertydynamic_sql( 4929 self, expression: exp.PartitionByRangePropertyDynamic 4930 ) -> str: 4931 start = self.sql(expression, "start") 4932 end = self.sql(expression, "end") 4933 4934 every = expression.args["every"] 4935 if isinstance(every, exp.Interval) and every.this.is_string: 4936 every.this.replace(exp.Literal.number(every.name)) 4937 4938 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4939 4940 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4941 name = self.sql(expression, "this") 4942 values = self.expressions(expression, flat=True) 4943 4944 return f"NAME {name} VALUE {values}" 4945 4946 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4947 kind = self.sql(expression, "kind") 4948 sample = self.sql(expression, "sample") 4949 return f"SAMPLE {sample} {kind}" 4950 4951 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4952 kind = self.sql(expression, "kind") 4953 option = self.sql(expression, "option") 4954 option = f" {option}" if option else "" 4955 this = self.sql(expression, "this") 4956 this = f" {this}" if this else "" 4957 columns = self.expressions(expression) 4958 columns = f" {columns}" if columns else "" 4959 return f"{kind}{option} STATISTICS{this}{columns}" 4960 4961 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4962 this = self.sql(expression, "this") 4963 columns = self.expressions(expression) 4964 inner_expression = self.sql(expression, "expression") 4965 inner_expression = f" {inner_expression}" if inner_expression else "" 4966 update_options = self.sql(expression, "update_options") 4967 update_options = f" {update_options} UPDATE" if update_options else "" 4968 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4969 4970 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4971 kind = self.sql(expression, "kind") 4972 kind = f" {kind}" if kind else "" 4973 return f"DELETE{kind} STATISTICS" 4974 4975 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4976 inner_expression = self.sql(expression, "expression") 4977 return f"LIST CHAINED ROWS{inner_expression}" 4978 4979 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4980 kind = self.sql(expression, "kind") 4981 this = self.sql(expression, "this") 4982 this = f" {this}" if this else "" 4983 inner_expression = self.sql(expression, "expression") 4984 return f"VALIDATE {kind}{this}{inner_expression}" 4985 4986 def analyze_sql(self, expression: exp.Analyze) -> str: 4987 options = self.expressions(expression, key="options", sep=" ") 4988 options = f" {options}" if options else "" 4989 kind = self.sql(expression, "kind") 4990 kind = f" {kind}" if kind else "" 4991 this = self.sql(expression, "this") 4992 this = f" {this}" if this else "" 4993 mode = self.sql(expression, "mode") 4994 mode = f" {mode}" if mode else "" 4995 properties = self.sql(expression, "properties") 4996 properties = f" {properties}" if properties else "" 4997 partition = self.sql(expression, "partition") 4998 partition = f" {partition}" if partition else "" 4999 inner_expression = self.sql(expression, "expression") 5000 inner_expression = f" {inner_expression}" if inner_expression else "" 5001 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5002 5003 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5004 this = self.sql(expression, "this") 5005 namespaces = self.expressions(expression, key="namespaces") 5006 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5007 passing = self.expressions(expression, key="passing") 5008 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5009 columns = self.expressions(expression, key="columns") 5010 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5011 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5012 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5013 5014 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5015 this = self.sql(expression, "this") 5016 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5017 5018 def export_sql(self, expression: exp.Export) -> str: 5019 this = self.sql(expression, "this") 5020 connection = self.sql(expression, "connection") 5021 connection = f"WITH CONNECTION {connection} " if connection else "" 5022 options = self.sql(expression, "options") 5023 return f"EXPORT DATA {connection}{options} AS {this}" 5024 5025 def declare_sql(self, expression: exp.Declare) -> str: 5026 return f"DECLARE {self.expressions(expression, flat=True)}" 5027 5028 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5029 variable = self.sql(expression, "this") 5030 default = self.sql(expression, "default") 5031 default = f" = {default}" if default else "" 5032 5033 kind = self.sql(expression, "kind") 5034 if isinstance(expression.args.get("kind"), exp.Schema): 5035 kind = f"TABLE {kind}" 5036 5037 return f"{variable} AS {kind}{default}" 5038 5039 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5040 kind = self.sql(expression, "kind") 5041 this = self.sql(expression, "this") 5042 set = self.sql(expression, "expression") 5043 using = self.sql(expression, "using") 5044 using = f" USING {using}" if using else "" 5045 5046 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5047 5048 return f"{kind_sql} {this} SET {set}{using}" 5049 5050 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5051 params = self.expressions(expression, key="params", flat=True) 5052 return self.func(expression.name, *expression.expressions) + f"({params})" 5053 5054 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5055 return self.func(expression.name, *expression.expressions) 5056 5057 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5058 return self.anonymousaggfunc_sql(expression) 5059 5060 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5061 return self.parameterizedagg_sql(expression) 5062 5063 def show_sql(self, expression: exp.Show) -> str: 5064 self.unsupported("Unsupported SHOW statement") 5065 return "" 5066 5067 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5068 # Snowflake GET/PUT statements: 5069 # PUT <file> <internalStage> <properties> 5070 # GET <internalStage> <file> <properties> 5071 props = expression.args.get("properties") 5072 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5073 this = self.sql(expression, "this") 5074 target = self.sql(expression, "target") 5075 5076 if isinstance(expression, exp.Put): 5077 return f"PUT {this} {target}{props_sql}" 5078 else: 5079 return f"GET {target} {this}{props_sql}" 5080 5081 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5082 this = self.sql(expression, "this") 5083 expr = self.sql(expression, "expression") 5084 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5085 return f"TRANSLATE({this} USING {expr}{with_error})" 5086 5087 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5088 if self.SUPPORTS_DECODE_CASE: 5089 return self.func("DECODE", *expression.expressions) 5090 5091 expression, *expressions = expression.expressions 5092 5093 ifs = [] 5094 for search, result in zip(expressions[::2], expressions[1::2]): 5095 if isinstance(search, exp.Literal): 5096 ifs.append(exp.If(this=expression.eq(search), true=result)) 5097 elif isinstance(search, exp.Null): 5098 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5099 else: 5100 if isinstance(search, exp.Binary): 5101 search = exp.paren(search) 5102 5103 cond = exp.or_( 5104 expression.eq(search), 5105 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5106 copy=False, 5107 ) 5108 ifs.append(exp.If(this=cond, true=result)) 5109 5110 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5111 return self.sql(case) 5112 5113 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5114 this = self.sql(expression, "this") 5115 this = self.seg(this, sep="") 5116 dimensions = self.expressions( 5117 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5118 ) 5119 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5120 metrics = self.expressions( 5121 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5122 ) 5123 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5124 where = self.sql(expression, "where") 5125 where = self.seg(f"WHERE {where}") if where else "" 5126 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}" 5127 5128 def getextract_sql(self, expression: exp.GetExtract) -> str: 5129 this = expression.this 5130 expr = expression.expression 5131 5132 if not this.type or not expression.type: 5133 from sqlglot.optimizer.annotate_types import annotate_types 5134 5135 this = annotate_types(this, dialect=self.dialect) 5136 5137 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5138 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5139 5140 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5141 5142 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5143 return self.sql( 5144 exp.DateAdd( 5145 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5146 expression=expression.this, 5147 unit=exp.var("DAY"), 5148 ) 5149 )
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)
720 def __init__( 721 self, 722 pretty: t.Optional[bool] = None, 723 identify: str | bool = False, 724 normalize: bool = False, 725 pad: int = 2, 726 indent: int = 2, 727 normalize_functions: t.Optional[str | bool] = None, 728 unsupported_level: ErrorLevel = ErrorLevel.WARN, 729 max_unsupported: int = 3, 730 leading_comma: bool = False, 731 max_text_width: int = 80, 732 comments: bool = True, 733 dialect: DialectType = None, 734 ): 735 import sqlglot 736 from sqlglot.dialects import Dialect 737 738 self.pretty = pretty if pretty is not None else sqlglot.pretty 739 self.identify = identify 740 self.normalize = normalize 741 self.pad = pad 742 self._indent = indent 743 self.unsupported_level = unsupported_level 744 self.max_unsupported = max_unsupported 745 self.leading_comma = leading_comma 746 self.max_text_width = max_text_width 747 self.comments = comments 748 self.dialect = Dialect.get_or_raise(dialect) 749 750 # This is both a Dialect property and a Generator argument, so we prioritize the latter 751 self.normalize_functions = ( 752 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 753 ) 754 755 self.unsupported_messages: t.List[str] = [] 756 self._escaped_quote_end: str = ( 757 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 758 ) 759 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 760 761 self._next_name = name_sequence("_t") 762 763 self._identifier_start = self.dialect.IDENTIFIER_START 764 self._identifier_end = self.dialect.IDENTIFIER_END 765 766 self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] =
{<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.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.JSONPathSlice'>, <class 'sqlglot.expressions.JSONPathScript'>, <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'>}
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.CHAR: 'CHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.NCHAR: 'NCHAR'>}
768 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 769 """ 770 Generates the SQL string corresponding to the given syntax tree. 771 772 Args: 773 expression: The syntax tree. 774 copy: Whether to copy the expression. The generator performs mutations so 775 it is safer to copy. 776 777 Returns: 778 The SQL string corresponding to `expression`. 779 """ 780 if copy: 781 expression = expression.copy() 782 783 expression = self.preprocess(expression) 784 785 self.unsupported_messages = [] 786 sql = self.sql(expression).strip() 787 788 if self.pretty: 789 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 790 791 if self.unsupported_level == ErrorLevel.IGNORE: 792 return sql 793 794 if self.unsupported_level == ErrorLevel.WARN: 795 for msg in self.unsupported_messages: 796 logger.warning(msg) 797 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 798 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 799 800 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:
802 def preprocess(self, expression: exp.Expression) -> exp.Expression: 803 """Apply generic preprocessing transformations to a given expression.""" 804 expression = self._move_ctes_to_top_level(expression) 805 806 if self.ENSURE_BOOLS: 807 from sqlglot.transforms import ensure_bools 808 809 expression = ensure_bools(expression) 810 811 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
835 def sanitize_comment(self, comment: str) -> str: 836 comment = " " + comment if comment[0].strip() else comment 837 comment = comment + " " if comment[-1].strip() else comment 838 839 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 840 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 841 comment = comment.replace("*/", "* /") 842 843 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
845 def maybe_comment( 846 self, 847 sql: str, 848 expression: t.Optional[exp.Expression] = None, 849 comments: t.Optional[t.List[str]] = None, 850 separated: bool = False, 851 ) -> str: 852 comments = ( 853 ((expression and expression.comments) if comments is None else comments) # type: ignore 854 if self.comments 855 else None 856 ) 857 858 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 859 return sql 860 861 comments_sql = " ".join( 862 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 863 ) 864 865 if not comments_sql: 866 return sql 867 868 comments_sql = self._replace_line_breaks(comments_sql) 869 870 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 871 return ( 872 f"{self.sep()}{comments_sql}{sql}" 873 if not sql or sql[0].isspace() 874 else f"{comments_sql}{self.sep()}{sql}" 875 ) 876 877 return f"{sql} {comments_sql}"
879 def wrap(self, expression: exp.Expression | str) -> str: 880 this_sql = ( 881 self.sql(expression) 882 if isinstance(expression, exp.UNWRAPPED_QUERIES) 883 else self.sql(expression, "this") 884 ) 885 if not this_sql: 886 return "()" 887 888 this_sql = self.indent(this_sql, level=1, pad=0) 889 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:
905 def indent( 906 self, 907 sql: str, 908 level: int = 0, 909 pad: t.Optional[int] = None, 910 skip_first: bool = False, 911 skip_last: bool = False, 912 ) -> str: 913 if not self.pretty or not sql: 914 return sql 915 916 pad = self.pad if pad is None else pad 917 lines = sql.split("\n") 918 919 return "\n".join( 920 ( 921 line 922 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 923 else f"{' ' * (level * self._indent + pad)}{line}" 924 ) 925 for i, line in enumerate(lines) 926 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
928 def sql( 929 self, 930 expression: t.Optional[str | exp.Expression], 931 key: t.Optional[str] = None, 932 comment: bool = True, 933 ) -> str: 934 if not expression: 935 return "" 936 937 if isinstance(expression, str): 938 return expression 939 940 if key: 941 value = expression.args.get(key) 942 if value: 943 return self.sql(value) 944 return "" 945 946 transform = self.TRANSFORMS.get(expression.__class__) 947 948 if callable(transform): 949 sql = transform(self, expression) 950 elif isinstance(expression, exp.Expression): 951 exp_handler_name = f"{expression.key}_sql" 952 953 if hasattr(self, exp_handler_name): 954 sql = getattr(self, exp_handler_name)(expression) 955 elif isinstance(expression, exp.Func): 956 sql = self.function_fallback_sql(expression) 957 elif isinstance(expression, exp.Property): 958 sql = self.property_sql(expression) 959 else: 960 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 961 else: 962 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 963 964 return self.maybe_comment(sql, expression) if self.comments and comment else sql
971 def cache_sql(self, expression: exp.Cache) -> str: 972 lazy = " LAZY" if expression.args.get("lazy") else "" 973 table = self.sql(expression, "this") 974 options = expression.args.get("options") 975 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 976 sql = self.sql(expression, "expression") 977 sql = f" AS{self.sep()}{sql}" if sql else "" 978 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 979 return self.prepend_ctes(expression, sql)
981 def characterset_sql(self, expression: exp.CharacterSet) -> str: 982 if isinstance(expression.parent, exp.Cast): 983 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 984 default = "DEFAULT " if expression.args.get("default") else "" 985 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
999 def column_sql(self, expression: exp.Column) -> str: 1000 join_mark = " (+)" if expression.args.get("join_mark") else "" 1001 1002 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1003 join_mark = "" 1004 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1005 1006 return f"{self.column_parts(expression)}{join_mark}"
1014 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1015 column = self.sql(expression, "this") 1016 kind = self.sql(expression, "kind") 1017 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1018 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1019 kind = f"{sep}{kind}" if kind else "" 1020 constraints = f" {constraints}" if constraints else "" 1021 position = self.sql(expression, "position") 1022 position = f" {position}" if position else "" 1023 1024 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1025 kind = "" 1026 1027 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1034 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1035 this = self.sql(expression, "this") 1036 if expression.args.get("not_null"): 1037 persisted = " PERSISTED NOT NULL" 1038 elif expression.args.get("persisted"): 1039 persisted = " PERSISTED" 1040 else: 1041 persisted = "" 1042 1043 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1056 def generatedasidentitycolumnconstraint_sql( 1057 self, expression: exp.GeneratedAsIdentityColumnConstraint 1058 ) -> str: 1059 this = "" 1060 if expression.this is not None: 1061 on_null = " ON NULL" if expression.args.get("on_null") else "" 1062 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1063 1064 start = expression.args.get("start") 1065 start = f"START WITH {start}" if start else "" 1066 increment = expression.args.get("increment") 1067 increment = f" INCREMENT BY {increment}" if increment else "" 1068 minvalue = expression.args.get("minvalue") 1069 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1070 maxvalue = expression.args.get("maxvalue") 1071 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1072 cycle = expression.args.get("cycle") 1073 cycle_sql = "" 1074 1075 if cycle is not None: 1076 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1077 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1078 1079 sequence_opts = "" 1080 if start or increment or cycle_sql: 1081 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1082 sequence_opts = f" ({sequence_opts.strip()})" 1083 1084 expr = self.sql(expression, "expression") 1085 expr = f"({expr})" if expr else "IDENTITY" 1086 1087 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1089 def generatedasrowcolumnconstraint_sql( 1090 self, expression: exp.GeneratedAsRowColumnConstraint 1091 ) -> str: 1092 start = "START" if expression.args.get("start") else "END" 1093 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1094 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:
1104 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1105 desc = expression.args.get("desc") 1106 if desc is not None: 1107 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1108 options = self.expressions(expression, key="options", flat=True, sep=" ") 1109 options = f" {options}" if options else "" 1110 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1112 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1113 this = self.sql(expression, "this") 1114 this = f" {this}" if this else "" 1115 index_type = expression.args.get("index_type") 1116 index_type = f" USING {index_type}" if index_type else "" 1117 on_conflict = self.sql(expression, "on_conflict") 1118 on_conflict = f" {on_conflict}" if on_conflict else "" 1119 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1120 options = self.expressions(expression, key="options", flat=True, sep=" ") 1121 options = f" {options}" if options else "" 1122 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1127 def create_sql(self, expression: exp.Create) -> str: 1128 kind = self.sql(expression, "kind") 1129 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1130 properties = expression.args.get("properties") 1131 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1132 1133 this = self.createable_sql(expression, properties_locs) 1134 1135 properties_sql = "" 1136 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1137 exp.Properties.Location.POST_WITH 1138 ): 1139 properties_sql = self.sql( 1140 exp.Properties( 1141 expressions=[ 1142 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1143 *properties_locs[exp.Properties.Location.POST_WITH], 1144 ] 1145 ) 1146 ) 1147 1148 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1149 properties_sql = self.sep() + properties_sql 1150 elif not self.pretty: 1151 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1152 properties_sql = f" {properties_sql}" 1153 1154 begin = " BEGIN" if expression.args.get("begin") else "" 1155 end = " END" if expression.args.get("end") else "" 1156 1157 expression_sql = self.sql(expression, "expression") 1158 if expression_sql: 1159 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1160 1161 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1162 postalias_props_sql = "" 1163 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1164 postalias_props_sql = self.properties( 1165 exp.Properties( 1166 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1167 ), 1168 wrapped=False, 1169 ) 1170 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1171 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1172 1173 postindex_props_sql = "" 1174 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1175 postindex_props_sql = self.properties( 1176 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1177 wrapped=False, 1178 prefix=" ", 1179 ) 1180 1181 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1182 indexes = f" {indexes}" if indexes else "" 1183 index_sql = indexes + postindex_props_sql 1184 1185 replace = " OR REPLACE" if expression.args.get("replace") else "" 1186 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1187 unique = " UNIQUE" if expression.args.get("unique") else "" 1188 1189 clustered = expression.args.get("clustered") 1190 if clustered is None: 1191 clustered_sql = "" 1192 elif clustered: 1193 clustered_sql = " CLUSTERED COLUMNSTORE" 1194 else: 1195 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1196 1197 postcreate_props_sql = "" 1198 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1199 postcreate_props_sql = self.properties( 1200 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1201 sep=" ", 1202 prefix=" ", 1203 wrapped=False, 1204 ) 1205 1206 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1207 1208 postexpression_props_sql = "" 1209 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1210 postexpression_props_sql = self.properties( 1211 exp.Properties( 1212 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1213 ), 1214 sep=" ", 1215 prefix=" ", 1216 wrapped=False, 1217 ) 1218 1219 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1220 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1221 no_schema_binding = ( 1222 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1223 ) 1224 1225 clone = self.sql(expression, "clone") 1226 clone = f" {clone}" if clone else "" 1227 1228 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1229 properties_expression = f"{expression_sql}{properties_sql}" 1230 else: 1231 properties_expression = f"{properties_sql}{expression_sql}" 1232 1233 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1234 return self.prepend_ctes(expression, expression_sql)
1236 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1237 start = self.sql(expression, "start") 1238 start = f"START WITH {start}" if start else "" 1239 increment = self.sql(expression, "increment") 1240 increment = f" INCREMENT BY {increment}" if increment else "" 1241 minvalue = self.sql(expression, "minvalue") 1242 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1243 maxvalue = self.sql(expression, "maxvalue") 1244 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1245 owned = self.sql(expression, "owned") 1246 owned = f" OWNED BY {owned}" if owned else "" 1247 1248 cache = expression.args.get("cache") 1249 if cache is None: 1250 cache_str = "" 1251 elif cache is True: 1252 cache_str = " CACHE" 1253 else: 1254 cache_str = f" CACHE {cache}" 1255 1256 options = self.expressions(expression, key="options", flat=True, sep=" ") 1257 options = f" {options}" if options else "" 1258 1259 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1261 def clone_sql(self, expression: exp.Clone) -> str: 1262 this = self.sql(expression, "this") 1263 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1264 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1265 return f"{shallow}{keyword} {this}"
1267 def describe_sql(self, expression: exp.Describe) -> str: 1268 style = expression.args.get("style") 1269 style = f" {style}" if style else "" 1270 partition = self.sql(expression, "partition") 1271 partition = f" {partition}" if partition else "" 1272 format = self.sql(expression, "format") 1273 format = f" {format}" if format else "" 1274 1275 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1287 def with_sql(self, expression: exp.With) -> str: 1288 sql = self.expressions(expression, flat=True) 1289 recursive = ( 1290 "RECURSIVE " 1291 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1292 else "" 1293 ) 1294 search = self.sql(expression, "search") 1295 search = f" {search}" if search else "" 1296 1297 return f"WITH {recursive}{sql}{search}"
1299 def cte_sql(self, expression: exp.CTE) -> str: 1300 alias = expression.args.get("alias") 1301 if alias: 1302 alias.add_comments(expression.pop_comments()) 1303 1304 alias_sql = self.sql(expression, "alias") 1305 1306 materialized = expression.args.get("materialized") 1307 if materialized is False: 1308 materialized = "NOT MATERIALIZED " 1309 elif materialized: 1310 materialized = "MATERIALIZED " 1311 1312 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1314 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1315 alias = self.sql(expression, "this") 1316 columns = self.expressions(expression, key="columns", flat=True) 1317 columns = f"({columns})" if columns else "" 1318 1319 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1320 columns = "" 1321 self.unsupported("Named columns are not supported in table alias.") 1322 1323 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1324 alias = self._next_name() 1325 1326 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1334 def hexstring_sql( 1335 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1336 ) -> str: 1337 this = self.sql(expression, "this") 1338 is_integer_type = expression.args.get("is_integer") 1339 1340 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1341 not self.dialect.HEX_START and not binary_function_repr 1342 ): 1343 # Integer representation will be returned if: 1344 # - The read dialect treats the hex value as integer literal but not the write 1345 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1346 return f"{int(this, 16)}" 1347 1348 if not is_integer_type: 1349 # Read dialect treats the hex value as BINARY/BLOB 1350 if binary_function_repr: 1351 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1352 return self.func(binary_function_repr, exp.Literal.string(this)) 1353 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1354 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1355 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1356 1357 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1365 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1366 this = self.sql(expression, "this") 1367 escape = expression.args.get("escape") 1368 1369 if self.dialect.UNICODE_START: 1370 escape_substitute = r"\\\1" 1371 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1372 else: 1373 escape_substitute = r"\\u\1" 1374 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1375 1376 if escape: 1377 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1378 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1379 else: 1380 escape_pattern = ESCAPED_UNICODE_RE 1381 escape_sql = "" 1382 1383 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1384 this = escape_pattern.sub(escape_substitute, this) 1385 1386 return f"{left_quote}{this}{right_quote}{escape_sql}"
1388 def rawstring_sql(self, expression: exp.RawString) -> str: 1389 string = expression.this 1390 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1391 string = string.replace("\\", "\\\\") 1392 1393 string = self.escape_str(string, escape_backslash=False) 1394 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1402 def datatype_sql(self, expression: exp.DataType) -> str: 1403 nested = "" 1404 values = "" 1405 interior = self.expressions(expression, flat=True) 1406 1407 type_value = expression.this 1408 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1409 type_sql = self.sql(expression, "kind") 1410 else: 1411 type_sql = ( 1412 self.TYPE_MAPPING.get(type_value, type_value.value) 1413 if isinstance(type_value, exp.DataType.Type) 1414 else type_value 1415 ) 1416 1417 if interior: 1418 if expression.args.get("nested"): 1419 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1420 if expression.args.get("values") is not None: 1421 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1422 values = self.expressions(expression, key="values", flat=True) 1423 values = f"{delimiters[0]}{values}{delimiters[1]}" 1424 elif type_value == exp.DataType.Type.INTERVAL: 1425 nested = f" {interior}" 1426 else: 1427 nested = f"({interior})" 1428 1429 type_sql = f"{type_sql}{nested}{values}" 1430 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1431 exp.DataType.Type.TIMETZ, 1432 exp.DataType.Type.TIMESTAMPTZ, 1433 ): 1434 type_sql = f"{type_sql} WITH TIME ZONE" 1435 1436 return type_sql
1438 def directory_sql(self, expression: exp.Directory) -> str: 1439 local = "LOCAL " if expression.args.get("local") else "" 1440 row_format = self.sql(expression, "row_format") 1441 row_format = f" {row_format}" if row_format else "" 1442 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1444 def delete_sql(self, expression: exp.Delete) -> str: 1445 this = self.sql(expression, "this") 1446 this = f" FROM {this}" if this else "" 1447 using = self.sql(expression, "using") 1448 using = f" USING {using}" if using else "" 1449 cluster = self.sql(expression, "cluster") 1450 cluster = f" {cluster}" if cluster else "" 1451 where = self.sql(expression, "where") 1452 returning = self.sql(expression, "returning") 1453 limit = self.sql(expression, "limit") 1454 tables = self.expressions(expression, key="tables") 1455 tables = f" {tables}" if tables else "" 1456 if self.RETURNING_END: 1457 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1458 else: 1459 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1460 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1462 def drop_sql(self, expression: exp.Drop) -> str: 1463 this = self.sql(expression, "this") 1464 expressions = self.expressions(expression, flat=True) 1465 expressions = f" ({expressions})" if expressions else "" 1466 kind = expression.args["kind"] 1467 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1468 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1469 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1470 on_cluster = self.sql(expression, "cluster") 1471 on_cluster = f" {on_cluster}" if on_cluster else "" 1472 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1473 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1474 cascade = " CASCADE" if expression.args.get("cascade") else "" 1475 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1476 purge = " PURGE" if expression.args.get("purge") else "" 1477 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1479 def set_operation(self, expression: exp.SetOperation) -> str: 1480 op_type = type(expression) 1481 op_name = op_type.key.upper() 1482 1483 distinct = expression.args.get("distinct") 1484 if ( 1485 distinct is False 1486 and op_type in (exp.Except, exp.Intersect) 1487 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1488 ): 1489 self.unsupported(f"{op_name} ALL is not supported") 1490 1491 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1492 1493 if distinct is None: 1494 distinct = default_distinct 1495 if distinct is None: 1496 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1497 1498 if distinct is default_distinct: 1499 distinct_or_all = "" 1500 else: 1501 distinct_or_all = " DISTINCT" if distinct else " ALL" 1502 1503 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1504 side_kind = f"{side_kind} " if side_kind else "" 1505 1506 by_name = " BY NAME" if expression.args.get("by_name") else "" 1507 on = self.expressions(expression, key="on", flat=True) 1508 on = f" ON ({on})" if on else "" 1509 1510 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1512 def set_operations(self, expression: exp.SetOperation) -> str: 1513 if not self.SET_OP_MODIFIERS: 1514 limit = expression.args.get("limit") 1515 order = expression.args.get("order") 1516 1517 if limit or order: 1518 select = self._move_ctes_to_top_level( 1519 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1520 ) 1521 1522 if limit: 1523 select = select.limit(limit.pop(), copy=False) 1524 if order: 1525 select = select.order_by(order.pop(), copy=False) 1526 return self.sql(select) 1527 1528 sqls: t.List[str] = [] 1529 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1530 1531 while stack: 1532 node = stack.pop() 1533 1534 if isinstance(node, exp.SetOperation): 1535 stack.append(node.expression) 1536 stack.append( 1537 self.maybe_comment( 1538 self.set_operation(node), comments=node.comments, separated=True 1539 ) 1540 ) 1541 stack.append(node.this) 1542 else: 1543 sqls.append(self.sql(node)) 1544 1545 this = self.sep().join(sqls) 1546 this = self.query_modifiers(expression, this) 1547 return self.prepend_ctes(expression, this)
1549 def fetch_sql(self, expression: exp.Fetch) -> str: 1550 direction = expression.args.get("direction") 1551 direction = f" {direction}" if direction else "" 1552 count = self.sql(expression, "count") 1553 count = f" {count}" if count else "" 1554 limit_options = self.sql(expression, "limit_options") 1555 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1556 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1558 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1559 percent = " PERCENT" if expression.args.get("percent") else "" 1560 rows = " ROWS" if expression.args.get("rows") else "" 1561 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1562 if not with_ties and rows: 1563 with_ties = " ONLY" 1564 return f"{percent}{rows}{with_ties}"
1566 def filter_sql(self, expression: exp.Filter) -> str: 1567 if self.AGGREGATE_FILTER_SUPPORTED: 1568 this = self.sql(expression, "this") 1569 where = self.sql(expression, "expression").strip() 1570 return f"{this} FILTER({where})" 1571 1572 agg = expression.this 1573 agg_arg = agg.this 1574 cond = expression.expression.this 1575 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1576 return self.sql(agg)
1585 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1586 using = self.sql(expression, "using") 1587 using = f" USING {using}" if using else "" 1588 columns = self.expressions(expression, key="columns", flat=True) 1589 columns = f"({columns})" if columns else "" 1590 partition_by = self.expressions(expression, key="partition_by", flat=True) 1591 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1592 where = self.sql(expression, "where") 1593 include = self.expressions(expression, key="include", flat=True) 1594 if include: 1595 include = f" INCLUDE ({include})" 1596 with_storage = self.expressions(expression, key="with_storage", flat=True) 1597 with_storage = f" WITH ({with_storage})" if with_storage else "" 1598 tablespace = self.sql(expression, "tablespace") 1599 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1600 on = self.sql(expression, "on") 1601 on = f" ON {on}" if on else "" 1602 1603 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1605 def index_sql(self, expression: exp.Index) -> str: 1606 unique = "UNIQUE " if expression.args.get("unique") else "" 1607 primary = "PRIMARY " if expression.args.get("primary") else "" 1608 amp = "AMP " if expression.args.get("amp") else "" 1609 name = self.sql(expression, "this") 1610 name = f"{name} " if name else "" 1611 table = self.sql(expression, "table") 1612 table = f"{self.INDEX_ON} {table}" if table else "" 1613 1614 index = "INDEX " if not table else "" 1615 1616 params = self.sql(expression, "params") 1617 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1619 def identifier_sql(self, expression: exp.Identifier) -> str: 1620 text = expression.name 1621 lower = text.lower() 1622 text = lower if self.normalize and not expression.quoted else text 1623 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1624 if ( 1625 expression.quoted 1626 or self.dialect.can_identify(text, self.identify) 1627 or lower in self.RESERVED_KEYWORDS 1628 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1629 ): 1630 text = f"{self._identifier_start}{text}{self._identifier_end}" 1631 return text
1646 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1647 input_format = self.sql(expression, "input_format") 1648 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1649 output_format = self.sql(expression, "output_format") 1650 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1651 return self.sep().join((input_format, output_format))
1661 def properties_sql(self, expression: exp.Properties) -> str: 1662 root_properties = [] 1663 with_properties = [] 1664 1665 for p in expression.expressions: 1666 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1667 if p_loc == exp.Properties.Location.POST_WITH: 1668 with_properties.append(p) 1669 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1670 root_properties.append(p) 1671 1672 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1673 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1674 1675 if root_props and with_props and not self.pretty: 1676 with_props = " " + with_props 1677 1678 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1685 def properties( 1686 self, 1687 properties: exp.Properties, 1688 prefix: str = "", 1689 sep: str = ", ", 1690 suffix: str = "", 1691 wrapped: bool = True, 1692 ) -> str: 1693 if properties.expressions: 1694 expressions = self.expressions(properties, sep=sep, indent=False) 1695 if expressions: 1696 expressions = self.wrap(expressions) if wrapped else expressions 1697 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1698 return ""
1703 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1704 properties_locs = defaultdict(list) 1705 for p in properties.expressions: 1706 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1707 if p_loc != exp.Properties.Location.UNSUPPORTED: 1708 properties_locs[p_loc].append(p) 1709 else: 1710 self.unsupported(f"Unsupported property {p.key}") 1711 1712 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1719 def property_sql(self, expression: exp.Property) -> str: 1720 property_cls = expression.__class__ 1721 if property_cls == exp.Property: 1722 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1723 1724 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1725 if not property_name: 1726 self.unsupported(f"Unsupported property {expression.key}") 1727 1728 return f"{property_name}={self.sql(expression, 'this')}"
1730 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1731 if self.SUPPORTS_CREATE_TABLE_LIKE: 1732 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1733 options = f" {options}" if options else "" 1734 1735 like = f"LIKE {self.sql(expression, 'this')}{options}" 1736 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1737 like = f"({like})" 1738 1739 return like 1740 1741 if expression.expressions: 1742 self.unsupported("Transpilation of LIKE property options is unsupported") 1743 1744 select = exp.select("*").from_(expression.this).limit(0) 1745 return f"AS {self.sql(select)}"
1752 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1753 no = "NO " if expression.args.get("no") else "" 1754 local = expression.args.get("local") 1755 local = f"{local} " if local else "" 1756 dual = "DUAL " if expression.args.get("dual") else "" 1757 before = "BEFORE " if expression.args.get("before") else "" 1758 after = "AFTER " if expression.args.get("after") else "" 1759 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1775 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1776 if expression.args.get("no"): 1777 return "NO MERGEBLOCKRATIO" 1778 if expression.args.get("default"): 1779 return "DEFAULT MERGEBLOCKRATIO" 1780 1781 percent = " PERCENT" if expression.args.get("percent") else "" 1782 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1784 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1785 default = expression.args.get("default") 1786 minimum = expression.args.get("minimum") 1787 maximum = expression.args.get("maximum") 1788 if default or minimum or maximum: 1789 if default: 1790 prop = "DEFAULT" 1791 elif minimum: 1792 prop = "MINIMUM" 1793 else: 1794 prop = "MAXIMUM" 1795 return f"{prop} DATABLOCKSIZE" 1796 units = expression.args.get("units") 1797 units = f" {units}" if units else "" 1798 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1800 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1801 autotemp = expression.args.get("autotemp") 1802 always = expression.args.get("always") 1803 default = expression.args.get("default") 1804 manual = expression.args.get("manual") 1805 never = expression.args.get("never") 1806 1807 if autotemp is not None: 1808 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1809 elif always: 1810 prop = "ALWAYS" 1811 elif default: 1812 prop = "DEFAULT" 1813 elif manual: 1814 prop = "MANUAL" 1815 elif never: 1816 prop = "NEVER" 1817 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1819 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1820 no = expression.args.get("no") 1821 no = " NO" if no else "" 1822 concurrent = expression.args.get("concurrent") 1823 concurrent = " CONCURRENT" if concurrent else "" 1824 target = self.sql(expression, "target") 1825 target = f" {target}" if target else "" 1826 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1828 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1829 if isinstance(expression.this, list): 1830 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1831 if expression.this: 1832 modulus = self.sql(expression, "this") 1833 remainder = self.sql(expression, "expression") 1834 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1835 1836 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1837 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1838 return f"FROM ({from_expressions}) TO ({to_expressions})"
1840 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1841 this = self.sql(expression, "this") 1842 1843 for_values_or_default = expression.expression 1844 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1845 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1846 else: 1847 for_values_or_default = " DEFAULT" 1848 1849 return f"PARTITION OF {this}{for_values_or_default}"
1851 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1852 kind = expression.args.get("kind") 1853 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1854 for_or_in = expression.args.get("for_or_in") 1855 for_or_in = f" {for_or_in}" if for_or_in else "" 1856 lock_type = expression.args.get("lock_type") 1857 override = " OVERRIDE" if expression.args.get("override") else "" 1858 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1860 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1861 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1862 statistics = expression.args.get("statistics") 1863 statistics_sql = "" 1864 if statistics is not None: 1865 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1866 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1868 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1869 this = self.sql(expression, "this") 1870 this = f"HISTORY_TABLE={this}" if this else "" 1871 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1872 data_consistency = ( 1873 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1874 ) 1875 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1876 retention_period = ( 1877 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1878 ) 1879 1880 if this: 1881 on_sql = self.func("ON", this, data_consistency, retention_period) 1882 else: 1883 on_sql = "ON" if expression.args.get("on") else "OFF" 1884 1885 sql = f"SYSTEM_VERSIONING={on_sql}" 1886 1887 return f"WITH({sql})" if expression.args.get("with") else sql
1889 def insert_sql(self, expression: exp.Insert) -> str: 1890 hint = self.sql(expression, "hint") 1891 overwrite = expression.args.get("overwrite") 1892 1893 if isinstance(expression.this, exp.Directory): 1894 this = " OVERWRITE" if overwrite else " INTO" 1895 else: 1896 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1897 1898 stored = self.sql(expression, "stored") 1899 stored = f" {stored}" if stored else "" 1900 alternative = expression.args.get("alternative") 1901 alternative = f" OR {alternative}" if alternative else "" 1902 ignore = " IGNORE" if expression.args.get("ignore") else "" 1903 is_function = expression.args.get("is_function") 1904 if is_function: 1905 this = f"{this} FUNCTION" 1906 this = f"{this} {self.sql(expression, 'this')}" 1907 1908 exists = " IF EXISTS" if expression.args.get("exists") else "" 1909 where = self.sql(expression, "where") 1910 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1911 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1912 on_conflict = self.sql(expression, "conflict") 1913 on_conflict = f" {on_conflict}" if on_conflict else "" 1914 by_name = " BY NAME" if expression.args.get("by_name") else "" 1915 returning = self.sql(expression, "returning") 1916 1917 if self.RETURNING_END: 1918 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1919 else: 1920 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1921 1922 partition_by = self.sql(expression, "partition") 1923 partition_by = f" {partition_by}" if partition_by else "" 1924 settings = self.sql(expression, "settings") 1925 settings = f" {settings}" if settings else "" 1926 1927 source = self.sql(expression, "source") 1928 source = f"TABLE {source}" if source else "" 1929 1930 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1931 return self.prepend_ctes(expression, sql)
1949 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1950 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1951 1952 constraint = self.sql(expression, "constraint") 1953 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1954 1955 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1956 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1957 action = self.sql(expression, "action") 1958 1959 expressions = self.expressions(expression, flat=True) 1960 if expressions: 1961 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1962 expressions = f" {set_keyword}{expressions}" 1963 1964 where = self.sql(expression, "where") 1965 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1970 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1971 fields = self.sql(expression, "fields") 1972 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1973 escaped = self.sql(expression, "escaped") 1974 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1975 items = self.sql(expression, "collection_items") 1976 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1977 keys = self.sql(expression, "map_keys") 1978 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1979 lines = self.sql(expression, "lines") 1980 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1981 null = self.sql(expression, "null") 1982 null = f" NULL DEFINED AS {null}" if null else "" 1983 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2011 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2012 table = self.table_parts(expression) 2013 only = "ONLY " if expression.args.get("only") else "" 2014 partition = self.sql(expression, "partition") 2015 partition = f" {partition}" if partition else "" 2016 version = self.sql(expression, "version") 2017 version = f" {version}" if version else "" 2018 alias = self.sql(expression, "alias") 2019 alias = f"{sep}{alias}" if alias else "" 2020 2021 sample = self.sql(expression, "sample") 2022 if self.dialect.ALIAS_POST_TABLESAMPLE: 2023 sample_pre_alias = sample 2024 sample_post_alias = "" 2025 else: 2026 sample_pre_alias = "" 2027 sample_post_alias = sample 2028 2029 hints = self.expressions(expression, key="hints", sep=" ") 2030 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2031 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2032 joins = self.indent( 2033 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2034 ) 2035 laterals = self.expressions(expression, key="laterals", sep="") 2036 2037 file_format = self.sql(expression, "format") 2038 if file_format: 2039 pattern = self.sql(expression, "pattern") 2040 pattern = f", PATTERN => {pattern}" if pattern else "" 2041 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2042 2043 ordinality = expression.args.get("ordinality") or "" 2044 if ordinality: 2045 ordinality = f" WITH ORDINALITY{alias}" 2046 alias = "" 2047 2048 when = self.sql(expression, "when") 2049 if when: 2050 table = f"{table} {when}" 2051 2052 changes = self.sql(expression, "changes") 2053 changes = f" {changes}" if changes else "" 2054 2055 rows_from = self.expressions(expression, key="rows_from") 2056 if rows_from: 2057 table = f"ROWS FROM {self.wrap(rows_from)}" 2058 2059 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2061 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2062 table = self.func("TABLE", expression.this) 2063 alias = self.sql(expression, "alias") 2064 alias = f" AS {alias}" if alias else "" 2065 sample = self.sql(expression, "sample") 2066 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2067 joins = self.indent( 2068 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2069 ) 2070 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2072 def tablesample_sql( 2073 self, 2074 expression: exp.TableSample, 2075 tablesample_keyword: t.Optional[str] = None, 2076 ) -> str: 2077 method = self.sql(expression, "method") 2078 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2079 numerator = self.sql(expression, "bucket_numerator") 2080 denominator = self.sql(expression, "bucket_denominator") 2081 field = self.sql(expression, "bucket_field") 2082 field = f" ON {field}" if field else "" 2083 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2084 seed = self.sql(expression, "seed") 2085 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2086 2087 size = self.sql(expression, "size") 2088 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2089 size = f"{size} ROWS" 2090 2091 percent = self.sql(expression, "percent") 2092 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2093 percent = f"{percent} PERCENT" 2094 2095 expr = f"{bucket}{percent}{size}" 2096 if self.TABLESAMPLE_REQUIRES_PARENS: 2097 expr = f"({expr})" 2098 2099 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2101 def pivot_sql(self, expression: exp.Pivot) -> str: 2102 expressions = self.expressions(expression, flat=True) 2103 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2104 2105 group = self.sql(expression, "group") 2106 2107 if expression.this: 2108 this = self.sql(expression, "this") 2109 if not expressions: 2110 return f"UNPIVOT {this}" 2111 2112 on = f"{self.seg('ON')} {expressions}" 2113 into = self.sql(expression, "into") 2114 into = f"{self.seg('INTO')} {into}" if into else "" 2115 using = self.expressions(expression, key="using", flat=True) 2116 using = f"{self.seg('USING')} {using}" if using else "" 2117 return f"{direction} {this}{on}{into}{using}{group}" 2118 2119 alias = self.sql(expression, "alias") 2120 alias = f" AS {alias}" if alias else "" 2121 2122 fields = self.expressions( 2123 expression, 2124 "fields", 2125 sep=" ", 2126 dynamic=True, 2127 new_line=True, 2128 skip_first=True, 2129 skip_last=True, 2130 ) 2131 2132 include_nulls = expression.args.get("include_nulls") 2133 if include_nulls is not None: 2134 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2135 else: 2136 nulls = "" 2137 2138 default_on_null = self.sql(expression, "default_on_null") 2139 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2140 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2151 def update_sql(self, expression: exp.Update) -> str: 2152 this = self.sql(expression, "this") 2153 set_sql = self.expressions(expression, flat=True) 2154 from_sql = self.sql(expression, "from") 2155 where_sql = self.sql(expression, "where") 2156 returning = self.sql(expression, "returning") 2157 order = self.sql(expression, "order") 2158 limit = self.sql(expression, "limit") 2159 if self.RETURNING_END: 2160 expression_sql = f"{from_sql}{where_sql}{returning}" 2161 else: 2162 expression_sql = f"{returning}{from_sql}{where_sql}" 2163 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2164 return self.prepend_ctes(expression, sql)
2166 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2167 values_as_table = values_as_table and self.VALUES_AS_TABLE 2168 2169 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2170 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2171 args = self.expressions(expression) 2172 alias = self.sql(expression, "alias") 2173 values = f"VALUES{self.seg('')}{args}" 2174 values = ( 2175 f"({values})" 2176 if self.WRAP_DERIVED_VALUES 2177 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2178 else values 2179 ) 2180 return f"{values} AS {alias}" if alias else values 2181 2182 # Converts `VALUES...` expression into a series of select unions. 2183 alias_node = expression.args.get("alias") 2184 column_names = alias_node and alias_node.columns 2185 2186 selects: t.List[exp.Query] = [] 2187 2188 for i, tup in enumerate(expression.expressions): 2189 row = tup.expressions 2190 2191 if i == 0 and column_names: 2192 row = [ 2193 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2194 ] 2195 2196 selects.append(exp.Select(expressions=row)) 2197 2198 if self.pretty: 2199 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2200 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2201 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2202 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2203 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2204 2205 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2206 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2207 return f"({unions}){alias}"
2212 @unsupported_args("expressions") 2213 def into_sql(self, expression: exp.Into) -> str: 2214 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2215 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2216 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2233 def group_sql(self, expression: exp.Group) -> str: 2234 group_by_all = expression.args.get("all") 2235 if group_by_all is True: 2236 modifier = " ALL" 2237 elif group_by_all is False: 2238 modifier = " DISTINCT" 2239 else: 2240 modifier = "" 2241 2242 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2243 2244 grouping_sets = self.expressions(expression, key="grouping_sets") 2245 cube = self.expressions(expression, key="cube") 2246 rollup = self.expressions(expression, key="rollup") 2247 2248 groupings = csv( 2249 self.seg(grouping_sets) if grouping_sets else "", 2250 self.seg(cube) if cube else "", 2251 self.seg(rollup) if rollup else "", 2252 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2253 sep=self.GROUPINGS_SEP, 2254 ) 2255 2256 if ( 2257 expression.expressions 2258 and groupings 2259 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2260 ): 2261 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2262 2263 return f"{group_by}{groupings}"
2269 def connect_sql(self, expression: exp.Connect) -> str: 2270 start = self.sql(expression, "start") 2271 start = self.seg(f"START WITH {start}") if start else "" 2272 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2273 connect = self.sql(expression, "connect") 2274 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2275 return start + connect
2280 def join_sql(self, expression: exp.Join) -> str: 2281 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2282 side = None 2283 else: 2284 side = expression.side 2285 2286 op_sql = " ".join( 2287 op 2288 for op in ( 2289 expression.method, 2290 "GLOBAL" if expression.args.get("global") else None, 2291 side, 2292 expression.kind, 2293 expression.hint if self.JOIN_HINTS else None, 2294 ) 2295 if op 2296 ) 2297 match_cond = self.sql(expression, "match_condition") 2298 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2299 on_sql = self.sql(expression, "on") 2300 using = expression.args.get("using") 2301 2302 if not on_sql and using: 2303 on_sql = csv(*(self.sql(column) for column in using)) 2304 2305 this = expression.this 2306 this_sql = self.sql(this) 2307 2308 exprs = self.expressions(expression) 2309 if exprs: 2310 this_sql = f"{this_sql},{self.seg(exprs)}" 2311 2312 if on_sql: 2313 on_sql = self.indent(on_sql, skip_first=True) 2314 space = self.seg(" " * self.pad) if self.pretty else " " 2315 if using: 2316 on_sql = f"{space}USING ({on_sql})" 2317 else: 2318 on_sql = f"{space}ON {on_sql}" 2319 elif not op_sql: 2320 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2321 return f" {this_sql}" 2322 2323 return f", {this_sql}" 2324 2325 if op_sql != "STRAIGHT_JOIN": 2326 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2327 2328 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2329 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:
2336 def lateral_op(self, expression: exp.Lateral) -> str: 2337 cross_apply = expression.args.get("cross_apply") 2338 2339 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2340 if cross_apply is True: 2341 op = "INNER JOIN " 2342 elif cross_apply is False: 2343 op = "LEFT JOIN " 2344 else: 2345 op = "" 2346 2347 return f"{op}LATERAL"
2349 def lateral_sql(self, expression: exp.Lateral) -> str: 2350 this = self.sql(expression, "this") 2351 2352 if expression.args.get("view"): 2353 alias = expression.args["alias"] 2354 columns = self.expressions(alias, key="columns", flat=True) 2355 table = f" {alias.name}" if alias.name else "" 2356 columns = f" AS {columns}" if columns else "" 2357 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2358 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2359 2360 alias = self.sql(expression, "alias") 2361 alias = f" AS {alias}" if alias else "" 2362 2363 ordinality = expression.args.get("ordinality") or "" 2364 if ordinality: 2365 ordinality = f" WITH ORDINALITY{alias}" 2366 alias = "" 2367 2368 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2370 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2371 this = self.sql(expression, "this") 2372 2373 args = [ 2374 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2375 for e in (expression.args.get(k) for k in ("offset", "expression")) 2376 if e 2377 ] 2378 2379 args_sql = ", ".join(self.sql(e) for e in args) 2380 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2381 expressions = self.expressions(expression, flat=True) 2382 limit_options = self.sql(expression, "limit_options") 2383 expressions = f" BY {expressions}" if expressions else "" 2384 2385 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2387 def offset_sql(self, expression: exp.Offset) -> str: 2388 this = self.sql(expression, "this") 2389 value = expression.expression 2390 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2391 expressions = self.expressions(expression, flat=True) 2392 expressions = f" BY {expressions}" if expressions else "" 2393 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2395 def setitem_sql(self, expression: exp.SetItem) -> str: 2396 kind = self.sql(expression, "kind") 2397 kind = f"{kind} " if kind else "" 2398 this = self.sql(expression, "this") 2399 expressions = self.expressions(expression) 2400 collate = self.sql(expression, "collate") 2401 collate = f" COLLATE {collate}" if collate else "" 2402 global_ = "GLOBAL " if expression.args.get("global") else "" 2403 return f"{global_}{kind}{this}{expressions}{collate}"
2410 def queryband_sql(self, expression: exp.QueryBand) -> str: 2411 this = self.sql(expression, "this") 2412 update = " UPDATE" if expression.args.get("update") else "" 2413 scope = self.sql(expression, "scope") 2414 scope = f" FOR {scope}" if scope else "" 2415 2416 return f"QUERY_BAND = {this}{update}{scope}"
2421 def lock_sql(self, expression: exp.Lock) -> str: 2422 if not self.LOCKING_READS_SUPPORTED: 2423 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2424 return "" 2425 2426 update = expression.args["update"] 2427 key = expression.args.get("key") 2428 if update: 2429 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2430 else: 2431 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2432 expressions = self.expressions(expression, flat=True) 2433 expressions = f" OF {expressions}" if expressions else "" 2434 wait = expression.args.get("wait") 2435 2436 if wait is not None: 2437 if isinstance(wait, exp.Literal): 2438 wait = f" WAIT {self.sql(wait)}" 2439 else: 2440 wait = " NOWAIT" if wait else " SKIP LOCKED" 2441 2442 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2450 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2451 if self.dialect.ESCAPED_SEQUENCES: 2452 to_escaped = self.dialect.ESCAPED_SEQUENCES 2453 text = "".join( 2454 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2455 ) 2456 2457 return self._replace_line_breaks(text).replace( 2458 self.dialect.QUOTE_END, self._escaped_quote_end 2459 )
2461 def loaddata_sql(self, expression: exp.LoadData) -> str: 2462 local = " LOCAL" if expression.args.get("local") else "" 2463 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2464 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2465 this = f" INTO TABLE {self.sql(expression, 'this')}" 2466 partition = self.sql(expression, "partition") 2467 partition = f" {partition}" if partition else "" 2468 input_format = self.sql(expression, "input_format") 2469 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2470 serde = self.sql(expression, "serde") 2471 serde = f" SERDE {serde}" if serde else "" 2472 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2480 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2481 this = self.sql(expression, "this") 2482 this = f"{this} " if this else this 2483 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2484 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2486 def withfill_sql(self, expression: exp.WithFill) -> str: 2487 from_sql = self.sql(expression, "from") 2488 from_sql = f" FROM {from_sql}" if from_sql else "" 2489 to_sql = self.sql(expression, "to") 2490 to_sql = f" TO {to_sql}" if to_sql else "" 2491 step_sql = self.sql(expression, "step") 2492 step_sql = f" STEP {step_sql}" if step_sql else "" 2493 interpolated_values = [ 2494 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2495 if isinstance(e, exp.Alias) 2496 else self.sql(e, "this") 2497 for e in expression.args.get("interpolate") or [] 2498 ] 2499 interpolate = ( 2500 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2501 ) 2502 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2513 def ordered_sql(self, expression: exp.Ordered) -> str: 2514 desc = expression.args.get("desc") 2515 asc = not desc 2516 2517 nulls_first = expression.args.get("nulls_first") 2518 nulls_last = not nulls_first 2519 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2520 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2521 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2522 2523 this = self.sql(expression, "this") 2524 2525 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2526 nulls_sort_change = "" 2527 if nulls_first and ( 2528 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2529 ): 2530 nulls_sort_change = " NULLS FIRST" 2531 elif ( 2532 nulls_last 2533 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2534 and not nulls_are_last 2535 ): 2536 nulls_sort_change = " NULLS LAST" 2537 2538 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2539 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2540 window = expression.find_ancestor(exp.Window, exp.Select) 2541 if isinstance(window, exp.Window) and window.args.get("spec"): 2542 self.unsupported( 2543 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2544 ) 2545 nulls_sort_change = "" 2546 elif self.NULL_ORDERING_SUPPORTED is False and ( 2547 (asc and nulls_sort_change == " NULLS LAST") 2548 or (desc and nulls_sort_change == " NULLS FIRST") 2549 ): 2550 # BigQuery does not allow these ordering/nulls combinations when used under 2551 # an aggregation func or under a window containing one 2552 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2553 2554 if isinstance(ancestor, exp.Window): 2555 ancestor = ancestor.this 2556 if isinstance(ancestor, exp.AggFunc): 2557 self.unsupported( 2558 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2559 ) 2560 nulls_sort_change = "" 2561 elif self.NULL_ORDERING_SUPPORTED is None: 2562 if expression.this.is_int: 2563 self.unsupported( 2564 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2565 ) 2566 elif not isinstance(expression.this, exp.Rand): 2567 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2568 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2569 nulls_sort_change = "" 2570 2571 with_fill = self.sql(expression, "with_fill") 2572 with_fill = f" {with_fill}" if with_fill else "" 2573 2574 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2584 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2585 partition = self.partition_by_sql(expression) 2586 order = self.sql(expression, "order") 2587 measures = self.expressions(expression, key="measures") 2588 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2589 rows = self.sql(expression, "rows") 2590 rows = self.seg(rows) if rows else "" 2591 after = self.sql(expression, "after") 2592 after = self.seg(after) if after else "" 2593 pattern = self.sql(expression, "pattern") 2594 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2595 definition_sqls = [ 2596 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2597 for definition in expression.args.get("define", []) 2598 ] 2599 definitions = self.expressions(sqls=definition_sqls) 2600 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2601 body = "".join( 2602 ( 2603 partition, 2604 order, 2605 measures, 2606 rows, 2607 after, 2608 pattern, 2609 define, 2610 ) 2611 ) 2612 alias = self.sql(expression, "alias") 2613 alias = f" {alias}" if alias else "" 2614 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2616 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2617 limit = expression.args.get("limit") 2618 2619 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2620 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2621 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2622 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2623 2624 return csv( 2625 *sqls, 2626 *[self.sql(join) for join in expression.args.get("joins") or []], 2627 self.sql(expression, "match"), 2628 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2629 self.sql(expression, "prewhere"), 2630 self.sql(expression, "where"), 2631 self.sql(expression, "connect"), 2632 self.sql(expression, "group"), 2633 self.sql(expression, "having"), 2634 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2635 self.sql(expression, "order"), 2636 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2637 *self.after_limit_modifiers(expression), 2638 self.options_modifier(expression), 2639 self.for_modifiers(expression), 2640 sep="", 2641 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2655 def offset_limit_modifiers( 2656 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2657 ) -> t.List[str]: 2658 return [ 2659 self.sql(expression, "offset") if fetch else self.sql(limit), 2660 self.sql(limit) if fetch else self.sql(expression, "offset"), 2661 ]
2668 def select_sql(self, expression: exp.Select) -> str: 2669 into = expression.args.get("into") 2670 if not self.SUPPORTS_SELECT_INTO and into: 2671 into.pop() 2672 2673 hint = self.sql(expression, "hint") 2674 distinct = self.sql(expression, "distinct") 2675 distinct = f" {distinct}" if distinct else "" 2676 kind = self.sql(expression, "kind") 2677 2678 limit = expression.args.get("limit") 2679 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2680 top = self.limit_sql(limit, top=True) 2681 limit.pop() 2682 else: 2683 top = "" 2684 2685 expressions = self.expressions(expression) 2686 2687 if kind: 2688 if kind in self.SELECT_KINDS: 2689 kind = f" AS {kind}" 2690 else: 2691 if kind == "STRUCT": 2692 expressions = self.expressions( 2693 sqls=[ 2694 self.sql( 2695 exp.Struct( 2696 expressions=[ 2697 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2698 if isinstance(e, exp.Alias) 2699 else e 2700 for e in expression.expressions 2701 ] 2702 ) 2703 ) 2704 ] 2705 ) 2706 kind = "" 2707 2708 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2709 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2710 2711 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2712 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2713 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2714 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2715 sql = self.query_modifiers( 2716 expression, 2717 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2718 self.sql(expression, "into", comment=False), 2719 self.sql(expression, "from", comment=False), 2720 ) 2721 2722 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2723 if expression.args.get("with"): 2724 sql = self.maybe_comment(sql, expression) 2725 expression.pop_comments() 2726 2727 sql = self.prepend_ctes(expression, sql) 2728 2729 if not self.SUPPORTS_SELECT_INTO and into: 2730 if into.args.get("temporary"): 2731 table_kind = " TEMPORARY" 2732 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2733 table_kind = " UNLOGGED" 2734 else: 2735 table_kind = "" 2736 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2737 2738 return sql
2750 def star_sql(self, expression: exp.Star) -> str: 2751 except_ = self.expressions(expression, key="except", flat=True) 2752 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2753 replace = self.expressions(expression, key="replace", flat=True) 2754 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2755 rename = self.expressions(expression, key="rename", flat=True) 2756 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2757 return f"*{except_}{replace}{rename}"
2773 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2774 alias = self.sql(expression, "alias") 2775 alias = f"{sep}{alias}" if alias else "" 2776 sample = self.sql(expression, "sample") 2777 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2778 alias = f"{sample}{alias}" 2779 2780 # Set to None so it's not generated again by self.query_modifiers() 2781 expression.set("sample", None) 2782 2783 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2784 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2785 return self.prepend_ctes(expression, sql)
2791 def unnest_sql(self, expression: exp.Unnest) -> str: 2792 args = self.expressions(expression, flat=True) 2793 2794 alias = expression.args.get("alias") 2795 offset = expression.args.get("offset") 2796 2797 if self.UNNEST_WITH_ORDINALITY: 2798 if alias and isinstance(offset, exp.Expression): 2799 alias.append("columns", offset) 2800 2801 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2802 columns = alias.columns 2803 alias = self.sql(columns[0]) if columns else "" 2804 else: 2805 alias = self.sql(alias) 2806 2807 alias = f" AS {alias}" if alias else alias 2808 if self.UNNEST_WITH_ORDINALITY: 2809 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2810 else: 2811 if isinstance(offset, exp.Expression): 2812 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2813 elif offset: 2814 suffix = f"{alias} WITH OFFSET" 2815 else: 2816 suffix = alias 2817 2818 return f"UNNEST({args}){suffix}"
2827 def window_sql(self, expression: exp.Window) -> str: 2828 this = self.sql(expression, "this") 2829 partition = self.partition_by_sql(expression) 2830 order = expression.args.get("order") 2831 order = self.order_sql(order, flat=True) if order else "" 2832 spec = self.sql(expression, "spec") 2833 alias = self.sql(expression, "alias") 2834 over = self.sql(expression, "over") or "OVER" 2835 2836 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2837 2838 first = expression.args.get("first") 2839 if first is None: 2840 first = "" 2841 else: 2842 first = "FIRST" if first else "LAST" 2843 2844 if not partition and not order and not spec and alias: 2845 return f"{this} {alias}" 2846 2847 args = self.format_args( 2848 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2849 ) 2850 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2856 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2857 kind = self.sql(expression, "kind") 2858 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2859 end = ( 2860 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2861 or "CURRENT ROW" 2862 ) 2863 2864 window_spec = f"{kind} BETWEEN {start} AND {end}" 2865 2866 exclude = self.sql(expression, "exclude") 2867 if exclude: 2868 if self.SUPPORTS_WINDOW_EXCLUDE: 2869 window_spec += f" EXCLUDE {exclude}" 2870 else: 2871 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2872 2873 return window_spec
2880 def between_sql(self, expression: exp.Between) -> str: 2881 this = self.sql(expression, "this") 2882 low = self.sql(expression, "low") 2883 high = self.sql(expression, "high") 2884 symmetric = expression.args.get("symmetric") 2885 2886 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2887 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2888 2889 flag = ( 2890 " SYMMETRIC" 2891 if symmetric 2892 else " ASYMMETRIC" 2893 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2894 else "" # silently drop ASYMMETRIC – semantics identical 2895 ) 2896 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]:
2898 def bracket_offset_expressions( 2899 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2900 ) -> t.List[exp.Expression]: 2901 return apply_index_offset( 2902 expression.this, 2903 expression.expressions, 2904 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2905 dialect=self.dialect, 2906 )
2919 def any_sql(self, expression: exp.Any) -> str: 2920 this = self.sql(expression, "this") 2921 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2922 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2923 this = self.wrap(this) 2924 return f"ANY{this}" 2925 return f"ANY {this}"
2930 def case_sql(self, expression: exp.Case) -> str: 2931 this = self.sql(expression, "this") 2932 statements = [f"CASE {this}" if this else "CASE"] 2933 2934 for e in expression.args["ifs"]: 2935 statements.append(f"WHEN {self.sql(e, 'this')}") 2936 statements.append(f"THEN {self.sql(e, 'true')}") 2937 2938 default = self.sql(expression, "default") 2939 2940 if default: 2941 statements.append(f"ELSE {default}") 2942 2943 statements.append("END") 2944 2945 if self.pretty and self.too_wide(statements): 2946 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2947 2948 return " ".join(statements)
2960 def extract_sql(self, expression: exp.Extract) -> str: 2961 from sqlglot.dialects.dialect import map_date_part 2962 2963 this = ( 2964 map_date_part(expression.this, self.dialect) 2965 if self.NORMALIZE_EXTRACT_DATE_PARTS 2966 else expression.this 2967 ) 2968 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2969 expression_sql = self.sql(expression, "expression") 2970 2971 return f"EXTRACT({this_sql} FROM {expression_sql})"
2973 def trim_sql(self, expression: exp.Trim) -> str: 2974 trim_type = self.sql(expression, "position") 2975 2976 if trim_type == "LEADING": 2977 func_name = "LTRIM" 2978 elif trim_type == "TRAILING": 2979 func_name = "RTRIM" 2980 else: 2981 func_name = "TRIM" 2982 2983 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]:
2985 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2986 args = expression.expressions 2987 if isinstance(expression, exp.ConcatWs): 2988 args = args[1:] # Skip the delimiter 2989 2990 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2991 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2992 2993 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2994 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2995 2996 return args
2998 def concat_sql(self, expression: exp.Concat) -> str: 2999 expressions = self.convert_concat_args(expression) 3000 3001 # Some dialects don't allow a single-argument CONCAT call 3002 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3003 return self.sql(expressions[0]) 3004 3005 return self.func("CONCAT", *expressions)
3016 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3017 expressions = self.expressions(expression, flat=True) 3018 expressions = f" ({expressions})" if expressions else "" 3019 reference = self.sql(expression, "reference") 3020 reference = f" {reference}" if reference else "" 3021 delete = self.sql(expression, "delete") 3022 delete = f" ON DELETE {delete}" if delete else "" 3023 update = self.sql(expression, "update") 3024 update = f" ON UPDATE {update}" if update else "" 3025 options = self.expressions(expression, key="options", flat=True, sep=" ") 3026 options = f" {options}" if options else "" 3027 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3029 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3030 expressions = self.expressions(expression, flat=True) 3031 include = self.sql(expression, "include") 3032 options = self.expressions(expression, key="options", flat=True, sep=" ") 3033 options = f" {options}" if options else "" 3034 return f"PRIMARY KEY ({expressions}){include}{options}"
3047 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3048 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3049 3050 if expression.args.get("escape"): 3051 path = self.escape_str(path) 3052 3053 if self.QUOTE_JSON_PATH: 3054 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3055 3056 return path
3058 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3059 if isinstance(expression, exp.JSONPathPart): 3060 transform = self.TRANSFORMS.get(expression.__class__) 3061 if not callable(transform): 3062 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3063 return "" 3064 3065 return transform(self, expression) 3066 3067 if isinstance(expression, int): 3068 return str(expression) 3069 3070 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3071 escaped = expression.replace("'", "\\'") 3072 escaped = f"\\'{expression}\\'" 3073 else: 3074 escaped = expression.replace('"', '\\"') 3075 escaped = f'"{escaped}"' 3076 3077 return escaped
3082 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3083 # Output the Teradata column FORMAT override. 3084 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3085 this = self.sql(expression, "this") 3086 fmt = self.sql(expression, "format") 3087 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3089 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3090 null_handling = expression.args.get("null_handling") 3091 null_handling = f" {null_handling}" if null_handling else "" 3092 3093 unique_keys = expression.args.get("unique_keys") 3094 if unique_keys is not None: 3095 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3096 else: 3097 unique_keys = "" 3098 3099 return_type = self.sql(expression, "return_type") 3100 return_type = f" RETURNING {return_type}" if return_type else "" 3101 encoding = self.sql(expression, "encoding") 3102 encoding = f" ENCODING {encoding}" if encoding else "" 3103 3104 return self.func( 3105 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3106 *expression.expressions, 3107 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3108 )
3113 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3114 null_handling = expression.args.get("null_handling") 3115 null_handling = f" {null_handling}" if null_handling else "" 3116 return_type = self.sql(expression, "return_type") 3117 return_type = f" RETURNING {return_type}" if return_type else "" 3118 strict = " STRICT" if expression.args.get("strict") else "" 3119 return self.func( 3120 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3121 )
3123 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3124 this = self.sql(expression, "this") 3125 order = self.sql(expression, "order") 3126 null_handling = expression.args.get("null_handling") 3127 null_handling = f" {null_handling}" if null_handling else "" 3128 return_type = self.sql(expression, "return_type") 3129 return_type = f" RETURNING {return_type}" if return_type else "" 3130 strict = " STRICT" if expression.args.get("strict") else "" 3131 return self.func( 3132 "JSON_ARRAYAGG", 3133 this, 3134 suffix=f"{order}{null_handling}{return_type}{strict})", 3135 )
3137 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3138 path = self.sql(expression, "path") 3139 path = f" PATH {path}" if path else "" 3140 nested_schema = self.sql(expression, "nested_schema") 3141 3142 if nested_schema: 3143 return f"NESTED{path} {nested_schema}" 3144 3145 this = self.sql(expression, "this") 3146 kind = self.sql(expression, "kind") 3147 kind = f" {kind}" if kind else "" 3148 return f"{this}{kind}{path}"
3153 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3154 this = self.sql(expression, "this") 3155 path = self.sql(expression, "path") 3156 path = f", {path}" if path else "" 3157 error_handling = expression.args.get("error_handling") 3158 error_handling = f" {error_handling}" if error_handling else "" 3159 empty_handling = expression.args.get("empty_handling") 3160 empty_handling = f" {empty_handling}" if empty_handling else "" 3161 schema = self.sql(expression, "schema") 3162 return self.func( 3163 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3164 )
3166 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3167 this = self.sql(expression, "this") 3168 kind = self.sql(expression, "kind") 3169 path = self.sql(expression, "path") 3170 path = f" {path}" if path else "" 3171 as_json = " AS JSON" if expression.args.get("as_json") else "" 3172 return f"{this} {kind}{path}{as_json}"
3174 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3175 this = self.sql(expression, "this") 3176 path = self.sql(expression, "path") 3177 path = f", {path}" if path else "" 3178 expressions = self.expressions(expression) 3179 with_ = ( 3180 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3181 if expressions 3182 else "" 3183 ) 3184 return f"OPENJSON({this}{path}){with_}"
3186 def in_sql(self, expression: exp.In) -> str: 3187 query = expression.args.get("query") 3188 unnest = expression.args.get("unnest") 3189 field = expression.args.get("field") 3190 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3191 3192 if query: 3193 in_sql = self.sql(query) 3194 elif unnest: 3195 in_sql = self.in_unnest_op(unnest) 3196 elif field: 3197 in_sql = self.sql(field) 3198 else: 3199 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3200 3201 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3206 def interval_sql(self, expression: exp.Interval) -> str: 3207 unit = self.sql(expression, "unit") 3208 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3209 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3210 unit = f" {unit}" if unit else "" 3211 3212 if self.SINGLE_STRING_INTERVAL: 3213 this = expression.this.name if expression.this else "" 3214 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3215 3216 this = self.sql(expression, "this") 3217 if this: 3218 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3219 this = f" {this}" if unwrapped else f" ({this})" 3220 3221 return f"INTERVAL{this}{unit}"
3226 def reference_sql(self, expression: exp.Reference) -> str: 3227 this = self.sql(expression, "this") 3228 expressions = self.expressions(expression, flat=True) 3229 expressions = f"({expressions})" if expressions else "" 3230 options = self.expressions(expression, key="options", flat=True, sep=" ") 3231 options = f" {options}" if options else "" 3232 return f"REFERENCES {this}{expressions}{options}"
3234 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3235 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3236 parent = expression.parent 3237 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3238 return self.func( 3239 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3240 )
3260 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3261 alias = expression.args["alias"] 3262 3263 parent = expression.parent 3264 pivot = parent and parent.parent 3265 3266 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3267 identifier_alias = isinstance(alias, exp.Identifier) 3268 literal_alias = isinstance(alias, exp.Literal) 3269 3270 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3271 alias.replace(exp.Literal.string(alias.output_name)) 3272 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3273 alias.replace(exp.to_identifier(alias.output_name)) 3274 3275 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:
3313 def connector_sql( 3314 self, 3315 expression: exp.Connector, 3316 op: str, 3317 stack: t.Optional[t.List[str | exp.Expression]] = None, 3318 ) -> str: 3319 if stack is not None: 3320 if expression.expressions: 3321 stack.append(self.expressions(expression, sep=f" {op} ")) 3322 else: 3323 stack.append(expression.right) 3324 if expression.comments and self.comments: 3325 for comment in expression.comments: 3326 if comment: 3327 op += f" /*{self.sanitize_comment(comment)}*/" 3328 stack.extend((op, expression.left)) 3329 return op 3330 3331 stack = [expression] 3332 sqls: t.List[str] = [] 3333 ops = set() 3334 3335 while stack: 3336 node = stack.pop() 3337 if isinstance(node, exp.Connector): 3338 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3339 else: 3340 sql = self.sql(node) 3341 if sqls and sqls[-1] in ops: 3342 sqls[-1] += f" {sql}" 3343 else: 3344 sqls.append(sql) 3345 3346 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3347 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3367 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3368 format_sql = self.sql(expression, "format") 3369 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3370 to_sql = self.sql(expression, "to") 3371 to_sql = f" {to_sql}" if to_sql else "" 3372 action = self.sql(expression, "action") 3373 action = f" {action}" if action else "" 3374 default = self.sql(expression, "default") 3375 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3376 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3390 def comment_sql(self, expression: exp.Comment) -> str: 3391 this = self.sql(expression, "this") 3392 kind = expression.args["kind"] 3393 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3394 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3395 expression_sql = self.sql(expression, "expression") 3396 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3398 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3399 this = self.sql(expression, "this") 3400 delete = " DELETE" if expression.args.get("delete") else "" 3401 recompress = self.sql(expression, "recompress") 3402 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3403 to_disk = self.sql(expression, "to_disk") 3404 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3405 to_volume = self.sql(expression, "to_volume") 3406 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3407 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3409 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3410 where = self.sql(expression, "where") 3411 group = self.sql(expression, "group") 3412 aggregates = self.expressions(expression, key="aggregates") 3413 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3414 3415 if not (where or group or aggregates) and len(expression.expressions) == 1: 3416 return f"TTL {self.expressions(expression, flat=True)}" 3417 3418 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3435 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3436 this = self.sql(expression, "this") 3437 3438 dtype = self.sql(expression, "dtype") 3439 if dtype: 3440 collate = self.sql(expression, "collate") 3441 collate = f" COLLATE {collate}" if collate else "" 3442 using = self.sql(expression, "using") 3443 using = f" USING {using}" if using else "" 3444 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3445 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3446 3447 default = self.sql(expression, "default") 3448 if default: 3449 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3450 3451 comment = self.sql(expression, "comment") 3452 if comment: 3453 return f"ALTER COLUMN {this} COMMENT {comment}" 3454 3455 visible = expression.args.get("visible") 3456 if visible: 3457 return f"ALTER COLUMN {this} SET {visible}" 3458 3459 allow_null = expression.args.get("allow_null") 3460 drop = expression.args.get("drop") 3461 3462 if not drop and not allow_null: 3463 self.unsupported("Unsupported ALTER COLUMN syntax") 3464 3465 if allow_null is not None: 3466 keyword = "DROP" if drop else "SET" 3467 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3468 3469 return f"ALTER COLUMN {this} DROP DEFAULT"
3485 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3486 compound = " COMPOUND" if expression.args.get("compound") else "" 3487 this = self.sql(expression, "this") 3488 expressions = self.expressions(expression, flat=True) 3489 expressions = f"({expressions})" if expressions else "" 3490 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3492 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3493 if not self.RENAME_TABLE_WITH_DB: 3494 # Remove db from tables 3495 expression = expression.transform( 3496 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3497 ).assert_is(exp.AlterRename) 3498 this = self.sql(expression, "this") 3499 to_kw = " TO" if include_to else "" 3500 return f"RENAME{to_kw} {this}"
3515 def alter_sql(self, expression: exp.Alter) -> str: 3516 actions = expression.args["actions"] 3517 3518 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3519 actions[0], exp.ColumnDef 3520 ): 3521 actions_sql = self.expressions(expression, key="actions", flat=True) 3522 actions_sql = f"ADD {actions_sql}" 3523 else: 3524 actions_list = [] 3525 for action in actions: 3526 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3527 action_sql = self.add_column_sql(action) 3528 else: 3529 action_sql = self.sql(action) 3530 if isinstance(action, exp.Query): 3531 action_sql = f"AS {action_sql}" 3532 3533 actions_list.append(action_sql) 3534 3535 actions_sql = self.format_args(*actions_list).lstrip("\n") 3536 3537 exists = " IF EXISTS" if expression.args.get("exists") else "" 3538 on_cluster = self.sql(expression, "cluster") 3539 on_cluster = f" {on_cluster}" if on_cluster else "" 3540 only = " ONLY" if expression.args.get("only") else "" 3541 options = self.expressions(expression, key="options") 3542 options = f", {options}" if options else "" 3543 kind = self.sql(expression, "kind") 3544 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3545 3546 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3548 def add_column_sql(self, expression: exp.Expression) -> str: 3549 sql = self.sql(expression) 3550 if isinstance(expression, exp.Schema): 3551 column_text = " COLUMNS" 3552 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3553 column_text = " COLUMN" 3554 else: 3555 column_text = "" 3556 3557 return f"ADD{column_text} {sql}"
3567 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3568 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3569 location = self.sql(expression, "location") 3570 location = f" {location}" if location else "" 3571 return f"ADD {exists}{self.sql(expression.this)}{location}"
3573 def distinct_sql(self, expression: exp.Distinct) -> str: 3574 this = self.expressions(expression, flat=True) 3575 3576 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3577 case = exp.case() 3578 for arg in expression.expressions: 3579 case = case.when(arg.is_(exp.null()), exp.null()) 3580 this = self.sql(case.else_(f"({this})")) 3581 3582 this = f" {this}" if this else "" 3583 3584 on = self.sql(expression, "on") 3585 on = f" ON {on}" if on else "" 3586 return f"DISTINCT{this}{on}"
3615 def div_sql(self, expression: exp.Div) -> str: 3616 l, r = expression.left, expression.right 3617 3618 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3619 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3620 3621 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3622 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3623 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3624 3625 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3626 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3627 return self.sql( 3628 exp.cast( 3629 l / r, 3630 to=exp.DataType.Type.BIGINT, 3631 ) 3632 ) 3633 3634 return self.binary(expression, "/")
3751 def log_sql(self, expression: exp.Log) -> str: 3752 this = expression.this 3753 expr = expression.expression 3754 3755 if self.dialect.LOG_BASE_FIRST is False: 3756 this, expr = expr, this 3757 elif self.dialect.LOG_BASE_FIRST is None and expr: 3758 if this.name in ("2", "10"): 3759 return self.func(f"LOG{this.name}", expr) 3760 3761 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3762 3763 return self.func("LOG", this, expr)
3772 def binary(self, expression: exp.Binary, op: str) -> str: 3773 sqls: t.List[str] = [] 3774 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3775 binary_type = type(expression) 3776 3777 while stack: 3778 node = stack.pop() 3779 3780 if type(node) is binary_type: 3781 op_func = node.args.get("operator") 3782 if op_func: 3783 op = f"OPERATOR({self.sql(op_func)})" 3784 3785 stack.append(node.right) 3786 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3787 stack.append(node.left) 3788 else: 3789 sqls.append(self.sql(node)) 3790 3791 return "".join(sqls)
3800 def function_fallback_sql(self, expression: exp.Func) -> str: 3801 args = [] 3802 3803 for key in expression.arg_types: 3804 arg_value = expression.args.get(key) 3805 3806 if isinstance(arg_value, list): 3807 for value in arg_value: 3808 args.append(value) 3809 elif arg_value is not None: 3810 args.append(arg_value) 3811 3812 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3813 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3814 else: 3815 name = expression.sql_name() 3816 3817 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:
3819 def func( 3820 self, 3821 name: str, 3822 *args: t.Optional[exp.Expression | str], 3823 prefix: str = "(", 3824 suffix: str = ")", 3825 normalize: bool = True, 3826 ) -> str: 3827 name = self.normalize_func(name) if normalize else name 3828 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3830 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3831 arg_sqls = tuple( 3832 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3833 ) 3834 if self.pretty and self.too_wide(arg_sqls): 3835 return self.indent( 3836 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3837 ) 3838 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]:
3843 def format_time( 3844 self, 3845 expression: exp.Expression, 3846 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3847 inverse_time_trie: t.Optional[t.Dict] = None, 3848 ) -> t.Optional[str]: 3849 return format_time( 3850 self.sql(expression, "format"), 3851 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3852 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3853 )
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:
3855 def expressions( 3856 self, 3857 expression: t.Optional[exp.Expression] = None, 3858 key: t.Optional[str] = None, 3859 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3860 flat: bool = False, 3861 indent: bool = True, 3862 skip_first: bool = False, 3863 skip_last: bool = False, 3864 sep: str = ", ", 3865 prefix: str = "", 3866 dynamic: bool = False, 3867 new_line: bool = False, 3868 ) -> str: 3869 expressions = expression.args.get(key or "expressions") if expression else sqls 3870 3871 if not expressions: 3872 return "" 3873 3874 if flat: 3875 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3876 3877 num_sqls = len(expressions) 3878 result_sqls = [] 3879 3880 for i, e in enumerate(expressions): 3881 sql = self.sql(e, comment=False) 3882 if not sql: 3883 continue 3884 3885 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3886 3887 if self.pretty: 3888 if self.leading_comma: 3889 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3890 else: 3891 result_sqls.append( 3892 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3893 ) 3894 else: 3895 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3896 3897 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3898 if new_line: 3899 result_sqls.insert(0, "") 3900 result_sqls.append("") 3901 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3902 else: 3903 result_sql = "".join(result_sqls) 3904 3905 return ( 3906 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3907 if indent 3908 else result_sql 3909 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3911 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3912 flat = flat or isinstance(expression.parent, exp.Properties) 3913 expressions_sql = self.expressions(expression, flat=flat) 3914 if flat: 3915 return f"{op} {expressions_sql}" 3916 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3918 def naked_property(self, expression: exp.Property) -> str: 3919 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3920 if not property_name: 3921 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3922 return f"{property_name} {self.sql(expression, 'this')}"
3930 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3931 this = self.sql(expression, "this") 3932 expressions = self.no_identify(self.expressions, expression) 3933 expressions = ( 3934 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3935 ) 3936 return f"{this}{expressions}" if expressions.strip() != "" else this
3946 def when_sql(self, expression: exp.When) -> str: 3947 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3948 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3949 condition = self.sql(expression, "condition") 3950 condition = f" AND {condition}" if condition else "" 3951 3952 then_expression = expression.args.get("then") 3953 if isinstance(then_expression, exp.Insert): 3954 this = self.sql(then_expression, "this") 3955 this = f"INSERT {this}" if this else "INSERT" 3956 then = self.sql(then_expression, "expression") 3957 then = f"{this} VALUES {then}" if then else this 3958 elif isinstance(then_expression, exp.Update): 3959 if isinstance(then_expression.args.get("expressions"), exp.Star): 3960 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3961 else: 3962 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3963 else: 3964 then = self.sql(then_expression) 3965 return f"WHEN {matched}{source}{condition} THEN {then}"
3970 def merge_sql(self, expression: exp.Merge) -> str: 3971 table = expression.this 3972 table_alias = "" 3973 3974 hints = table.args.get("hints") 3975 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3976 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3977 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3978 3979 this = self.sql(table) 3980 using = f"USING {self.sql(expression, 'using')}" 3981 on = f"ON {self.sql(expression, 'on')}" 3982 whens = self.sql(expression, "whens") 3983 3984 returning = self.sql(expression, "returning") 3985 if returning: 3986 whens = f"{whens}{returning}" 3987 3988 sep = self.sep() 3989 3990 return self.prepend_ctes( 3991 expression, 3992 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3993 )
3999 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4000 if not self.SUPPORTS_TO_NUMBER: 4001 self.unsupported("Unsupported TO_NUMBER function") 4002 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4003 4004 fmt = expression.args.get("format") 4005 if not fmt: 4006 self.unsupported("Conversion format is required for TO_NUMBER") 4007 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4008 4009 return self.func("TO_NUMBER", expression.this, fmt)
4011 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4012 this = self.sql(expression, "this") 4013 kind = self.sql(expression, "kind") 4014 settings_sql = self.expressions(expression, key="settings", sep=" ") 4015 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4016 return f"{this}({kind}{args})"
4035 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4036 expressions = self.expressions(expression, flat=True) 4037 expressions = f" {self.wrap(expressions)}" if expressions else "" 4038 buckets = self.sql(expression, "buckets") 4039 kind = self.sql(expression, "kind") 4040 buckets = f" BUCKETS {buckets}" if buckets else "" 4041 order = self.sql(expression, "order") 4042 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4047 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4048 expressions = self.expressions(expression, key="expressions", flat=True) 4049 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4050 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4051 buckets = self.sql(expression, "buckets") 4052 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4054 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4055 this = self.sql(expression, "this") 4056 having = self.sql(expression, "having") 4057 4058 if having: 4059 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4060 4061 return self.func("ANY_VALUE", this)
4063 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4064 transform = self.func("TRANSFORM", *expression.expressions) 4065 row_format_before = self.sql(expression, "row_format_before") 4066 row_format_before = f" {row_format_before}" if row_format_before else "" 4067 record_writer = self.sql(expression, "record_writer") 4068 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4069 using = f" USING {self.sql(expression, 'command_script')}" 4070 schema = self.sql(expression, "schema") 4071 schema = f" AS {schema}" if schema else "" 4072 row_format_after = self.sql(expression, "row_format_after") 4073 row_format_after = f" {row_format_after}" if row_format_after else "" 4074 record_reader = self.sql(expression, "record_reader") 4075 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4076 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4078 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4079 key_block_size = self.sql(expression, "key_block_size") 4080 if key_block_size: 4081 return f"KEY_BLOCK_SIZE = {key_block_size}" 4082 4083 using = self.sql(expression, "using") 4084 if using: 4085 return f"USING {using}" 4086 4087 parser = self.sql(expression, "parser") 4088 if parser: 4089 return f"WITH PARSER {parser}" 4090 4091 comment = self.sql(expression, "comment") 4092 if comment: 4093 return f"COMMENT {comment}" 4094 4095 visible = expression.args.get("visible") 4096 if visible is not None: 4097 return "VISIBLE" if visible else "INVISIBLE" 4098 4099 engine_attr = self.sql(expression, "engine_attr") 4100 if engine_attr: 4101 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4102 4103 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4104 if secondary_engine_attr: 4105 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4106 4107 self.unsupported("Unsupported index constraint option.") 4108 return ""
4114 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4115 kind = self.sql(expression, "kind") 4116 kind = f"{kind} INDEX" if kind else "INDEX" 4117 this = self.sql(expression, "this") 4118 this = f" {this}" if this else "" 4119 index_type = self.sql(expression, "index_type") 4120 index_type = f" USING {index_type}" if index_type else "" 4121 expressions = self.expressions(expression, flat=True) 4122 expressions = f" ({expressions})" if expressions else "" 4123 options = self.expressions(expression, key="options", sep=" ") 4124 options = f" {options}" if options else "" 4125 return f"{kind}{this}{index_type}{expressions}{options}"
4127 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4128 if self.NVL2_SUPPORTED: 4129 return self.function_fallback_sql(expression) 4130 4131 case = exp.Case().when( 4132 expression.this.is_(exp.null()).not_(copy=False), 4133 expression.args["true"], 4134 copy=False, 4135 ) 4136 else_cond = expression.args.get("false") 4137 if else_cond: 4138 case.else_(else_cond, copy=False) 4139 4140 return self.sql(case)
4142 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4143 this = self.sql(expression, "this") 4144 expr = self.sql(expression, "expression") 4145 iterator = self.sql(expression, "iterator") 4146 condition = self.sql(expression, "condition") 4147 condition = f" IF {condition}" if condition else "" 4148 return f"{this} FOR {expr} IN {iterator}{condition}"
4156 def predict_sql(self, expression: exp.Predict) -> str: 4157 model = self.sql(expression, "this") 4158 model = f"MODEL {model}" 4159 table = self.sql(expression, "expression") 4160 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4161 parameters = self.sql(expression, "params_struct") 4162 return self.func("PREDICT", model, table, parameters or None)
4174 def toarray_sql(self, expression: exp.ToArray) -> str: 4175 arg = expression.this 4176 if not arg.type: 4177 from sqlglot.optimizer.annotate_types import annotate_types 4178 4179 arg = annotate_types(arg, dialect=self.dialect) 4180 4181 if arg.is_type(exp.DataType.Type.ARRAY): 4182 return self.sql(arg) 4183 4184 cond_for_null = arg.is_(exp.null()) 4185 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4187 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4188 this = expression.this 4189 time_format = self.format_time(expression) 4190 4191 if time_format: 4192 return self.sql( 4193 exp.cast( 4194 exp.StrToTime(this=this, format=expression.args["format"]), 4195 exp.DataType.Type.TIME, 4196 ) 4197 ) 4198 4199 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4200 return self.sql(this) 4201 4202 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4204 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4205 this = expression.this 4206 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4207 return self.sql(this) 4208 4209 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4211 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4212 this = expression.this 4213 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4214 return self.sql(this) 4215 4216 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4218 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4219 this = expression.this 4220 time_format = self.format_time(expression) 4221 4222 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4223 return self.sql( 4224 exp.cast( 4225 exp.StrToTime(this=this, format=expression.args["format"]), 4226 exp.DataType.Type.DATE, 4227 ) 4228 ) 4229 4230 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4231 return self.sql(this) 4232 4233 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4245 def lastday_sql(self, expression: exp.LastDay) -> str: 4246 if self.LAST_DAY_SUPPORTS_DATE_PART: 4247 return self.function_fallback_sql(expression) 4248 4249 unit = expression.text("unit") 4250 if unit and unit != "MONTH": 4251 self.unsupported("Date parts are not supported in LAST_DAY.") 4252 4253 return self.func("LAST_DAY", expression.this)
4262 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4263 if self.CAN_IMPLEMENT_ARRAY_ANY: 4264 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4265 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4266 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4267 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4268 4269 from sqlglot.dialects import Dialect 4270 4271 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4272 if self.dialect.__class__ != Dialect: 4273 self.unsupported("ARRAY_ANY is unsupported") 4274 4275 return self.function_fallback_sql(expression)
4277 def struct_sql(self, expression: exp.Struct) -> str: 4278 expression.set( 4279 "expressions", 4280 [ 4281 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4282 if isinstance(e, exp.PropertyEQ) 4283 else e 4284 for e in expression.expressions 4285 ], 4286 ) 4287 4288 return self.function_fallback_sql(expression)
4296 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4297 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4298 tables = f" {self.expressions(expression)}" 4299 4300 exists = " IF EXISTS" if expression.args.get("exists") else "" 4301 4302 on_cluster = self.sql(expression, "cluster") 4303 on_cluster = f" {on_cluster}" if on_cluster else "" 4304 4305 identity = self.sql(expression, "identity") 4306 identity = f" {identity} IDENTITY" if identity else "" 4307 4308 option = self.sql(expression, "option") 4309 option = f" {option}" if option else "" 4310 4311 partition = self.sql(expression, "partition") 4312 partition = f" {partition}" if partition else "" 4313 4314 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4318 def convert_sql(self, expression: exp.Convert) -> str: 4319 to = expression.this 4320 value = expression.expression 4321 style = expression.args.get("style") 4322 safe = expression.args.get("safe") 4323 strict = expression.args.get("strict") 4324 4325 if not to or not value: 4326 return "" 4327 4328 # Retrieve length of datatype and override to default if not specified 4329 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4330 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4331 4332 transformed: t.Optional[exp.Expression] = None 4333 cast = exp.Cast if strict else exp.TryCast 4334 4335 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4336 if isinstance(style, exp.Literal) and style.is_int: 4337 from sqlglot.dialects.tsql import TSQL 4338 4339 style_value = style.name 4340 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4341 if not converted_style: 4342 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4343 4344 fmt = exp.Literal.string(converted_style) 4345 4346 if to.this == exp.DataType.Type.DATE: 4347 transformed = exp.StrToDate(this=value, format=fmt) 4348 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4349 transformed = exp.StrToTime(this=value, format=fmt) 4350 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4351 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4352 elif to.this == exp.DataType.Type.TEXT: 4353 transformed = exp.TimeToStr(this=value, format=fmt) 4354 4355 if not transformed: 4356 transformed = cast(this=value, to=to, safe=safe) 4357 4358 return self.sql(transformed)
4426 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4427 option = self.sql(expression, "this") 4428 4429 if expression.expressions: 4430 upper = option.upper() 4431 4432 # Snowflake FILE_FORMAT options are separated by whitespace 4433 sep = " " if upper == "FILE_FORMAT" else ", " 4434 4435 # Databricks copy/format options do not set their list of values with EQ 4436 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4437 values = self.expressions(expression, flat=True, sep=sep) 4438 return f"{option}{op}({values})" 4439 4440 value = self.sql(expression, "expression") 4441 4442 if not value: 4443 return option 4444 4445 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4446 4447 return f"{option}{op}{value}"
4449 def credentials_sql(self, expression: exp.Credentials) -> str: 4450 cred_expr = expression.args.get("credentials") 4451 if isinstance(cred_expr, exp.Literal): 4452 # Redshift case: CREDENTIALS <string> 4453 credentials = self.sql(expression, "credentials") 4454 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4455 else: 4456 # Snowflake case: CREDENTIALS = (...) 4457 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4458 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4459 4460 storage = self.sql(expression, "storage") 4461 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4462 4463 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4464 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4465 4466 iam_role = self.sql(expression, "iam_role") 4467 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4468 4469 region = self.sql(expression, "region") 4470 region = f" REGION {region}" if region else "" 4471 4472 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4474 def copy_sql(self, expression: exp.Copy) -> str: 4475 this = self.sql(expression, "this") 4476 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4477 4478 credentials = self.sql(expression, "credentials") 4479 credentials = self.seg(credentials) if credentials else "" 4480 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4481 files = self.expressions(expression, key="files", flat=True) 4482 4483 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4484 params = self.expressions( 4485 expression, 4486 key="params", 4487 sep=sep, 4488 new_line=True, 4489 skip_last=True, 4490 skip_first=True, 4491 indent=self.COPY_PARAMS_ARE_WRAPPED, 4492 ) 4493 4494 if params: 4495 if self.COPY_PARAMS_ARE_WRAPPED: 4496 params = f" WITH ({params})" 4497 elif not self.pretty: 4498 params = f" {params}" 4499 4500 return f"COPY{this}{kind} {files}{credentials}{params}"
4505 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4506 on_sql = "ON" if expression.args.get("on") else "OFF" 4507 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4508 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4509 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4510 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4511 4512 if filter_col or retention_period: 4513 on_sql = self.func("ON", filter_col, retention_period) 4514 4515 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4517 def maskingpolicycolumnconstraint_sql( 4518 self, expression: exp.MaskingPolicyColumnConstraint 4519 ) -> str: 4520 this = self.sql(expression, "this") 4521 expressions = self.expressions(expression, flat=True) 4522 expressions = f" USING ({expressions})" if expressions else "" 4523 return f"MASKING POLICY {this}{expressions}"
4533 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4534 this = self.sql(expression, "this") 4535 expr = expression.expression 4536 4537 if isinstance(expr, exp.Func): 4538 # T-SQL's CLR functions are case sensitive 4539 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4540 else: 4541 expr = self.sql(expression, "expression") 4542 4543 return self.scope_resolution(expr, this)
4551 def rand_sql(self, expression: exp.Rand) -> str: 4552 lower = self.sql(expression, "lower") 4553 upper = self.sql(expression, "upper") 4554 4555 if lower and upper: 4556 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4557 return self.func("RAND", expression.this)
4559 def changes_sql(self, expression: exp.Changes) -> str: 4560 information = self.sql(expression, "information") 4561 information = f"INFORMATION => {information}" 4562 at_before = self.sql(expression, "at_before") 4563 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4564 end = self.sql(expression, "end") 4565 end = f"{self.seg('')}{end}" if end else "" 4566 4567 return f"CHANGES ({information}){at_before}{end}"
4569 def pad_sql(self, expression: exp.Pad) -> str: 4570 prefix = "L" if expression.args.get("is_left") else "R" 4571 4572 fill_pattern = self.sql(expression, "fill_pattern") or None 4573 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4574 fill_pattern = "' '" 4575 4576 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4582 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4583 generate_series = exp.GenerateSeries(**expression.args) 4584 4585 parent = expression.parent 4586 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4587 parent = parent.parent 4588 4589 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4590 return self.sql(exp.Unnest(expressions=[generate_series])) 4591 4592 if isinstance(parent, exp.Select): 4593 self.unsupported("GenerateSeries projection unnesting is not supported.") 4594 4595 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4597 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4598 exprs = expression.expressions 4599 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4600 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4601 else: 4602 rhs = self.expressions(expression) 4603 4604 return self.func(name, expression.this, rhs or None)
4606 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4607 if self.SUPPORTS_CONVERT_TIMEZONE: 4608 return self.function_fallback_sql(expression) 4609 4610 source_tz = expression.args.get("source_tz") 4611 target_tz = expression.args.get("target_tz") 4612 timestamp = expression.args.get("timestamp") 4613 4614 if source_tz and timestamp: 4615 timestamp = exp.AtTimeZone( 4616 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4617 ) 4618 4619 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4620 4621 return self.sql(expr)
4623 def json_sql(self, expression: exp.JSON) -> str: 4624 this = self.sql(expression, "this") 4625 this = f" {this}" if this else "" 4626 4627 _with = expression.args.get("with") 4628 4629 if _with is None: 4630 with_sql = "" 4631 elif not _with: 4632 with_sql = " WITHOUT" 4633 else: 4634 with_sql = " WITH" 4635 4636 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4637 4638 return f"JSON{this}{with_sql}{unique_sql}"
4640 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4641 def _generate_on_options(arg: t.Any) -> str: 4642 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4643 4644 path = self.sql(expression, "path") 4645 returning = self.sql(expression, "returning") 4646 returning = f" RETURNING {returning}" if returning else "" 4647 4648 on_condition = self.sql(expression, "on_condition") 4649 on_condition = f" {on_condition}" if on_condition else "" 4650 4651 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4653 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4654 else_ = "ELSE " if expression.args.get("else_") else "" 4655 condition = self.sql(expression, "expression") 4656 condition = f"WHEN {condition} THEN " if condition else else_ 4657 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4658 return f"{condition}{insert}"
4666 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4667 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4668 empty = expression.args.get("empty") 4669 empty = ( 4670 f"DEFAULT {empty} ON EMPTY" 4671 if isinstance(empty, exp.Expression) 4672 else self.sql(expression, "empty") 4673 ) 4674 4675 error = expression.args.get("error") 4676 error = ( 4677 f"DEFAULT {error} ON ERROR" 4678 if isinstance(error, exp.Expression) 4679 else self.sql(expression, "error") 4680 ) 4681 4682 if error and empty: 4683 error = ( 4684 f"{empty} {error}" 4685 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4686 else f"{error} {empty}" 4687 ) 4688 empty = "" 4689 4690 null = self.sql(expression, "null") 4691 4692 return f"{empty}{error}{null}"
4698 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4699 this = self.sql(expression, "this") 4700 path = self.sql(expression, "path") 4701 4702 passing = self.expressions(expression, "passing") 4703 passing = f" PASSING {passing}" if passing else "" 4704 4705 on_condition = self.sql(expression, "on_condition") 4706 on_condition = f" {on_condition}" if on_condition else "" 4707 4708 path = f"{path}{passing}{on_condition}" 4709 4710 return self.func("JSON_EXISTS", this, path)
4712 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4713 array_agg = self.function_fallback_sql(expression) 4714 4715 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4716 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4717 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4718 parent = expression.parent 4719 if isinstance(parent, exp.Filter): 4720 parent_cond = parent.expression.this 4721 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4722 else: 4723 this = expression.this 4724 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4725 if this.find(exp.Column): 4726 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4727 this_sql = ( 4728 self.expressions(this) 4729 if isinstance(this, exp.Distinct) 4730 else self.sql(expression, "this") 4731 ) 4732 4733 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4734 4735 return array_agg
4743 def grant_sql(self, expression: exp.Grant) -> str: 4744 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4745 4746 kind = self.sql(expression, "kind") 4747 kind = f" {kind}" if kind else "" 4748 4749 securable = self.sql(expression, "securable") 4750 securable = f" {securable}" if securable else "" 4751 4752 principals = self.expressions(expression, key="principals", flat=True) 4753 4754 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4755 4756 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4780 def overlay_sql(self, expression: exp.Overlay): 4781 this = self.sql(expression, "this") 4782 expr = self.sql(expression, "expression") 4783 from_sql = self.sql(expression, "from") 4784 for_sql = self.sql(expression, "for") 4785 for_sql = f" FOR {for_sql}" if for_sql else "" 4786 4787 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4793 def string_sql(self, expression: exp.String) -> str: 4794 this = expression.this 4795 zone = expression.args.get("zone") 4796 4797 if zone: 4798 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4799 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4800 # set for source_tz to transpile the time conversion before the STRING cast 4801 this = exp.ConvertTimezone( 4802 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4803 ) 4804 4805 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4815 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4816 filler = self.sql(expression, "this") 4817 filler = f" {filler}" if filler else "" 4818 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4819 return f"TRUNCATE{filler} {with_count}"
4821 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4822 if self.SUPPORTS_UNIX_SECONDS: 4823 return self.function_fallback_sql(expression) 4824 4825 start_ts = exp.cast( 4826 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4827 ) 4828 4829 return self.sql( 4830 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4831 )
4833 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4834 dim = expression.expression 4835 4836 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4837 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4838 if not (dim.is_int and dim.name == "1"): 4839 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4840 dim = None 4841 4842 # If dimension is required but not specified, default initialize it 4843 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4844 dim = exp.Literal.number(1) 4845 4846 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4848 def attach_sql(self, expression: exp.Attach) -> str: 4849 this = self.sql(expression, "this") 4850 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4851 expressions = self.expressions(expression) 4852 expressions = f" ({expressions})" if expressions else "" 4853 4854 return f"ATTACH{exists_sql} {this}{expressions}"
4856 def detach_sql(self, expression: exp.Detach) -> str: 4857 this = self.sql(expression, "this") 4858 # the DATABASE keyword is required if IF EXISTS is set 4859 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4860 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4861 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4862 4863 return f"DETACH{exists_sql} {this}"
4871 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4872 this_sql = self.sql(expression, "this") 4873 if isinstance(expression.this, exp.Table): 4874 this_sql = f"TABLE {this_sql}" 4875 4876 return self.func( 4877 "FEATURES_AT_TIME", 4878 this_sql, 4879 expression.args.get("time"), 4880 expression.args.get("num_rows"), 4881 expression.args.get("ignore_feature_nulls"), 4882 )
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4889 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4890 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4891 encode = f"{encode} {self.sql(expression, 'this')}" 4892 4893 properties = expression.args.get("properties") 4894 if properties: 4895 encode = f"{encode} {self.properties(properties)}" 4896 4897 return encode
4899 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4900 this = self.sql(expression, "this") 4901 include = f"INCLUDE {this}" 4902 4903 column_def = self.sql(expression, "column_def") 4904 if column_def: 4905 include = f"{include} {column_def}" 4906 4907 alias = self.sql(expression, "alias") 4908 if alias: 4909 include = f"{include} AS {alias}" 4910 4911 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
4923 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4924 partitions = self.expressions(expression, "partition_expressions") 4925 create = self.expressions(expression, "create_expressions") 4926 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
4928 def partitionbyrangepropertydynamic_sql( 4929 self, expression: exp.PartitionByRangePropertyDynamic 4930 ) -> str: 4931 start = self.sql(expression, "start") 4932 end = self.sql(expression, "end") 4933 4934 every = expression.args["every"] 4935 if isinstance(every, exp.Interval) and every.this.is_string: 4936 every.this.replace(exp.Literal.number(every.name)) 4937 4938 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4951 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4952 kind = self.sql(expression, "kind") 4953 option = self.sql(expression, "option") 4954 option = f" {option}" if option else "" 4955 this = self.sql(expression, "this") 4956 this = f" {this}" if this else "" 4957 columns = self.expressions(expression) 4958 columns = f" {columns}" if columns else "" 4959 return f"{kind}{option} STATISTICS{this}{columns}"
4961 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4962 this = self.sql(expression, "this") 4963 columns = self.expressions(expression) 4964 inner_expression = self.sql(expression, "expression") 4965 inner_expression = f" {inner_expression}" if inner_expression else "" 4966 update_options = self.sql(expression, "update_options") 4967 update_options = f" {update_options} UPDATE" if update_options else "" 4968 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
4979 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4980 kind = self.sql(expression, "kind") 4981 this = self.sql(expression, "this") 4982 this = f" {this}" if this else "" 4983 inner_expression = self.sql(expression, "expression") 4984 return f"VALIDATE {kind}{this}{inner_expression}"
4986 def analyze_sql(self, expression: exp.Analyze) -> str: 4987 options = self.expressions(expression, key="options", sep=" ") 4988 options = f" {options}" if options else "" 4989 kind = self.sql(expression, "kind") 4990 kind = f" {kind}" if kind else "" 4991 this = self.sql(expression, "this") 4992 this = f" {this}" if this else "" 4993 mode = self.sql(expression, "mode") 4994 mode = f" {mode}" if mode else "" 4995 properties = self.sql(expression, "properties") 4996 properties = f" {properties}" if properties else "" 4997 partition = self.sql(expression, "partition") 4998 partition = f" {partition}" if partition else "" 4999 inner_expression = self.sql(expression, "expression") 5000 inner_expression = f" {inner_expression}" if inner_expression else "" 5001 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5003 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5004 this = self.sql(expression, "this") 5005 namespaces = self.expressions(expression, key="namespaces") 5006 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5007 passing = self.expressions(expression, key="passing") 5008 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5009 columns = self.expressions(expression, key="columns") 5010 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5011 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5012 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5018 def export_sql(self, expression: exp.Export) -> str: 5019 this = self.sql(expression, "this") 5020 connection = self.sql(expression, "connection") 5021 connection = f"WITH CONNECTION {connection} " if connection else "" 5022 options = self.sql(expression, "options") 5023 return f"EXPORT DATA {connection}{options} AS {this}"
5028 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5029 variable = self.sql(expression, "this") 5030 default = self.sql(expression, "default") 5031 default = f" = {default}" if default else "" 5032 5033 kind = self.sql(expression, "kind") 5034 if isinstance(expression.args.get("kind"), exp.Schema): 5035 kind = f"TABLE {kind}" 5036 5037 return f"{variable} AS {kind}{default}"
5039 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5040 kind = self.sql(expression, "kind") 5041 this = self.sql(expression, "this") 5042 set = self.sql(expression, "expression") 5043 using = self.sql(expression, "using") 5044 using = f" USING {using}" if using else "" 5045 5046 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5047 5048 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5067 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5068 # Snowflake GET/PUT statements: 5069 # PUT <file> <internalStage> <properties> 5070 # GET <internalStage> <file> <properties> 5071 props = expression.args.get("properties") 5072 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5073 this = self.sql(expression, "this") 5074 target = self.sql(expression, "target") 5075 5076 if isinstance(expression, exp.Put): 5077 return f"PUT {this} {target}{props_sql}" 5078 else: 5079 return f"GET {target} {this}{props_sql}"
5087 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5088 if self.SUPPORTS_DECODE_CASE: 5089 return self.func("DECODE", *expression.expressions) 5090 5091 expression, *expressions = expression.expressions 5092 5093 ifs = [] 5094 for search, result in zip(expressions[::2], expressions[1::2]): 5095 if isinstance(search, exp.Literal): 5096 ifs.append(exp.If(this=expression.eq(search), true=result)) 5097 elif isinstance(search, exp.Null): 5098 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5099 else: 5100 if isinstance(search, exp.Binary): 5101 search = exp.paren(search) 5102 5103 cond = exp.or_( 5104 expression.eq(search), 5105 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5106 copy=False, 5107 ) 5108 ifs.append(exp.If(this=cond, true=result)) 5109 5110 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5111 return self.sql(case)
5113 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5114 this = self.sql(expression, "this") 5115 this = self.seg(this, sep="") 5116 dimensions = self.expressions( 5117 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5118 ) 5119 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5120 metrics = self.expressions( 5121 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5122 ) 5123 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5124 where = self.sql(expression, "where") 5125 where = self.seg(f"WHERE {where}") if where else "" 5126 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"
5128 def getextract_sql(self, expression: exp.GetExtract) -> str: 5129 this = expression.this 5130 expr = expression.expression 5131 5132 if not this.type or not expression.type: 5133 from sqlglot.optimizer.annotate_types import annotate_types 5134 5135 this = annotate_types(this, dialect=self.dialect) 5136 5137 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5138 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5139 5140 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))