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 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 696 697 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 698 699 __slots__ = ( 700 "pretty", 701 "identify", 702 "normalize", 703 "pad", 704 "_indent", 705 "normalize_functions", 706 "unsupported_level", 707 "max_unsupported", 708 "leading_comma", 709 "max_text_width", 710 "comments", 711 "dialect", 712 "unsupported_messages", 713 "_escaped_quote_end", 714 "_escaped_identifier_end", 715 "_next_name", 716 "_identifier_start", 717 "_identifier_end", 718 "_quote_json_path_key_using_brackets", 719 ) 720 721 def __init__( 722 self, 723 pretty: t.Optional[bool] = None, 724 identify: str | bool = False, 725 normalize: bool = False, 726 pad: int = 2, 727 indent: int = 2, 728 normalize_functions: t.Optional[str | bool] = None, 729 unsupported_level: ErrorLevel = ErrorLevel.WARN, 730 max_unsupported: int = 3, 731 leading_comma: bool = False, 732 max_text_width: int = 80, 733 comments: bool = True, 734 dialect: DialectType = None, 735 ): 736 import sqlglot 737 from sqlglot.dialects import Dialect 738 739 self.pretty = pretty if pretty is not None else sqlglot.pretty 740 self.identify = identify 741 self.normalize = normalize 742 self.pad = pad 743 self._indent = indent 744 self.unsupported_level = unsupported_level 745 self.max_unsupported = max_unsupported 746 self.leading_comma = leading_comma 747 self.max_text_width = max_text_width 748 self.comments = comments 749 self.dialect = Dialect.get_or_raise(dialect) 750 751 # This is both a Dialect property and a Generator argument, so we prioritize the latter 752 self.normalize_functions = ( 753 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 754 ) 755 756 self.unsupported_messages: t.List[str] = [] 757 self._escaped_quote_end: str = ( 758 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 759 ) 760 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 761 762 self._next_name = name_sequence("_t") 763 764 self._identifier_start = self.dialect.IDENTIFIER_START 765 self._identifier_end = self.dialect.IDENTIFIER_END 766 767 self._quote_json_path_key_using_brackets = True 768 769 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 770 """ 771 Generates the SQL string corresponding to the given syntax tree. 772 773 Args: 774 expression: The syntax tree. 775 copy: Whether to copy the expression. The generator performs mutations so 776 it is safer to copy. 777 778 Returns: 779 The SQL string corresponding to `expression`. 780 """ 781 if copy: 782 expression = expression.copy() 783 784 expression = self.preprocess(expression) 785 786 self.unsupported_messages = [] 787 sql = self.sql(expression).strip() 788 789 if self.pretty: 790 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 791 792 if self.unsupported_level == ErrorLevel.IGNORE: 793 return sql 794 795 if self.unsupported_level == ErrorLevel.WARN: 796 for msg in self.unsupported_messages: 797 logger.warning(msg) 798 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 799 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 800 801 return sql 802 803 def preprocess(self, expression: exp.Expression) -> exp.Expression: 804 """Apply generic preprocessing transformations to a given expression.""" 805 expression = self._move_ctes_to_top_level(expression) 806 807 if self.ENSURE_BOOLS: 808 from sqlglot.transforms import ensure_bools 809 810 expression = ensure_bools(expression) 811 812 return expression 813 814 def _move_ctes_to_top_level(self, expression: E) -> E: 815 if ( 816 not expression.parent 817 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 818 and any(node.parent is not expression for node in expression.find_all(exp.With)) 819 ): 820 from sqlglot.transforms import move_ctes_to_top_level 821 822 expression = move_ctes_to_top_level(expression) 823 return expression 824 825 def unsupported(self, message: str) -> None: 826 if self.unsupported_level == ErrorLevel.IMMEDIATE: 827 raise UnsupportedError(message) 828 self.unsupported_messages.append(message) 829 830 def sep(self, sep: str = " ") -> str: 831 return f"{sep.strip()}\n" if self.pretty else sep 832 833 def seg(self, sql: str, sep: str = " ") -> str: 834 return f"{self.sep(sep)}{sql}" 835 836 def sanitize_comment(self, comment: str) -> str: 837 comment = " " + comment if comment[0].strip() else comment 838 comment = comment + " " if comment[-1].strip() else comment 839 840 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 841 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 842 comment = comment.replace("*/", "* /") 843 844 return comment 845 846 def maybe_comment( 847 self, 848 sql: str, 849 expression: t.Optional[exp.Expression] = None, 850 comments: t.Optional[t.List[str]] = None, 851 separated: bool = False, 852 ) -> str: 853 comments = ( 854 ((expression and expression.comments) if comments is None else comments) # type: ignore 855 if self.comments 856 else None 857 ) 858 859 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 860 return sql 861 862 comments_sql = " ".join( 863 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 864 ) 865 866 if not comments_sql: 867 return sql 868 869 comments_sql = self._replace_line_breaks(comments_sql) 870 871 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 872 return ( 873 f"{self.sep()}{comments_sql}{sql}" 874 if not sql or sql[0].isspace() 875 else f"{comments_sql}{self.sep()}{sql}" 876 ) 877 878 return f"{sql} {comments_sql}" 879 880 def wrap(self, expression: exp.Expression | str) -> str: 881 this_sql = ( 882 self.sql(expression) 883 if isinstance(expression, exp.UNWRAPPED_QUERIES) 884 else self.sql(expression, "this") 885 ) 886 if not this_sql: 887 return "()" 888 889 this_sql = self.indent(this_sql, level=1, pad=0) 890 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 891 892 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 893 original = self.identify 894 self.identify = False 895 result = func(*args, **kwargs) 896 self.identify = original 897 return result 898 899 def normalize_func(self, name: str) -> str: 900 if self.normalize_functions == "upper" or self.normalize_functions is True: 901 return name.upper() 902 if self.normalize_functions == "lower": 903 return name.lower() 904 return name 905 906 def indent( 907 self, 908 sql: str, 909 level: int = 0, 910 pad: t.Optional[int] = None, 911 skip_first: bool = False, 912 skip_last: bool = False, 913 ) -> str: 914 if not self.pretty or not sql: 915 return sql 916 917 pad = self.pad if pad is None else pad 918 lines = sql.split("\n") 919 920 return "\n".join( 921 ( 922 line 923 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 924 else f"{' ' * (level * self._indent + pad)}{line}" 925 ) 926 for i, line in enumerate(lines) 927 ) 928 929 def sql( 930 self, 931 expression: t.Optional[str | exp.Expression], 932 key: t.Optional[str] = None, 933 comment: bool = True, 934 ) -> str: 935 if not expression: 936 return "" 937 938 if isinstance(expression, str): 939 return expression 940 941 if key: 942 value = expression.args.get(key) 943 if value: 944 return self.sql(value) 945 return "" 946 947 transform = self.TRANSFORMS.get(expression.__class__) 948 949 if callable(transform): 950 sql = transform(self, expression) 951 elif isinstance(expression, exp.Expression): 952 exp_handler_name = f"{expression.key}_sql" 953 954 if hasattr(self, exp_handler_name): 955 sql = getattr(self, exp_handler_name)(expression) 956 elif isinstance(expression, exp.Func): 957 sql = self.function_fallback_sql(expression) 958 elif isinstance(expression, exp.Property): 959 sql = self.property_sql(expression) 960 else: 961 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 962 else: 963 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 964 965 return self.maybe_comment(sql, expression) if self.comments and comment else sql 966 967 def uncache_sql(self, expression: exp.Uncache) -> str: 968 table = self.sql(expression, "this") 969 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 970 return f"UNCACHE TABLE{exists_sql} {table}" 971 972 def cache_sql(self, expression: exp.Cache) -> str: 973 lazy = " LAZY" if expression.args.get("lazy") else "" 974 table = self.sql(expression, "this") 975 options = expression.args.get("options") 976 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 977 sql = self.sql(expression, "expression") 978 sql = f" AS{self.sep()}{sql}" if sql else "" 979 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 980 return self.prepend_ctes(expression, sql) 981 982 def characterset_sql(self, expression: exp.CharacterSet) -> str: 983 if isinstance(expression.parent, exp.Cast): 984 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 985 default = "DEFAULT " if expression.args.get("default") else "" 986 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 987 988 def column_parts(self, expression: exp.Column) -> str: 989 return ".".join( 990 self.sql(part) 991 for part in ( 992 expression.args.get("catalog"), 993 expression.args.get("db"), 994 expression.args.get("table"), 995 expression.args.get("this"), 996 ) 997 if part 998 ) 999 1000 def column_sql(self, expression: exp.Column) -> str: 1001 join_mark = " (+)" if expression.args.get("join_mark") else "" 1002 1003 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1004 join_mark = "" 1005 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1006 1007 return f"{self.column_parts(expression)}{join_mark}" 1008 1009 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1010 this = self.sql(expression, "this") 1011 this = f" {this}" if this else "" 1012 position = self.sql(expression, "position") 1013 return f"{position}{this}" 1014 1015 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1016 column = self.sql(expression, "this") 1017 kind = self.sql(expression, "kind") 1018 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1019 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1020 kind = f"{sep}{kind}" if kind else "" 1021 constraints = f" {constraints}" if constraints else "" 1022 position = self.sql(expression, "position") 1023 position = f" {position}" if position else "" 1024 1025 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1026 kind = "" 1027 1028 return f"{exists}{column}{kind}{constraints}{position}" 1029 1030 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1031 this = self.sql(expression, "this") 1032 kind_sql = self.sql(expression, "kind").strip() 1033 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1034 1035 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1036 this = self.sql(expression, "this") 1037 if expression.args.get("not_null"): 1038 persisted = " PERSISTED NOT NULL" 1039 elif expression.args.get("persisted"): 1040 persisted = " PERSISTED" 1041 else: 1042 persisted = "" 1043 1044 return f"AS {this}{persisted}" 1045 1046 def autoincrementcolumnconstraint_sql(self, _) -> str: 1047 return self.token_sql(TokenType.AUTO_INCREMENT) 1048 1049 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1050 if isinstance(expression.this, list): 1051 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1052 else: 1053 this = self.sql(expression, "this") 1054 1055 return f"COMPRESS {this}" 1056 1057 def generatedasidentitycolumnconstraint_sql( 1058 self, expression: exp.GeneratedAsIdentityColumnConstraint 1059 ) -> str: 1060 this = "" 1061 if expression.this is not None: 1062 on_null = " ON NULL" if expression.args.get("on_null") else "" 1063 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1064 1065 start = expression.args.get("start") 1066 start = f"START WITH {start}" if start else "" 1067 increment = expression.args.get("increment") 1068 increment = f" INCREMENT BY {increment}" if increment else "" 1069 minvalue = expression.args.get("minvalue") 1070 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1071 maxvalue = expression.args.get("maxvalue") 1072 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1073 cycle = expression.args.get("cycle") 1074 cycle_sql = "" 1075 1076 if cycle is not None: 1077 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1078 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1079 1080 sequence_opts = "" 1081 if start or increment or cycle_sql: 1082 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1083 sequence_opts = f" ({sequence_opts.strip()})" 1084 1085 expr = self.sql(expression, "expression") 1086 expr = f"({expr})" if expr else "IDENTITY" 1087 1088 return f"GENERATED{this} AS {expr}{sequence_opts}" 1089 1090 def generatedasrowcolumnconstraint_sql( 1091 self, expression: exp.GeneratedAsRowColumnConstraint 1092 ) -> str: 1093 start = "START" if expression.args.get("start") else "END" 1094 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1095 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1096 1097 def periodforsystemtimeconstraint_sql( 1098 self, expression: exp.PeriodForSystemTimeConstraint 1099 ) -> str: 1100 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1101 1102 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1103 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1104 1105 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1106 desc = expression.args.get("desc") 1107 if desc is not None: 1108 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1109 options = self.expressions(expression, key="options", flat=True, sep=" ") 1110 options = f" {options}" if options else "" 1111 return f"PRIMARY KEY{options}" 1112 1113 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1114 this = self.sql(expression, "this") 1115 this = f" {this}" if this else "" 1116 index_type = expression.args.get("index_type") 1117 index_type = f" USING {index_type}" if index_type else "" 1118 on_conflict = self.sql(expression, "on_conflict") 1119 on_conflict = f" {on_conflict}" if on_conflict else "" 1120 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1121 options = self.expressions(expression, key="options", flat=True, sep=" ") 1122 options = f" {options}" if options else "" 1123 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1124 1125 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1126 return self.sql(expression, "this") 1127 1128 def create_sql(self, expression: exp.Create) -> str: 1129 kind = self.sql(expression, "kind") 1130 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1131 properties = expression.args.get("properties") 1132 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1133 1134 this = self.createable_sql(expression, properties_locs) 1135 1136 properties_sql = "" 1137 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1138 exp.Properties.Location.POST_WITH 1139 ): 1140 properties_sql = self.sql( 1141 exp.Properties( 1142 expressions=[ 1143 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1144 *properties_locs[exp.Properties.Location.POST_WITH], 1145 ] 1146 ) 1147 ) 1148 1149 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1150 properties_sql = self.sep() + properties_sql 1151 elif not self.pretty: 1152 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1153 properties_sql = f" {properties_sql}" 1154 1155 begin = " BEGIN" if expression.args.get("begin") else "" 1156 end = " END" if expression.args.get("end") else "" 1157 1158 expression_sql = self.sql(expression, "expression") 1159 if expression_sql: 1160 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1161 1162 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1163 postalias_props_sql = "" 1164 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1165 postalias_props_sql = self.properties( 1166 exp.Properties( 1167 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1168 ), 1169 wrapped=False, 1170 ) 1171 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1172 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1173 1174 postindex_props_sql = "" 1175 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1176 postindex_props_sql = self.properties( 1177 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1178 wrapped=False, 1179 prefix=" ", 1180 ) 1181 1182 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1183 indexes = f" {indexes}" if indexes else "" 1184 index_sql = indexes + postindex_props_sql 1185 1186 replace = " OR REPLACE" if expression.args.get("replace") else "" 1187 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1188 unique = " UNIQUE" if expression.args.get("unique") else "" 1189 1190 clustered = expression.args.get("clustered") 1191 if clustered is None: 1192 clustered_sql = "" 1193 elif clustered: 1194 clustered_sql = " CLUSTERED COLUMNSTORE" 1195 else: 1196 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1197 1198 postcreate_props_sql = "" 1199 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1200 postcreate_props_sql = self.properties( 1201 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1202 sep=" ", 1203 prefix=" ", 1204 wrapped=False, 1205 ) 1206 1207 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1208 1209 postexpression_props_sql = "" 1210 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1211 postexpression_props_sql = self.properties( 1212 exp.Properties( 1213 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1214 ), 1215 sep=" ", 1216 prefix=" ", 1217 wrapped=False, 1218 ) 1219 1220 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1221 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1222 no_schema_binding = ( 1223 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1224 ) 1225 1226 clone = self.sql(expression, "clone") 1227 clone = f" {clone}" if clone else "" 1228 1229 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1230 properties_expression = f"{expression_sql}{properties_sql}" 1231 else: 1232 properties_expression = f"{properties_sql}{expression_sql}" 1233 1234 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1235 return self.prepend_ctes(expression, expression_sql) 1236 1237 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1238 start = self.sql(expression, "start") 1239 start = f"START WITH {start}" if start else "" 1240 increment = self.sql(expression, "increment") 1241 increment = f" INCREMENT BY {increment}" if increment else "" 1242 minvalue = self.sql(expression, "minvalue") 1243 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1244 maxvalue = self.sql(expression, "maxvalue") 1245 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1246 owned = self.sql(expression, "owned") 1247 owned = f" OWNED BY {owned}" if owned else "" 1248 1249 cache = expression.args.get("cache") 1250 if cache is None: 1251 cache_str = "" 1252 elif cache is True: 1253 cache_str = " CACHE" 1254 else: 1255 cache_str = f" CACHE {cache}" 1256 1257 options = self.expressions(expression, key="options", flat=True, sep=" ") 1258 options = f" {options}" if options else "" 1259 1260 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1261 1262 def clone_sql(self, expression: exp.Clone) -> str: 1263 this = self.sql(expression, "this") 1264 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1265 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1266 return f"{shallow}{keyword} {this}" 1267 1268 def describe_sql(self, expression: exp.Describe) -> str: 1269 style = expression.args.get("style") 1270 style = f" {style}" if style else "" 1271 partition = self.sql(expression, "partition") 1272 partition = f" {partition}" if partition else "" 1273 format = self.sql(expression, "format") 1274 format = f" {format}" if format else "" 1275 1276 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1277 1278 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1279 tag = self.sql(expression, "tag") 1280 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1281 1282 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1283 with_ = self.sql(expression, "with") 1284 if with_: 1285 sql = f"{with_}{self.sep()}{sql}" 1286 return sql 1287 1288 def with_sql(self, expression: exp.With) -> str: 1289 sql = self.expressions(expression, flat=True) 1290 recursive = ( 1291 "RECURSIVE " 1292 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1293 else "" 1294 ) 1295 search = self.sql(expression, "search") 1296 search = f" {search}" if search else "" 1297 1298 return f"WITH {recursive}{sql}{search}" 1299 1300 def cte_sql(self, expression: exp.CTE) -> str: 1301 alias = expression.args.get("alias") 1302 if alias: 1303 alias.add_comments(expression.pop_comments()) 1304 1305 alias_sql = self.sql(expression, "alias") 1306 1307 materialized = expression.args.get("materialized") 1308 if materialized is False: 1309 materialized = "NOT MATERIALIZED " 1310 elif materialized: 1311 materialized = "MATERIALIZED " 1312 1313 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1314 1315 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1316 alias = self.sql(expression, "this") 1317 columns = self.expressions(expression, key="columns", flat=True) 1318 columns = f"({columns})" if columns else "" 1319 1320 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1321 columns = "" 1322 self.unsupported("Named columns are not supported in table alias.") 1323 1324 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1325 alias = self._next_name() 1326 1327 return f"{alias}{columns}" 1328 1329 def bitstring_sql(self, expression: exp.BitString) -> str: 1330 this = self.sql(expression, "this") 1331 if self.dialect.BIT_START: 1332 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1333 return f"{int(this, 2)}" 1334 1335 def hexstring_sql( 1336 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1337 ) -> str: 1338 this = self.sql(expression, "this") 1339 is_integer_type = expression.args.get("is_integer") 1340 1341 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1342 not self.dialect.HEX_START and not binary_function_repr 1343 ): 1344 # Integer representation will be returned if: 1345 # - The read dialect treats the hex value as integer literal but not the write 1346 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1347 return f"{int(this, 16)}" 1348 1349 if not is_integer_type: 1350 # Read dialect treats the hex value as BINARY/BLOB 1351 if binary_function_repr: 1352 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1353 return self.func(binary_function_repr, exp.Literal.string(this)) 1354 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1355 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1356 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1357 1358 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1359 1360 def bytestring_sql(self, expression: exp.ByteString) -> str: 1361 this = self.sql(expression, "this") 1362 if self.dialect.BYTE_START: 1363 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1364 return this 1365 1366 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1367 this = self.sql(expression, "this") 1368 escape = expression.args.get("escape") 1369 1370 if self.dialect.UNICODE_START: 1371 escape_substitute = r"\\\1" 1372 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1373 else: 1374 escape_substitute = r"\\u\1" 1375 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1376 1377 if escape: 1378 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1379 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1380 else: 1381 escape_pattern = ESCAPED_UNICODE_RE 1382 escape_sql = "" 1383 1384 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1385 this = escape_pattern.sub(escape_substitute, this) 1386 1387 return f"{left_quote}{this}{right_quote}{escape_sql}" 1388 1389 def rawstring_sql(self, expression: exp.RawString) -> str: 1390 string = expression.this 1391 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1392 string = string.replace("\\", "\\\\") 1393 1394 string = self.escape_str(string, escape_backslash=False) 1395 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1396 1397 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1398 this = self.sql(expression, "this") 1399 specifier = self.sql(expression, "expression") 1400 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1401 return f"{this}{specifier}" 1402 1403 def datatype_sql(self, expression: exp.DataType) -> str: 1404 nested = "" 1405 values = "" 1406 interior = self.expressions(expression, flat=True) 1407 1408 type_value = expression.this 1409 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1410 type_sql = self.sql(expression, "kind") 1411 else: 1412 type_sql = ( 1413 self.TYPE_MAPPING.get(type_value, type_value.value) 1414 if isinstance(type_value, exp.DataType.Type) 1415 else type_value 1416 ) 1417 1418 if interior: 1419 if expression.args.get("nested"): 1420 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1421 if expression.args.get("values") is not None: 1422 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1423 values = self.expressions(expression, key="values", flat=True) 1424 values = f"{delimiters[0]}{values}{delimiters[1]}" 1425 elif type_value == exp.DataType.Type.INTERVAL: 1426 nested = f" {interior}" 1427 else: 1428 nested = f"({interior})" 1429 1430 type_sql = f"{type_sql}{nested}{values}" 1431 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1432 exp.DataType.Type.TIMETZ, 1433 exp.DataType.Type.TIMESTAMPTZ, 1434 ): 1435 type_sql = f"{type_sql} WITH TIME ZONE" 1436 1437 return type_sql 1438 1439 def directory_sql(self, expression: exp.Directory) -> str: 1440 local = "LOCAL " if expression.args.get("local") else "" 1441 row_format = self.sql(expression, "row_format") 1442 row_format = f" {row_format}" if row_format else "" 1443 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1444 1445 def delete_sql(self, expression: exp.Delete) -> str: 1446 this = self.sql(expression, "this") 1447 this = f" FROM {this}" if this else "" 1448 using = self.sql(expression, "using") 1449 using = f" USING {using}" if using else "" 1450 cluster = self.sql(expression, "cluster") 1451 cluster = f" {cluster}" if cluster else "" 1452 where = self.sql(expression, "where") 1453 returning = self.sql(expression, "returning") 1454 limit = self.sql(expression, "limit") 1455 tables = self.expressions(expression, key="tables") 1456 tables = f" {tables}" if tables else "" 1457 if self.RETURNING_END: 1458 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1459 else: 1460 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1461 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1462 1463 def drop_sql(self, expression: exp.Drop) -> str: 1464 this = self.sql(expression, "this") 1465 expressions = self.expressions(expression, flat=True) 1466 expressions = f" ({expressions})" if expressions else "" 1467 kind = expression.args["kind"] 1468 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1469 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1470 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1471 on_cluster = self.sql(expression, "cluster") 1472 on_cluster = f" {on_cluster}" if on_cluster else "" 1473 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1474 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1475 cascade = " CASCADE" if expression.args.get("cascade") else "" 1476 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1477 purge = " PURGE" if expression.args.get("purge") else "" 1478 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1479 1480 def set_operation(self, expression: exp.SetOperation) -> str: 1481 op_type = type(expression) 1482 op_name = op_type.key.upper() 1483 1484 distinct = expression.args.get("distinct") 1485 if ( 1486 distinct is False 1487 and op_type in (exp.Except, exp.Intersect) 1488 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1489 ): 1490 self.unsupported(f"{op_name} ALL is not supported") 1491 1492 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1493 1494 if distinct is None: 1495 distinct = default_distinct 1496 if distinct is None: 1497 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1498 1499 if distinct is default_distinct: 1500 distinct_or_all = "" 1501 else: 1502 distinct_or_all = " DISTINCT" if distinct else " ALL" 1503 1504 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1505 side_kind = f"{side_kind} " if side_kind else "" 1506 1507 by_name = " BY NAME" if expression.args.get("by_name") else "" 1508 on = self.expressions(expression, key="on", flat=True) 1509 on = f" ON ({on})" if on else "" 1510 1511 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1512 1513 def set_operations(self, expression: exp.SetOperation) -> str: 1514 if not self.SET_OP_MODIFIERS: 1515 limit = expression.args.get("limit") 1516 order = expression.args.get("order") 1517 1518 if limit or order: 1519 select = self._move_ctes_to_top_level( 1520 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1521 ) 1522 1523 if limit: 1524 select = select.limit(limit.pop(), copy=False) 1525 if order: 1526 select = select.order_by(order.pop(), copy=False) 1527 return self.sql(select) 1528 1529 sqls: t.List[str] = [] 1530 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1531 1532 while stack: 1533 node = stack.pop() 1534 1535 if isinstance(node, exp.SetOperation): 1536 stack.append(node.expression) 1537 stack.append( 1538 self.maybe_comment( 1539 self.set_operation(node), comments=node.comments, separated=True 1540 ) 1541 ) 1542 stack.append(node.this) 1543 else: 1544 sqls.append(self.sql(node)) 1545 1546 this = self.sep().join(sqls) 1547 this = self.query_modifiers(expression, this) 1548 return self.prepend_ctes(expression, this) 1549 1550 def fetch_sql(self, expression: exp.Fetch) -> str: 1551 direction = expression.args.get("direction") 1552 direction = f" {direction}" if direction else "" 1553 count = self.sql(expression, "count") 1554 count = f" {count}" if count else "" 1555 limit_options = self.sql(expression, "limit_options") 1556 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1557 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1558 1559 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1560 percent = " PERCENT" if expression.args.get("percent") else "" 1561 rows = " ROWS" if expression.args.get("rows") else "" 1562 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1563 if not with_ties and rows: 1564 with_ties = " ONLY" 1565 return f"{percent}{rows}{with_ties}" 1566 1567 def filter_sql(self, expression: exp.Filter) -> str: 1568 if self.AGGREGATE_FILTER_SUPPORTED: 1569 this = self.sql(expression, "this") 1570 where = self.sql(expression, "expression").strip() 1571 return f"{this} FILTER({where})" 1572 1573 agg = expression.this 1574 agg_arg = agg.this 1575 cond = expression.expression.this 1576 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1577 return self.sql(agg) 1578 1579 def hint_sql(self, expression: exp.Hint) -> str: 1580 if not self.QUERY_HINTS: 1581 self.unsupported("Hints are not supported") 1582 return "" 1583 1584 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1585 1586 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1587 using = self.sql(expression, "using") 1588 using = f" USING {using}" if using else "" 1589 columns = self.expressions(expression, key="columns", flat=True) 1590 columns = f"({columns})" if columns else "" 1591 partition_by = self.expressions(expression, key="partition_by", flat=True) 1592 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1593 where = self.sql(expression, "where") 1594 include = self.expressions(expression, key="include", flat=True) 1595 if include: 1596 include = f" INCLUDE ({include})" 1597 with_storage = self.expressions(expression, key="with_storage", flat=True) 1598 with_storage = f" WITH ({with_storage})" if with_storage else "" 1599 tablespace = self.sql(expression, "tablespace") 1600 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1601 on = self.sql(expression, "on") 1602 on = f" ON {on}" if on else "" 1603 1604 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1605 1606 def index_sql(self, expression: exp.Index) -> str: 1607 unique = "UNIQUE " if expression.args.get("unique") else "" 1608 primary = "PRIMARY " if expression.args.get("primary") else "" 1609 amp = "AMP " if expression.args.get("amp") else "" 1610 name = self.sql(expression, "this") 1611 name = f"{name} " if name else "" 1612 table = self.sql(expression, "table") 1613 table = f"{self.INDEX_ON} {table}" if table else "" 1614 1615 index = "INDEX " if not table else "" 1616 1617 params = self.sql(expression, "params") 1618 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1619 1620 def identifier_sql(self, expression: exp.Identifier) -> str: 1621 text = expression.name 1622 lower = text.lower() 1623 text = lower if self.normalize and not expression.quoted else text 1624 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1625 if ( 1626 expression.quoted 1627 or self.dialect.can_identify(text, self.identify) 1628 or lower in self.RESERVED_KEYWORDS 1629 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1630 ): 1631 text = f"{self._identifier_start}{text}{self._identifier_end}" 1632 return text 1633 1634 def hex_sql(self, expression: exp.Hex) -> str: 1635 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1636 if self.dialect.HEX_LOWERCASE: 1637 text = self.func("LOWER", text) 1638 1639 return text 1640 1641 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1642 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1643 if not self.dialect.HEX_LOWERCASE: 1644 text = self.func("LOWER", text) 1645 return text 1646 1647 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1648 input_format = self.sql(expression, "input_format") 1649 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1650 output_format = self.sql(expression, "output_format") 1651 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1652 return self.sep().join((input_format, output_format)) 1653 1654 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1655 string = self.sql(exp.Literal.string(expression.name)) 1656 return f"{prefix}{string}" 1657 1658 def partition_sql(self, expression: exp.Partition) -> str: 1659 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1660 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1661 1662 def properties_sql(self, expression: exp.Properties) -> str: 1663 root_properties = [] 1664 with_properties = [] 1665 1666 for p in expression.expressions: 1667 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1668 if p_loc == exp.Properties.Location.POST_WITH: 1669 with_properties.append(p) 1670 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1671 root_properties.append(p) 1672 1673 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1674 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1675 1676 if root_props and with_props and not self.pretty: 1677 with_props = " " + with_props 1678 1679 return root_props + with_props 1680 1681 def root_properties(self, properties: exp.Properties) -> str: 1682 if properties.expressions: 1683 return self.expressions(properties, indent=False, sep=" ") 1684 return "" 1685 1686 def properties( 1687 self, 1688 properties: exp.Properties, 1689 prefix: str = "", 1690 sep: str = ", ", 1691 suffix: str = "", 1692 wrapped: bool = True, 1693 ) -> str: 1694 if properties.expressions: 1695 expressions = self.expressions(properties, sep=sep, indent=False) 1696 if expressions: 1697 expressions = self.wrap(expressions) if wrapped else expressions 1698 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1699 return "" 1700 1701 def with_properties(self, properties: exp.Properties) -> str: 1702 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1703 1704 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1705 properties_locs = defaultdict(list) 1706 for p in properties.expressions: 1707 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1708 if p_loc != exp.Properties.Location.UNSUPPORTED: 1709 properties_locs[p_loc].append(p) 1710 else: 1711 self.unsupported(f"Unsupported property {p.key}") 1712 1713 return properties_locs 1714 1715 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1716 if isinstance(expression.this, exp.Dot): 1717 return self.sql(expression, "this") 1718 return f"'{expression.name}'" if string_key else expression.name 1719 1720 def property_sql(self, expression: exp.Property) -> str: 1721 property_cls = expression.__class__ 1722 if property_cls == exp.Property: 1723 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1724 1725 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1726 if not property_name: 1727 self.unsupported(f"Unsupported property {expression.key}") 1728 1729 return f"{property_name}={self.sql(expression, 'this')}" 1730 1731 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1732 if self.SUPPORTS_CREATE_TABLE_LIKE: 1733 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1734 options = f" {options}" if options else "" 1735 1736 like = f"LIKE {self.sql(expression, 'this')}{options}" 1737 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1738 like = f"({like})" 1739 1740 return like 1741 1742 if expression.expressions: 1743 self.unsupported("Transpilation of LIKE property options is unsupported") 1744 1745 select = exp.select("*").from_(expression.this).limit(0) 1746 return f"AS {self.sql(select)}" 1747 1748 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1749 no = "NO " if expression.args.get("no") else "" 1750 protection = " PROTECTION" if expression.args.get("protection") else "" 1751 return f"{no}FALLBACK{protection}" 1752 1753 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1754 no = "NO " if expression.args.get("no") else "" 1755 local = expression.args.get("local") 1756 local = f"{local} " if local else "" 1757 dual = "DUAL " if expression.args.get("dual") else "" 1758 before = "BEFORE " if expression.args.get("before") else "" 1759 after = "AFTER " if expression.args.get("after") else "" 1760 return f"{no}{local}{dual}{before}{after}JOURNAL" 1761 1762 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1763 freespace = self.sql(expression, "this") 1764 percent = " PERCENT" if expression.args.get("percent") else "" 1765 return f"FREESPACE={freespace}{percent}" 1766 1767 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1768 if expression.args.get("default"): 1769 property = "DEFAULT" 1770 elif expression.args.get("on"): 1771 property = "ON" 1772 else: 1773 property = "OFF" 1774 return f"CHECKSUM={property}" 1775 1776 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1777 if expression.args.get("no"): 1778 return "NO MERGEBLOCKRATIO" 1779 if expression.args.get("default"): 1780 return "DEFAULT MERGEBLOCKRATIO" 1781 1782 percent = " PERCENT" if expression.args.get("percent") else "" 1783 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1784 1785 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1786 default = expression.args.get("default") 1787 minimum = expression.args.get("minimum") 1788 maximum = expression.args.get("maximum") 1789 if default or minimum or maximum: 1790 if default: 1791 prop = "DEFAULT" 1792 elif minimum: 1793 prop = "MINIMUM" 1794 else: 1795 prop = "MAXIMUM" 1796 return f"{prop} DATABLOCKSIZE" 1797 units = expression.args.get("units") 1798 units = f" {units}" if units else "" 1799 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1800 1801 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1802 autotemp = expression.args.get("autotemp") 1803 always = expression.args.get("always") 1804 default = expression.args.get("default") 1805 manual = expression.args.get("manual") 1806 never = expression.args.get("never") 1807 1808 if autotemp is not None: 1809 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1810 elif always: 1811 prop = "ALWAYS" 1812 elif default: 1813 prop = "DEFAULT" 1814 elif manual: 1815 prop = "MANUAL" 1816 elif never: 1817 prop = "NEVER" 1818 return f"BLOCKCOMPRESSION={prop}" 1819 1820 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1821 no = expression.args.get("no") 1822 no = " NO" if no else "" 1823 concurrent = expression.args.get("concurrent") 1824 concurrent = " CONCURRENT" if concurrent else "" 1825 target = self.sql(expression, "target") 1826 target = f" {target}" if target else "" 1827 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1828 1829 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1830 if isinstance(expression.this, list): 1831 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1832 if expression.this: 1833 modulus = self.sql(expression, "this") 1834 remainder = self.sql(expression, "expression") 1835 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1836 1837 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1838 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1839 return f"FROM ({from_expressions}) TO ({to_expressions})" 1840 1841 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1842 this = self.sql(expression, "this") 1843 1844 for_values_or_default = expression.expression 1845 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1846 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1847 else: 1848 for_values_or_default = " DEFAULT" 1849 1850 return f"PARTITION OF {this}{for_values_or_default}" 1851 1852 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1853 kind = expression.args.get("kind") 1854 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1855 for_or_in = expression.args.get("for_or_in") 1856 for_or_in = f" {for_or_in}" if for_or_in else "" 1857 lock_type = expression.args.get("lock_type") 1858 override = " OVERRIDE" if expression.args.get("override") else "" 1859 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1860 1861 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1862 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1863 statistics = expression.args.get("statistics") 1864 statistics_sql = "" 1865 if statistics is not None: 1866 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1867 return f"{data_sql}{statistics_sql}" 1868 1869 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1870 this = self.sql(expression, "this") 1871 this = f"HISTORY_TABLE={this}" if this else "" 1872 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1873 data_consistency = ( 1874 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1875 ) 1876 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1877 retention_period = ( 1878 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1879 ) 1880 1881 if this: 1882 on_sql = self.func("ON", this, data_consistency, retention_period) 1883 else: 1884 on_sql = "ON" if expression.args.get("on") else "OFF" 1885 1886 sql = f"SYSTEM_VERSIONING={on_sql}" 1887 1888 return f"WITH({sql})" if expression.args.get("with") else sql 1889 1890 def insert_sql(self, expression: exp.Insert) -> str: 1891 hint = self.sql(expression, "hint") 1892 overwrite = expression.args.get("overwrite") 1893 1894 if isinstance(expression.this, exp.Directory): 1895 this = " OVERWRITE" if overwrite else " INTO" 1896 else: 1897 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1898 1899 stored = self.sql(expression, "stored") 1900 stored = f" {stored}" if stored else "" 1901 alternative = expression.args.get("alternative") 1902 alternative = f" OR {alternative}" if alternative else "" 1903 ignore = " IGNORE" if expression.args.get("ignore") else "" 1904 is_function = expression.args.get("is_function") 1905 if is_function: 1906 this = f"{this} FUNCTION" 1907 this = f"{this} {self.sql(expression, 'this')}" 1908 1909 exists = " IF EXISTS" if expression.args.get("exists") else "" 1910 where = self.sql(expression, "where") 1911 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1912 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1913 on_conflict = self.sql(expression, "conflict") 1914 on_conflict = f" {on_conflict}" if on_conflict else "" 1915 by_name = " BY NAME" if expression.args.get("by_name") else "" 1916 returning = self.sql(expression, "returning") 1917 1918 if self.RETURNING_END: 1919 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1920 else: 1921 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1922 1923 partition_by = self.sql(expression, "partition") 1924 partition_by = f" {partition_by}" if partition_by else "" 1925 settings = self.sql(expression, "settings") 1926 settings = f" {settings}" if settings else "" 1927 1928 source = self.sql(expression, "source") 1929 source = f"TABLE {source}" if source else "" 1930 1931 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1932 return self.prepend_ctes(expression, sql) 1933 1934 def introducer_sql(self, expression: exp.Introducer) -> str: 1935 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1936 1937 def kill_sql(self, expression: exp.Kill) -> str: 1938 kind = self.sql(expression, "kind") 1939 kind = f" {kind}" if kind else "" 1940 this = self.sql(expression, "this") 1941 this = f" {this}" if this else "" 1942 return f"KILL{kind}{this}" 1943 1944 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1945 return expression.name 1946 1947 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1948 return expression.name 1949 1950 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1951 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1952 1953 constraint = self.sql(expression, "constraint") 1954 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1955 1956 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1957 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1958 action = self.sql(expression, "action") 1959 1960 expressions = self.expressions(expression, flat=True) 1961 if expressions: 1962 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1963 expressions = f" {set_keyword}{expressions}" 1964 1965 where = self.sql(expression, "where") 1966 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1967 1968 def returning_sql(self, expression: exp.Returning) -> str: 1969 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1970 1971 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1972 fields = self.sql(expression, "fields") 1973 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1974 escaped = self.sql(expression, "escaped") 1975 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1976 items = self.sql(expression, "collection_items") 1977 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1978 keys = self.sql(expression, "map_keys") 1979 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1980 lines = self.sql(expression, "lines") 1981 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1982 null = self.sql(expression, "null") 1983 null = f" NULL DEFINED AS {null}" if null else "" 1984 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1985 1986 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1987 return f"WITH ({self.expressions(expression, flat=True)})" 1988 1989 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1990 this = f"{self.sql(expression, 'this')} INDEX" 1991 target = self.sql(expression, "target") 1992 target = f" FOR {target}" if target else "" 1993 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1994 1995 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1996 this = self.sql(expression, "this") 1997 kind = self.sql(expression, "kind") 1998 expr = self.sql(expression, "expression") 1999 return f"{this} ({kind} => {expr})" 2000 2001 def table_parts(self, expression: exp.Table) -> str: 2002 return ".".join( 2003 self.sql(part) 2004 for part in ( 2005 expression.args.get("catalog"), 2006 expression.args.get("db"), 2007 expression.args.get("this"), 2008 ) 2009 if part is not None 2010 ) 2011 2012 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2013 table = self.table_parts(expression) 2014 only = "ONLY " if expression.args.get("only") else "" 2015 partition = self.sql(expression, "partition") 2016 partition = f" {partition}" if partition else "" 2017 version = self.sql(expression, "version") 2018 version = f" {version}" if version else "" 2019 alias = self.sql(expression, "alias") 2020 alias = f"{sep}{alias}" if alias else "" 2021 2022 sample = self.sql(expression, "sample") 2023 if self.dialect.ALIAS_POST_TABLESAMPLE: 2024 sample_pre_alias = sample 2025 sample_post_alias = "" 2026 else: 2027 sample_pre_alias = "" 2028 sample_post_alias = sample 2029 2030 hints = self.expressions(expression, key="hints", sep=" ") 2031 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2032 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2033 joins = self.indent( 2034 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2035 ) 2036 laterals = self.expressions(expression, key="laterals", sep="") 2037 2038 file_format = self.sql(expression, "format") 2039 if file_format: 2040 pattern = self.sql(expression, "pattern") 2041 pattern = f", PATTERN => {pattern}" if pattern else "" 2042 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2043 2044 ordinality = expression.args.get("ordinality") or "" 2045 if ordinality: 2046 ordinality = f" WITH ORDINALITY{alias}" 2047 alias = "" 2048 2049 when = self.sql(expression, "when") 2050 if when: 2051 table = f"{table} {when}" 2052 2053 changes = self.sql(expression, "changes") 2054 changes = f" {changes}" if changes else "" 2055 2056 rows_from = self.expressions(expression, key="rows_from") 2057 if rows_from: 2058 table = f"ROWS FROM {self.wrap(rows_from)}" 2059 2060 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2061 2062 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2063 table = self.func("TABLE", expression.this) 2064 alias = self.sql(expression, "alias") 2065 alias = f" AS {alias}" if alias else "" 2066 sample = self.sql(expression, "sample") 2067 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2068 joins = self.indent( 2069 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2070 ) 2071 return f"{table}{alias}{pivots}{sample}{joins}" 2072 2073 def tablesample_sql( 2074 self, 2075 expression: exp.TableSample, 2076 tablesample_keyword: t.Optional[str] = None, 2077 ) -> str: 2078 method = self.sql(expression, "method") 2079 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2080 numerator = self.sql(expression, "bucket_numerator") 2081 denominator = self.sql(expression, "bucket_denominator") 2082 field = self.sql(expression, "bucket_field") 2083 field = f" ON {field}" if field else "" 2084 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2085 seed = self.sql(expression, "seed") 2086 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2087 2088 size = self.sql(expression, "size") 2089 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2090 size = f"{size} ROWS" 2091 2092 percent = self.sql(expression, "percent") 2093 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2094 percent = f"{percent} PERCENT" 2095 2096 expr = f"{bucket}{percent}{size}" 2097 if self.TABLESAMPLE_REQUIRES_PARENS: 2098 expr = f"({expr})" 2099 2100 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2101 2102 def pivot_sql(self, expression: exp.Pivot) -> str: 2103 expressions = self.expressions(expression, flat=True) 2104 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2105 2106 group = self.sql(expression, "group") 2107 2108 if expression.this: 2109 this = self.sql(expression, "this") 2110 if not expressions: 2111 return f"UNPIVOT {this}" 2112 2113 on = f"{self.seg('ON')} {expressions}" 2114 into = self.sql(expression, "into") 2115 into = f"{self.seg('INTO')} {into}" if into else "" 2116 using = self.expressions(expression, key="using", flat=True) 2117 using = f"{self.seg('USING')} {using}" if using else "" 2118 return f"{direction} {this}{on}{into}{using}{group}" 2119 2120 alias = self.sql(expression, "alias") 2121 alias = f" AS {alias}" if alias else "" 2122 2123 fields = self.expressions( 2124 expression, 2125 "fields", 2126 sep=" ", 2127 dynamic=True, 2128 new_line=True, 2129 skip_first=True, 2130 skip_last=True, 2131 ) 2132 2133 include_nulls = expression.args.get("include_nulls") 2134 if include_nulls is not None: 2135 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2136 else: 2137 nulls = "" 2138 2139 default_on_null = self.sql(expression, "default_on_null") 2140 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2141 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2142 2143 def version_sql(self, expression: exp.Version) -> str: 2144 this = f"FOR {expression.name}" 2145 kind = expression.text("kind") 2146 expr = self.sql(expression, "expression") 2147 return f"{this} {kind} {expr}" 2148 2149 def tuple_sql(self, expression: exp.Tuple) -> str: 2150 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2151 2152 def update_sql(self, expression: exp.Update) -> str: 2153 this = self.sql(expression, "this") 2154 set_sql = self.expressions(expression, flat=True) 2155 from_sql = self.sql(expression, "from") 2156 where_sql = self.sql(expression, "where") 2157 returning = self.sql(expression, "returning") 2158 order = self.sql(expression, "order") 2159 limit = self.sql(expression, "limit") 2160 if self.RETURNING_END: 2161 expression_sql = f"{from_sql}{where_sql}{returning}" 2162 else: 2163 expression_sql = f"{returning}{from_sql}{where_sql}" 2164 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2165 return self.prepend_ctes(expression, sql) 2166 2167 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2168 values_as_table = values_as_table and self.VALUES_AS_TABLE 2169 2170 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2171 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2172 args = self.expressions(expression) 2173 alias = self.sql(expression, "alias") 2174 values = f"VALUES{self.seg('')}{args}" 2175 values = ( 2176 f"({values})" 2177 if self.WRAP_DERIVED_VALUES 2178 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2179 else values 2180 ) 2181 return f"{values} AS {alias}" if alias else values 2182 2183 # Converts `VALUES...` expression into a series of select unions. 2184 alias_node = expression.args.get("alias") 2185 column_names = alias_node and alias_node.columns 2186 2187 selects: t.List[exp.Query] = [] 2188 2189 for i, tup in enumerate(expression.expressions): 2190 row = tup.expressions 2191 2192 if i == 0 and column_names: 2193 row = [ 2194 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2195 ] 2196 2197 selects.append(exp.Select(expressions=row)) 2198 2199 if self.pretty: 2200 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2201 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2202 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2203 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2204 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2205 2206 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2207 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2208 return f"({unions}){alias}" 2209 2210 def var_sql(self, expression: exp.Var) -> str: 2211 return self.sql(expression, "this") 2212 2213 @unsupported_args("expressions") 2214 def into_sql(self, expression: exp.Into) -> str: 2215 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2216 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2217 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2218 2219 def from_sql(self, expression: exp.From) -> str: 2220 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2221 2222 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2223 grouping_sets = self.expressions(expression, indent=False) 2224 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2225 2226 def rollup_sql(self, expression: exp.Rollup) -> str: 2227 expressions = self.expressions(expression, indent=False) 2228 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2229 2230 def cube_sql(self, expression: exp.Cube) -> str: 2231 expressions = self.expressions(expression, indent=False) 2232 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2233 2234 def group_sql(self, expression: exp.Group) -> str: 2235 group_by_all = expression.args.get("all") 2236 if group_by_all is True: 2237 modifier = " ALL" 2238 elif group_by_all is False: 2239 modifier = " DISTINCT" 2240 else: 2241 modifier = "" 2242 2243 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2244 2245 grouping_sets = self.expressions(expression, key="grouping_sets") 2246 cube = self.expressions(expression, key="cube") 2247 rollup = self.expressions(expression, key="rollup") 2248 2249 groupings = csv( 2250 self.seg(grouping_sets) if grouping_sets else "", 2251 self.seg(cube) if cube else "", 2252 self.seg(rollup) if rollup else "", 2253 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2254 sep=self.GROUPINGS_SEP, 2255 ) 2256 2257 if ( 2258 expression.expressions 2259 and groupings 2260 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2261 ): 2262 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2263 2264 return f"{group_by}{groupings}" 2265 2266 def having_sql(self, expression: exp.Having) -> str: 2267 this = self.indent(self.sql(expression, "this")) 2268 return f"{self.seg('HAVING')}{self.sep()}{this}" 2269 2270 def connect_sql(self, expression: exp.Connect) -> str: 2271 start = self.sql(expression, "start") 2272 start = self.seg(f"START WITH {start}") if start else "" 2273 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2274 connect = self.sql(expression, "connect") 2275 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2276 return start + connect 2277 2278 def prior_sql(self, expression: exp.Prior) -> str: 2279 return f"PRIOR {self.sql(expression, 'this')}" 2280 2281 def join_sql(self, expression: exp.Join) -> str: 2282 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2283 side = None 2284 else: 2285 side = expression.side 2286 2287 op_sql = " ".join( 2288 op 2289 for op in ( 2290 expression.method, 2291 "GLOBAL" if expression.args.get("global") else None, 2292 side, 2293 expression.kind, 2294 expression.hint if self.JOIN_HINTS else None, 2295 ) 2296 if op 2297 ) 2298 match_cond = self.sql(expression, "match_condition") 2299 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2300 on_sql = self.sql(expression, "on") 2301 using = expression.args.get("using") 2302 2303 if not on_sql and using: 2304 on_sql = csv(*(self.sql(column) for column in using)) 2305 2306 this = expression.this 2307 this_sql = self.sql(this) 2308 2309 exprs = self.expressions(expression) 2310 if exprs: 2311 this_sql = f"{this_sql},{self.seg(exprs)}" 2312 2313 if on_sql: 2314 on_sql = self.indent(on_sql, skip_first=True) 2315 space = self.seg(" " * self.pad) if self.pretty else " " 2316 if using: 2317 on_sql = f"{space}USING ({on_sql})" 2318 else: 2319 on_sql = f"{space}ON {on_sql}" 2320 elif not op_sql: 2321 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2322 return f" {this_sql}" 2323 2324 return f", {this_sql}" 2325 2326 if op_sql != "STRAIGHT_JOIN": 2327 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2328 2329 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2330 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2331 2332 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2333 args = self.expressions(expression, flat=True) 2334 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2335 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2336 2337 def lateral_op(self, expression: exp.Lateral) -> str: 2338 cross_apply = expression.args.get("cross_apply") 2339 2340 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2341 if cross_apply is True: 2342 op = "INNER JOIN " 2343 elif cross_apply is False: 2344 op = "LEFT JOIN " 2345 else: 2346 op = "" 2347 2348 return f"{op}LATERAL" 2349 2350 def lateral_sql(self, expression: exp.Lateral) -> str: 2351 this = self.sql(expression, "this") 2352 2353 if expression.args.get("view"): 2354 alias = expression.args["alias"] 2355 columns = self.expressions(alias, key="columns", flat=True) 2356 table = f" {alias.name}" if alias.name else "" 2357 columns = f" AS {columns}" if columns else "" 2358 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2359 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2360 2361 alias = self.sql(expression, "alias") 2362 alias = f" AS {alias}" if alias else "" 2363 2364 ordinality = expression.args.get("ordinality") or "" 2365 if ordinality: 2366 ordinality = f" WITH ORDINALITY{alias}" 2367 alias = "" 2368 2369 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2370 2371 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2372 this = self.sql(expression, "this") 2373 2374 args = [ 2375 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2376 for e in (expression.args.get(k) for k in ("offset", "expression")) 2377 if e 2378 ] 2379 2380 args_sql = ", ".join(self.sql(e) for e in args) 2381 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2382 expressions = self.expressions(expression, flat=True) 2383 limit_options = self.sql(expression, "limit_options") 2384 expressions = f" BY {expressions}" if expressions else "" 2385 2386 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2387 2388 def offset_sql(self, expression: exp.Offset) -> str: 2389 this = self.sql(expression, "this") 2390 value = expression.expression 2391 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2392 expressions = self.expressions(expression, flat=True) 2393 expressions = f" BY {expressions}" if expressions else "" 2394 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2395 2396 def setitem_sql(self, expression: exp.SetItem) -> str: 2397 kind = self.sql(expression, "kind") 2398 kind = f"{kind} " if kind else "" 2399 this = self.sql(expression, "this") 2400 expressions = self.expressions(expression) 2401 collate = self.sql(expression, "collate") 2402 collate = f" COLLATE {collate}" if collate else "" 2403 global_ = "GLOBAL " if expression.args.get("global") else "" 2404 return f"{global_}{kind}{this}{expressions}{collate}" 2405 2406 def set_sql(self, expression: exp.Set) -> str: 2407 expressions = f" {self.expressions(expression, flat=True)}" 2408 tag = " TAG" if expression.args.get("tag") else "" 2409 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2410 2411 def queryband_sql(self, expression: exp.QueryBand) -> str: 2412 this = self.sql(expression, "this") 2413 update = " UPDATE" if expression.args.get("update") else "" 2414 scope = self.sql(expression, "scope") 2415 scope = f" FOR {scope}" if scope else "" 2416 2417 return f"QUERY_BAND = {this}{update}{scope}" 2418 2419 def pragma_sql(self, expression: exp.Pragma) -> str: 2420 return f"PRAGMA {self.sql(expression, 'this')}" 2421 2422 def lock_sql(self, expression: exp.Lock) -> str: 2423 if not self.LOCKING_READS_SUPPORTED: 2424 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2425 return "" 2426 2427 update = expression.args["update"] 2428 key = expression.args.get("key") 2429 if update: 2430 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2431 else: 2432 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2433 expressions = self.expressions(expression, flat=True) 2434 expressions = f" OF {expressions}" if expressions else "" 2435 wait = expression.args.get("wait") 2436 2437 if wait is not None: 2438 if isinstance(wait, exp.Literal): 2439 wait = f" WAIT {self.sql(wait)}" 2440 else: 2441 wait = " NOWAIT" if wait else " SKIP LOCKED" 2442 2443 return f"{lock_type}{expressions}{wait or ''}" 2444 2445 def literal_sql(self, expression: exp.Literal) -> str: 2446 text = expression.this or "" 2447 if expression.is_string: 2448 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2449 return text 2450 2451 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2452 if self.dialect.ESCAPED_SEQUENCES: 2453 to_escaped = self.dialect.ESCAPED_SEQUENCES 2454 text = "".join( 2455 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2456 ) 2457 2458 return self._replace_line_breaks(text).replace( 2459 self.dialect.QUOTE_END, self._escaped_quote_end 2460 ) 2461 2462 def loaddata_sql(self, expression: exp.LoadData) -> str: 2463 local = " LOCAL" if expression.args.get("local") else "" 2464 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2465 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2466 this = f" INTO TABLE {self.sql(expression, 'this')}" 2467 partition = self.sql(expression, "partition") 2468 partition = f" {partition}" if partition else "" 2469 input_format = self.sql(expression, "input_format") 2470 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2471 serde = self.sql(expression, "serde") 2472 serde = f" SERDE {serde}" if serde else "" 2473 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2474 2475 def null_sql(self, *_) -> str: 2476 return "NULL" 2477 2478 def boolean_sql(self, expression: exp.Boolean) -> str: 2479 return "TRUE" if expression.this else "FALSE" 2480 2481 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2482 this = self.sql(expression, "this") 2483 this = f"{this} " if this else this 2484 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2485 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2486 2487 def withfill_sql(self, expression: exp.WithFill) -> str: 2488 from_sql = self.sql(expression, "from") 2489 from_sql = f" FROM {from_sql}" if from_sql else "" 2490 to_sql = self.sql(expression, "to") 2491 to_sql = f" TO {to_sql}" if to_sql else "" 2492 step_sql = self.sql(expression, "step") 2493 step_sql = f" STEP {step_sql}" if step_sql else "" 2494 interpolated_values = [ 2495 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2496 if isinstance(e, exp.Alias) 2497 else self.sql(e, "this") 2498 for e in expression.args.get("interpolate") or [] 2499 ] 2500 interpolate = ( 2501 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2502 ) 2503 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2504 2505 def cluster_sql(self, expression: exp.Cluster) -> str: 2506 return self.op_expressions("CLUSTER BY", expression) 2507 2508 def distribute_sql(self, expression: exp.Distribute) -> str: 2509 return self.op_expressions("DISTRIBUTE BY", expression) 2510 2511 def sort_sql(self, expression: exp.Sort) -> str: 2512 return self.op_expressions("SORT BY", expression) 2513 2514 def ordered_sql(self, expression: exp.Ordered) -> str: 2515 desc = expression.args.get("desc") 2516 asc = not desc 2517 2518 nulls_first = expression.args.get("nulls_first") 2519 nulls_last = not nulls_first 2520 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2521 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2522 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2523 2524 this = self.sql(expression, "this") 2525 2526 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2527 nulls_sort_change = "" 2528 if nulls_first and ( 2529 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2530 ): 2531 nulls_sort_change = " NULLS FIRST" 2532 elif ( 2533 nulls_last 2534 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2535 and not nulls_are_last 2536 ): 2537 nulls_sort_change = " NULLS LAST" 2538 2539 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2540 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2541 window = expression.find_ancestor(exp.Window, exp.Select) 2542 if isinstance(window, exp.Window) and window.args.get("spec"): 2543 self.unsupported( 2544 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2545 ) 2546 nulls_sort_change = "" 2547 elif self.NULL_ORDERING_SUPPORTED is False and ( 2548 (asc and nulls_sort_change == " NULLS LAST") 2549 or (desc and nulls_sort_change == " NULLS FIRST") 2550 ): 2551 # BigQuery does not allow these ordering/nulls combinations when used under 2552 # an aggregation func or under a window containing one 2553 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2554 2555 if isinstance(ancestor, exp.Window): 2556 ancestor = ancestor.this 2557 if isinstance(ancestor, exp.AggFunc): 2558 self.unsupported( 2559 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2560 ) 2561 nulls_sort_change = "" 2562 elif self.NULL_ORDERING_SUPPORTED is None: 2563 if expression.this.is_int: 2564 self.unsupported( 2565 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2566 ) 2567 elif not isinstance(expression.this, exp.Rand): 2568 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2569 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2570 nulls_sort_change = "" 2571 2572 with_fill = self.sql(expression, "with_fill") 2573 with_fill = f" {with_fill}" if with_fill else "" 2574 2575 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2576 2577 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2578 window_frame = self.sql(expression, "window_frame") 2579 window_frame = f"{window_frame} " if window_frame else "" 2580 2581 this = self.sql(expression, "this") 2582 2583 return f"{window_frame}{this}" 2584 2585 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2586 partition = self.partition_by_sql(expression) 2587 order = self.sql(expression, "order") 2588 measures = self.expressions(expression, key="measures") 2589 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2590 rows = self.sql(expression, "rows") 2591 rows = self.seg(rows) if rows else "" 2592 after = self.sql(expression, "after") 2593 after = self.seg(after) if after else "" 2594 pattern = self.sql(expression, "pattern") 2595 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2596 definition_sqls = [ 2597 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2598 for definition in expression.args.get("define", []) 2599 ] 2600 definitions = self.expressions(sqls=definition_sqls) 2601 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2602 body = "".join( 2603 ( 2604 partition, 2605 order, 2606 measures, 2607 rows, 2608 after, 2609 pattern, 2610 define, 2611 ) 2612 ) 2613 alias = self.sql(expression, "alias") 2614 alias = f" {alias}" if alias else "" 2615 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2616 2617 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2618 limit = expression.args.get("limit") 2619 2620 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2621 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2622 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2623 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2624 2625 return csv( 2626 *sqls, 2627 *[self.sql(join) for join in expression.args.get("joins") or []], 2628 self.sql(expression, "match"), 2629 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2630 self.sql(expression, "prewhere"), 2631 self.sql(expression, "where"), 2632 self.sql(expression, "connect"), 2633 self.sql(expression, "group"), 2634 self.sql(expression, "having"), 2635 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2636 self.sql(expression, "order"), 2637 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2638 *self.after_limit_modifiers(expression), 2639 self.options_modifier(expression), 2640 self.for_modifiers(expression), 2641 sep="", 2642 ) 2643 2644 def options_modifier(self, expression: exp.Expression) -> str: 2645 options = self.expressions(expression, key="options") 2646 return f" {options}" if options else "" 2647 2648 def for_modifiers(self, expression: exp.Expression) -> str: 2649 for_modifiers = self.expressions(expression, key="for") 2650 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2651 2652 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2653 self.unsupported("Unsupported query option.") 2654 return "" 2655 2656 def offset_limit_modifiers( 2657 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2658 ) -> t.List[str]: 2659 return [ 2660 self.sql(expression, "offset") if fetch else self.sql(limit), 2661 self.sql(limit) if fetch else self.sql(expression, "offset"), 2662 ] 2663 2664 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2665 locks = self.expressions(expression, key="locks", sep=" ") 2666 locks = f" {locks}" if locks else "" 2667 return [locks, self.sql(expression, "sample")] 2668 2669 def select_sql(self, expression: exp.Select) -> str: 2670 into = expression.args.get("into") 2671 if not self.SUPPORTS_SELECT_INTO and into: 2672 into.pop() 2673 2674 hint = self.sql(expression, "hint") 2675 distinct = self.sql(expression, "distinct") 2676 distinct = f" {distinct}" if distinct else "" 2677 kind = self.sql(expression, "kind") 2678 2679 limit = expression.args.get("limit") 2680 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2681 top = self.limit_sql(limit, top=True) 2682 limit.pop() 2683 else: 2684 top = "" 2685 2686 expressions = self.expressions(expression) 2687 2688 if kind: 2689 if kind in self.SELECT_KINDS: 2690 kind = f" AS {kind}" 2691 else: 2692 if kind == "STRUCT": 2693 expressions = self.expressions( 2694 sqls=[ 2695 self.sql( 2696 exp.Struct( 2697 expressions=[ 2698 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2699 if isinstance(e, exp.Alias) 2700 else e 2701 for e in expression.expressions 2702 ] 2703 ) 2704 ) 2705 ] 2706 ) 2707 kind = "" 2708 2709 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2710 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2711 2712 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2713 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2714 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2715 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2716 sql = self.query_modifiers( 2717 expression, 2718 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2719 self.sql(expression, "into", comment=False), 2720 self.sql(expression, "from", comment=False), 2721 ) 2722 2723 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2724 if expression.args.get("with"): 2725 sql = self.maybe_comment(sql, expression) 2726 expression.pop_comments() 2727 2728 sql = self.prepend_ctes(expression, sql) 2729 2730 if not self.SUPPORTS_SELECT_INTO and into: 2731 if into.args.get("temporary"): 2732 table_kind = " TEMPORARY" 2733 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2734 table_kind = " UNLOGGED" 2735 else: 2736 table_kind = "" 2737 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2738 2739 return sql 2740 2741 def schema_sql(self, expression: exp.Schema) -> str: 2742 this = self.sql(expression, "this") 2743 sql = self.schema_columns_sql(expression) 2744 return f"{this} {sql}" if this and sql else this or sql 2745 2746 def schema_columns_sql(self, expression: exp.Schema) -> str: 2747 if expression.expressions: 2748 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2749 return "" 2750 2751 def star_sql(self, expression: exp.Star) -> str: 2752 except_ = self.expressions(expression, key="except", flat=True) 2753 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2754 replace = self.expressions(expression, key="replace", flat=True) 2755 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2756 rename = self.expressions(expression, key="rename", flat=True) 2757 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2758 return f"*{except_}{replace}{rename}" 2759 2760 def parameter_sql(self, expression: exp.Parameter) -> str: 2761 this = self.sql(expression, "this") 2762 return f"{self.PARAMETER_TOKEN}{this}" 2763 2764 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2765 this = self.sql(expression, "this") 2766 kind = expression.text("kind") 2767 if kind: 2768 kind = f"{kind}." 2769 return f"@@{kind}{this}" 2770 2771 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2772 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2773 2774 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2775 alias = self.sql(expression, "alias") 2776 alias = f"{sep}{alias}" if alias else "" 2777 sample = self.sql(expression, "sample") 2778 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2779 alias = f"{sample}{alias}" 2780 2781 # Set to None so it's not generated again by self.query_modifiers() 2782 expression.set("sample", None) 2783 2784 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2785 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2786 return self.prepend_ctes(expression, sql) 2787 2788 def qualify_sql(self, expression: exp.Qualify) -> str: 2789 this = self.indent(self.sql(expression, "this")) 2790 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2791 2792 def unnest_sql(self, expression: exp.Unnest) -> str: 2793 args = self.expressions(expression, flat=True) 2794 2795 alias = expression.args.get("alias") 2796 offset = expression.args.get("offset") 2797 2798 if self.UNNEST_WITH_ORDINALITY: 2799 if alias and isinstance(offset, exp.Expression): 2800 alias.append("columns", offset) 2801 2802 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2803 columns = alias.columns 2804 alias = self.sql(columns[0]) if columns else "" 2805 else: 2806 alias = self.sql(alias) 2807 2808 alias = f" AS {alias}" if alias else alias 2809 if self.UNNEST_WITH_ORDINALITY: 2810 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2811 else: 2812 if isinstance(offset, exp.Expression): 2813 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2814 elif offset: 2815 suffix = f"{alias} WITH OFFSET" 2816 else: 2817 suffix = alias 2818 2819 return f"UNNEST({args}){suffix}" 2820 2821 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2822 return "" 2823 2824 def where_sql(self, expression: exp.Where) -> str: 2825 this = self.indent(self.sql(expression, "this")) 2826 return f"{self.seg('WHERE')}{self.sep()}{this}" 2827 2828 def window_sql(self, expression: exp.Window) -> str: 2829 this = self.sql(expression, "this") 2830 partition = self.partition_by_sql(expression) 2831 order = expression.args.get("order") 2832 order = self.order_sql(order, flat=True) if order else "" 2833 spec = self.sql(expression, "spec") 2834 alias = self.sql(expression, "alias") 2835 over = self.sql(expression, "over") or "OVER" 2836 2837 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2838 2839 first = expression.args.get("first") 2840 if first is None: 2841 first = "" 2842 else: 2843 first = "FIRST" if first else "LAST" 2844 2845 if not partition and not order and not spec and alias: 2846 return f"{this} {alias}" 2847 2848 args = self.format_args( 2849 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2850 ) 2851 return f"{this} ({args})" 2852 2853 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2854 partition = self.expressions(expression, key="partition_by", flat=True) 2855 return f"PARTITION BY {partition}" if partition else "" 2856 2857 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2858 kind = self.sql(expression, "kind") 2859 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2860 end = ( 2861 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2862 or "CURRENT ROW" 2863 ) 2864 2865 window_spec = f"{kind} BETWEEN {start} AND {end}" 2866 2867 exclude = self.sql(expression, "exclude") 2868 if exclude: 2869 if self.SUPPORTS_WINDOW_EXCLUDE: 2870 window_spec += f" EXCLUDE {exclude}" 2871 else: 2872 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2873 2874 return window_spec 2875 2876 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2877 this = self.sql(expression, "this") 2878 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2879 return f"{this} WITHIN GROUP ({expression_sql})" 2880 2881 def between_sql(self, expression: exp.Between) -> str: 2882 this = self.sql(expression, "this") 2883 low = self.sql(expression, "low") 2884 high = self.sql(expression, "high") 2885 symmetric = expression.args.get("symmetric") 2886 2887 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2888 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2889 2890 flag = ( 2891 " SYMMETRIC" 2892 if symmetric 2893 else " ASYMMETRIC" 2894 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2895 else "" # silently drop ASYMMETRIC – semantics identical 2896 ) 2897 return f"{this} BETWEEN{flag} {low} AND {high}" 2898 2899 def bracket_offset_expressions( 2900 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2901 ) -> t.List[exp.Expression]: 2902 return apply_index_offset( 2903 expression.this, 2904 expression.expressions, 2905 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2906 dialect=self.dialect, 2907 ) 2908 2909 def bracket_sql(self, expression: exp.Bracket) -> str: 2910 expressions = self.bracket_offset_expressions(expression) 2911 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2912 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2913 2914 def all_sql(self, expression: exp.All) -> str: 2915 this = self.sql(expression, "this") 2916 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2917 this = self.wrap(this) 2918 return f"ALL {this}" 2919 2920 def any_sql(self, expression: exp.Any) -> str: 2921 this = self.sql(expression, "this") 2922 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2923 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2924 this = self.wrap(this) 2925 return f"ANY{this}" 2926 return f"ANY {this}" 2927 2928 def exists_sql(self, expression: exp.Exists) -> str: 2929 return f"EXISTS{self.wrap(expression)}" 2930 2931 def case_sql(self, expression: exp.Case) -> str: 2932 this = self.sql(expression, "this") 2933 statements = [f"CASE {this}" if this else "CASE"] 2934 2935 for e in expression.args["ifs"]: 2936 statements.append(f"WHEN {self.sql(e, 'this')}") 2937 statements.append(f"THEN {self.sql(e, 'true')}") 2938 2939 default = self.sql(expression, "default") 2940 2941 if default: 2942 statements.append(f"ELSE {default}") 2943 2944 statements.append("END") 2945 2946 if self.pretty and self.too_wide(statements): 2947 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2948 2949 return " ".join(statements) 2950 2951 def constraint_sql(self, expression: exp.Constraint) -> str: 2952 this = self.sql(expression, "this") 2953 expressions = self.expressions(expression, flat=True) 2954 return f"CONSTRAINT {this} {expressions}" 2955 2956 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2957 order = expression.args.get("order") 2958 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2959 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2960 2961 def extract_sql(self, expression: exp.Extract) -> str: 2962 from sqlglot.dialects.dialect import map_date_part 2963 2964 this = ( 2965 map_date_part(expression.this, self.dialect) 2966 if self.NORMALIZE_EXTRACT_DATE_PARTS 2967 else expression.this 2968 ) 2969 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2970 expression_sql = self.sql(expression, "expression") 2971 2972 return f"EXTRACT({this_sql} FROM {expression_sql})" 2973 2974 def trim_sql(self, expression: exp.Trim) -> str: 2975 trim_type = self.sql(expression, "position") 2976 2977 if trim_type == "LEADING": 2978 func_name = "LTRIM" 2979 elif trim_type == "TRAILING": 2980 func_name = "RTRIM" 2981 else: 2982 func_name = "TRIM" 2983 2984 return self.func(func_name, expression.this, expression.expression) 2985 2986 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2987 args = expression.expressions 2988 if isinstance(expression, exp.ConcatWs): 2989 args = args[1:] # Skip the delimiter 2990 2991 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2992 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2993 2994 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2995 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2996 2997 return args 2998 2999 def concat_sql(self, expression: exp.Concat) -> str: 3000 expressions = self.convert_concat_args(expression) 3001 3002 # Some dialects don't allow a single-argument CONCAT call 3003 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3004 return self.sql(expressions[0]) 3005 3006 return self.func("CONCAT", *expressions) 3007 3008 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3009 return self.func( 3010 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3011 ) 3012 3013 def check_sql(self, expression: exp.Check) -> str: 3014 this = self.sql(expression, key="this") 3015 return f"CHECK ({this})" 3016 3017 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3018 expressions = self.expressions(expression, flat=True) 3019 expressions = f" ({expressions})" if expressions else "" 3020 reference = self.sql(expression, "reference") 3021 reference = f" {reference}" if reference else "" 3022 delete = self.sql(expression, "delete") 3023 delete = f" ON DELETE {delete}" if delete else "" 3024 update = self.sql(expression, "update") 3025 update = f" ON UPDATE {update}" if update else "" 3026 options = self.expressions(expression, key="options", flat=True, sep=" ") 3027 options = f" {options}" if options else "" 3028 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3029 3030 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3031 expressions = self.expressions(expression, flat=True) 3032 include = self.sql(expression, "include") 3033 options = self.expressions(expression, key="options", flat=True, sep=" ") 3034 options = f" {options}" if options else "" 3035 return f"PRIMARY KEY ({expressions}){include}{options}" 3036 3037 def if_sql(self, expression: exp.If) -> str: 3038 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3039 3040 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3041 modifier = expression.args.get("modifier") 3042 modifier = f" {modifier}" if modifier else "" 3043 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3044 3045 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3046 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3047 3048 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3049 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3050 3051 if expression.args.get("escape"): 3052 path = self.escape_str(path) 3053 3054 if self.QUOTE_JSON_PATH: 3055 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3056 3057 return path 3058 3059 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3060 if isinstance(expression, exp.JSONPathPart): 3061 transform = self.TRANSFORMS.get(expression.__class__) 3062 if not callable(transform): 3063 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3064 return "" 3065 3066 return transform(self, expression) 3067 3068 if isinstance(expression, int): 3069 return str(expression) 3070 3071 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3072 escaped = expression.replace("'", "\\'") 3073 escaped = f"\\'{expression}\\'" 3074 else: 3075 escaped = expression.replace('"', '\\"') 3076 escaped = f'"{escaped}"' 3077 3078 return escaped 3079 3080 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3081 return f"{self.sql(expression, 'this')} FORMAT JSON" 3082 3083 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3084 # Output the Teradata column FORMAT override. 3085 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3086 this = self.sql(expression, "this") 3087 fmt = self.sql(expression, "format") 3088 return f"{this} (FORMAT {fmt})" 3089 3090 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3091 null_handling = expression.args.get("null_handling") 3092 null_handling = f" {null_handling}" if null_handling else "" 3093 3094 unique_keys = expression.args.get("unique_keys") 3095 if unique_keys is not None: 3096 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3097 else: 3098 unique_keys = "" 3099 3100 return_type = self.sql(expression, "return_type") 3101 return_type = f" RETURNING {return_type}" if return_type else "" 3102 encoding = self.sql(expression, "encoding") 3103 encoding = f" ENCODING {encoding}" if encoding else "" 3104 3105 return self.func( 3106 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3107 *expression.expressions, 3108 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3109 ) 3110 3111 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3112 return self.jsonobject_sql(expression) 3113 3114 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3115 null_handling = expression.args.get("null_handling") 3116 null_handling = f" {null_handling}" if null_handling else "" 3117 return_type = self.sql(expression, "return_type") 3118 return_type = f" RETURNING {return_type}" if return_type else "" 3119 strict = " STRICT" if expression.args.get("strict") else "" 3120 return self.func( 3121 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3122 ) 3123 3124 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3125 this = self.sql(expression, "this") 3126 order = self.sql(expression, "order") 3127 null_handling = expression.args.get("null_handling") 3128 null_handling = f" {null_handling}" if null_handling else "" 3129 return_type = self.sql(expression, "return_type") 3130 return_type = f" RETURNING {return_type}" if return_type else "" 3131 strict = " STRICT" if expression.args.get("strict") else "" 3132 return self.func( 3133 "JSON_ARRAYAGG", 3134 this, 3135 suffix=f"{order}{null_handling}{return_type}{strict})", 3136 ) 3137 3138 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3139 path = self.sql(expression, "path") 3140 path = f" PATH {path}" if path else "" 3141 nested_schema = self.sql(expression, "nested_schema") 3142 3143 if nested_schema: 3144 return f"NESTED{path} {nested_schema}" 3145 3146 this = self.sql(expression, "this") 3147 kind = self.sql(expression, "kind") 3148 kind = f" {kind}" if kind else "" 3149 return f"{this}{kind}{path}" 3150 3151 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3152 return self.func("COLUMNS", *expression.expressions) 3153 3154 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3155 this = self.sql(expression, "this") 3156 path = self.sql(expression, "path") 3157 path = f", {path}" if path else "" 3158 error_handling = expression.args.get("error_handling") 3159 error_handling = f" {error_handling}" if error_handling else "" 3160 empty_handling = expression.args.get("empty_handling") 3161 empty_handling = f" {empty_handling}" if empty_handling else "" 3162 schema = self.sql(expression, "schema") 3163 return self.func( 3164 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3165 ) 3166 3167 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3168 this = self.sql(expression, "this") 3169 kind = self.sql(expression, "kind") 3170 path = self.sql(expression, "path") 3171 path = f" {path}" if path else "" 3172 as_json = " AS JSON" if expression.args.get("as_json") else "" 3173 return f"{this} {kind}{path}{as_json}" 3174 3175 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3176 this = self.sql(expression, "this") 3177 path = self.sql(expression, "path") 3178 path = f", {path}" if path else "" 3179 expressions = self.expressions(expression) 3180 with_ = ( 3181 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3182 if expressions 3183 else "" 3184 ) 3185 return f"OPENJSON({this}{path}){with_}" 3186 3187 def in_sql(self, expression: exp.In) -> str: 3188 query = expression.args.get("query") 3189 unnest = expression.args.get("unnest") 3190 field = expression.args.get("field") 3191 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3192 3193 if query: 3194 in_sql = self.sql(query) 3195 elif unnest: 3196 in_sql = self.in_unnest_op(unnest) 3197 elif field: 3198 in_sql = self.sql(field) 3199 else: 3200 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3201 3202 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3203 3204 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3205 return f"(SELECT {self.sql(unnest)})" 3206 3207 def interval_sql(self, expression: exp.Interval) -> str: 3208 unit = self.sql(expression, "unit") 3209 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3210 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3211 unit = f" {unit}" if unit else "" 3212 3213 if self.SINGLE_STRING_INTERVAL: 3214 this = expression.this.name if expression.this else "" 3215 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3216 3217 this = self.sql(expression, "this") 3218 if this: 3219 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3220 this = f" {this}" if unwrapped else f" ({this})" 3221 3222 return f"INTERVAL{this}{unit}" 3223 3224 def return_sql(self, expression: exp.Return) -> str: 3225 return f"RETURN {self.sql(expression, 'this')}" 3226 3227 def reference_sql(self, expression: exp.Reference) -> str: 3228 this = self.sql(expression, "this") 3229 expressions = self.expressions(expression, flat=True) 3230 expressions = f"({expressions})" if expressions else "" 3231 options = self.expressions(expression, key="options", flat=True, sep=" ") 3232 options = f" {options}" if options else "" 3233 return f"REFERENCES {this}{expressions}{options}" 3234 3235 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3236 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3237 parent = expression.parent 3238 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3239 return self.func( 3240 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3241 ) 3242 3243 def paren_sql(self, expression: exp.Paren) -> str: 3244 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3245 return f"({sql}{self.seg(')', sep='')}" 3246 3247 def neg_sql(self, expression: exp.Neg) -> str: 3248 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3249 this_sql = self.sql(expression, "this") 3250 sep = " " if this_sql[0] == "-" else "" 3251 return f"-{sep}{this_sql}" 3252 3253 def not_sql(self, expression: exp.Not) -> str: 3254 return f"NOT {self.sql(expression, 'this')}" 3255 3256 def alias_sql(self, expression: exp.Alias) -> str: 3257 alias = self.sql(expression, "alias") 3258 alias = f" AS {alias}" if alias else "" 3259 return f"{self.sql(expression, 'this')}{alias}" 3260 3261 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3262 alias = expression.args["alias"] 3263 3264 parent = expression.parent 3265 pivot = parent and parent.parent 3266 3267 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3268 identifier_alias = isinstance(alias, exp.Identifier) 3269 literal_alias = isinstance(alias, exp.Literal) 3270 3271 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3272 alias.replace(exp.Literal.string(alias.output_name)) 3273 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3274 alias.replace(exp.to_identifier(alias.output_name)) 3275 3276 return self.alias_sql(expression) 3277 3278 def aliases_sql(self, expression: exp.Aliases) -> str: 3279 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3280 3281 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3282 this = self.sql(expression, "this") 3283 index = self.sql(expression, "expression") 3284 return f"{this} AT {index}" 3285 3286 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3287 this = self.sql(expression, "this") 3288 zone = self.sql(expression, "zone") 3289 return f"{this} AT TIME ZONE {zone}" 3290 3291 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3292 this = self.sql(expression, "this") 3293 zone = self.sql(expression, "zone") 3294 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3295 3296 def add_sql(self, expression: exp.Add) -> str: 3297 return self.binary(expression, "+") 3298 3299 def and_sql( 3300 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3301 ) -> str: 3302 return self.connector_sql(expression, "AND", stack) 3303 3304 def or_sql( 3305 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3306 ) -> str: 3307 return self.connector_sql(expression, "OR", stack) 3308 3309 def xor_sql( 3310 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3311 ) -> str: 3312 return self.connector_sql(expression, "XOR", stack) 3313 3314 def connector_sql( 3315 self, 3316 expression: exp.Connector, 3317 op: str, 3318 stack: t.Optional[t.List[str | exp.Expression]] = None, 3319 ) -> str: 3320 if stack is not None: 3321 if expression.expressions: 3322 stack.append(self.expressions(expression, sep=f" {op} ")) 3323 else: 3324 stack.append(expression.right) 3325 if expression.comments and self.comments: 3326 for comment in expression.comments: 3327 if comment: 3328 op += f" /*{self.sanitize_comment(comment)}*/" 3329 stack.extend((op, expression.left)) 3330 return op 3331 3332 stack = [expression] 3333 sqls: t.List[str] = [] 3334 ops = set() 3335 3336 while stack: 3337 node = stack.pop() 3338 if isinstance(node, exp.Connector): 3339 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3340 else: 3341 sql = self.sql(node) 3342 if sqls and sqls[-1] in ops: 3343 sqls[-1] += f" {sql}" 3344 else: 3345 sqls.append(sql) 3346 3347 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3348 return sep.join(sqls) 3349 3350 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3351 return self.binary(expression, "&") 3352 3353 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3354 return self.binary(expression, "<<") 3355 3356 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3357 return f"~{self.sql(expression, 'this')}" 3358 3359 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3360 return self.binary(expression, "|") 3361 3362 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3363 return self.binary(expression, ">>") 3364 3365 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3366 return self.binary(expression, "^") 3367 3368 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3369 format_sql = self.sql(expression, "format") 3370 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3371 to_sql = self.sql(expression, "to") 3372 to_sql = f" {to_sql}" if to_sql else "" 3373 action = self.sql(expression, "action") 3374 action = f" {action}" if action else "" 3375 default = self.sql(expression, "default") 3376 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3377 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3378 3379 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3380 zone = self.sql(expression, "this") 3381 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3382 3383 def collate_sql(self, expression: exp.Collate) -> str: 3384 if self.COLLATE_IS_FUNC: 3385 return self.function_fallback_sql(expression) 3386 return self.binary(expression, "COLLATE") 3387 3388 def command_sql(self, expression: exp.Command) -> str: 3389 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3390 3391 def comment_sql(self, expression: exp.Comment) -> str: 3392 this = self.sql(expression, "this") 3393 kind = expression.args["kind"] 3394 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3395 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3396 expression_sql = self.sql(expression, "expression") 3397 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3398 3399 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3400 this = self.sql(expression, "this") 3401 delete = " DELETE" if expression.args.get("delete") else "" 3402 recompress = self.sql(expression, "recompress") 3403 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3404 to_disk = self.sql(expression, "to_disk") 3405 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3406 to_volume = self.sql(expression, "to_volume") 3407 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3408 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3409 3410 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3411 where = self.sql(expression, "where") 3412 group = self.sql(expression, "group") 3413 aggregates = self.expressions(expression, key="aggregates") 3414 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3415 3416 if not (where or group or aggregates) and len(expression.expressions) == 1: 3417 return f"TTL {self.expressions(expression, flat=True)}" 3418 3419 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3420 3421 def transaction_sql(self, expression: exp.Transaction) -> str: 3422 return "BEGIN" 3423 3424 def commit_sql(self, expression: exp.Commit) -> str: 3425 chain = expression.args.get("chain") 3426 if chain is not None: 3427 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3428 3429 return f"COMMIT{chain or ''}" 3430 3431 def rollback_sql(self, expression: exp.Rollback) -> str: 3432 savepoint = expression.args.get("savepoint") 3433 savepoint = f" TO {savepoint}" if savepoint else "" 3434 return f"ROLLBACK{savepoint}" 3435 3436 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3437 this = self.sql(expression, "this") 3438 3439 dtype = self.sql(expression, "dtype") 3440 if dtype: 3441 collate = self.sql(expression, "collate") 3442 collate = f" COLLATE {collate}" if collate else "" 3443 using = self.sql(expression, "using") 3444 using = f" USING {using}" if using else "" 3445 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3446 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3447 3448 default = self.sql(expression, "default") 3449 if default: 3450 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3451 3452 comment = self.sql(expression, "comment") 3453 if comment: 3454 return f"ALTER COLUMN {this} COMMENT {comment}" 3455 3456 visible = expression.args.get("visible") 3457 if visible: 3458 return f"ALTER COLUMN {this} SET {visible}" 3459 3460 allow_null = expression.args.get("allow_null") 3461 drop = expression.args.get("drop") 3462 3463 if not drop and not allow_null: 3464 self.unsupported("Unsupported ALTER COLUMN syntax") 3465 3466 if allow_null is not None: 3467 keyword = "DROP" if drop else "SET" 3468 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3469 3470 return f"ALTER COLUMN {this} DROP DEFAULT" 3471 3472 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3473 this = self.sql(expression, "this") 3474 3475 visible = expression.args.get("visible") 3476 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3477 3478 return f"ALTER INDEX {this} {visible_sql}" 3479 3480 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3481 this = self.sql(expression, "this") 3482 if not isinstance(expression.this, exp.Var): 3483 this = f"KEY DISTKEY {this}" 3484 return f"ALTER DISTSTYLE {this}" 3485 3486 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3487 compound = " COMPOUND" if expression.args.get("compound") else "" 3488 this = self.sql(expression, "this") 3489 expressions = self.expressions(expression, flat=True) 3490 expressions = f"({expressions})" if expressions else "" 3491 return f"ALTER{compound} SORTKEY {this or expressions}" 3492 3493 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3494 if not self.RENAME_TABLE_WITH_DB: 3495 # Remove db from tables 3496 expression = expression.transform( 3497 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3498 ).assert_is(exp.AlterRename) 3499 this = self.sql(expression, "this") 3500 to_kw = " TO" if include_to else "" 3501 return f"RENAME{to_kw} {this}" 3502 3503 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3504 exists = " IF EXISTS" if expression.args.get("exists") else "" 3505 old_column = self.sql(expression, "this") 3506 new_column = self.sql(expression, "to") 3507 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3508 3509 def alterset_sql(self, expression: exp.AlterSet) -> str: 3510 exprs = self.expressions(expression, flat=True) 3511 if self.ALTER_SET_WRAPPED: 3512 exprs = f"({exprs})" 3513 3514 return f"SET {exprs}" 3515 3516 def alter_sql(self, expression: exp.Alter) -> str: 3517 actions = expression.args["actions"] 3518 3519 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3520 actions[0], exp.ColumnDef 3521 ): 3522 actions_sql = self.expressions(expression, key="actions", flat=True) 3523 actions_sql = f"ADD {actions_sql}" 3524 else: 3525 actions_list = [] 3526 for action in actions: 3527 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3528 action_sql = self.add_column_sql(action) 3529 else: 3530 action_sql = self.sql(action) 3531 if isinstance(action, exp.Query): 3532 action_sql = f"AS {action_sql}" 3533 3534 actions_list.append(action_sql) 3535 3536 actions_sql = self.format_args(*actions_list).lstrip("\n") 3537 3538 exists = " IF EXISTS" if expression.args.get("exists") else "" 3539 on_cluster = self.sql(expression, "cluster") 3540 on_cluster = f" {on_cluster}" if on_cluster else "" 3541 only = " ONLY" if expression.args.get("only") else "" 3542 options = self.expressions(expression, key="options") 3543 options = f", {options}" if options else "" 3544 kind = self.sql(expression, "kind") 3545 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3546 3547 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}" 3548 3549 def add_column_sql(self, expression: exp.Expression) -> str: 3550 sql = self.sql(expression) 3551 if isinstance(expression, exp.Schema): 3552 column_text = " COLUMNS" 3553 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3554 column_text = " COLUMN" 3555 else: 3556 column_text = "" 3557 3558 return f"ADD{column_text} {sql}" 3559 3560 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3561 expressions = self.expressions(expression) 3562 exists = " IF EXISTS " if expression.args.get("exists") else " " 3563 return f"DROP{exists}{expressions}" 3564 3565 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3566 return f"ADD {self.expressions(expression, indent=False)}" 3567 3568 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3569 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3570 location = self.sql(expression, "location") 3571 location = f" {location}" if location else "" 3572 return f"ADD {exists}{self.sql(expression.this)}{location}" 3573 3574 def distinct_sql(self, expression: exp.Distinct) -> str: 3575 this = self.expressions(expression, flat=True) 3576 3577 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3578 case = exp.case() 3579 for arg in expression.expressions: 3580 case = case.when(arg.is_(exp.null()), exp.null()) 3581 this = self.sql(case.else_(f"({this})")) 3582 3583 this = f" {this}" if this else "" 3584 3585 on = self.sql(expression, "on") 3586 on = f" ON {on}" if on else "" 3587 return f"DISTINCT{this}{on}" 3588 3589 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3590 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3591 3592 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3593 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3594 3595 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3596 this_sql = self.sql(expression, "this") 3597 expression_sql = self.sql(expression, "expression") 3598 kind = "MAX" if expression.args.get("max") else "MIN" 3599 return f"{this_sql} HAVING {kind} {expression_sql}" 3600 3601 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3602 return self.sql( 3603 exp.Cast( 3604 this=exp.Div(this=expression.this, expression=expression.expression), 3605 to=exp.DataType(this=exp.DataType.Type.INT), 3606 ) 3607 ) 3608 3609 def dpipe_sql(self, expression: exp.DPipe) -> str: 3610 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3611 return self.func( 3612 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3613 ) 3614 return self.binary(expression, "||") 3615 3616 def div_sql(self, expression: exp.Div) -> str: 3617 l, r = expression.left, expression.right 3618 3619 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3620 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3621 3622 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3623 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3624 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3625 3626 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3627 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3628 return self.sql( 3629 exp.cast( 3630 l / r, 3631 to=exp.DataType.Type.BIGINT, 3632 ) 3633 ) 3634 3635 return self.binary(expression, "/") 3636 3637 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3638 n = exp._wrap(expression.this, exp.Binary) 3639 d = exp._wrap(expression.expression, exp.Binary) 3640 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3641 3642 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3643 return self.binary(expression, "OVERLAPS") 3644 3645 def distance_sql(self, expression: exp.Distance) -> str: 3646 return self.binary(expression, "<->") 3647 3648 def dot_sql(self, expression: exp.Dot) -> str: 3649 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3650 3651 def eq_sql(self, expression: exp.EQ) -> str: 3652 return self.binary(expression, "=") 3653 3654 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3655 return self.binary(expression, ":=") 3656 3657 def escape_sql(self, expression: exp.Escape) -> str: 3658 return self.binary(expression, "ESCAPE") 3659 3660 def glob_sql(self, expression: exp.Glob) -> str: 3661 return self.binary(expression, "GLOB") 3662 3663 def gt_sql(self, expression: exp.GT) -> str: 3664 return self.binary(expression, ">") 3665 3666 def gte_sql(self, expression: exp.GTE) -> str: 3667 return self.binary(expression, ">=") 3668 3669 def is_sql(self, expression: exp.Is) -> str: 3670 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3671 return self.sql( 3672 expression.this if expression.expression.this else exp.not_(expression.this) 3673 ) 3674 return self.binary(expression, "IS") 3675 3676 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3677 this = expression.this 3678 rhs = expression.expression 3679 3680 if isinstance(expression, exp.Like): 3681 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3682 op = "LIKE" 3683 else: 3684 exp_class = exp.ILike 3685 op = "ILIKE" 3686 3687 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3688 exprs = rhs.this.unnest() 3689 3690 if isinstance(exprs, exp.Tuple): 3691 exprs = exprs.expressions 3692 3693 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3694 3695 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3696 for expr in exprs[1:]: 3697 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3698 3699 return self.sql(like_expr) 3700 3701 return self.binary(expression, op) 3702 3703 def like_sql(self, expression: exp.Like) -> str: 3704 return self._like_sql(expression) 3705 3706 def ilike_sql(self, expression: exp.ILike) -> str: 3707 return self._like_sql(expression) 3708 3709 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3710 return self.binary(expression, "SIMILAR TO") 3711 3712 def lt_sql(self, expression: exp.LT) -> str: 3713 return self.binary(expression, "<") 3714 3715 def lte_sql(self, expression: exp.LTE) -> str: 3716 return self.binary(expression, "<=") 3717 3718 def mod_sql(self, expression: exp.Mod) -> str: 3719 return self.binary(expression, "%") 3720 3721 def mul_sql(self, expression: exp.Mul) -> str: 3722 return self.binary(expression, "*") 3723 3724 def neq_sql(self, expression: exp.NEQ) -> str: 3725 return self.binary(expression, "<>") 3726 3727 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3728 return self.binary(expression, "IS NOT DISTINCT FROM") 3729 3730 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3731 return self.binary(expression, "IS DISTINCT FROM") 3732 3733 def slice_sql(self, expression: exp.Slice) -> str: 3734 return self.binary(expression, ":") 3735 3736 def sub_sql(self, expression: exp.Sub) -> str: 3737 return self.binary(expression, "-") 3738 3739 def trycast_sql(self, expression: exp.TryCast) -> str: 3740 return self.cast_sql(expression, safe_prefix="TRY_") 3741 3742 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3743 return self.cast_sql(expression) 3744 3745 def try_sql(self, expression: exp.Try) -> str: 3746 if not self.TRY_SUPPORTED: 3747 self.unsupported("Unsupported TRY function") 3748 return self.sql(expression, "this") 3749 3750 return self.func("TRY", expression.this) 3751 3752 def log_sql(self, expression: exp.Log) -> str: 3753 this = expression.this 3754 expr = expression.expression 3755 3756 if self.dialect.LOG_BASE_FIRST is False: 3757 this, expr = expr, this 3758 elif self.dialect.LOG_BASE_FIRST is None and expr: 3759 if this.name in ("2", "10"): 3760 return self.func(f"LOG{this.name}", expr) 3761 3762 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3763 3764 return self.func("LOG", this, expr) 3765 3766 def use_sql(self, expression: exp.Use) -> str: 3767 kind = self.sql(expression, "kind") 3768 kind = f" {kind}" if kind else "" 3769 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3770 this = f" {this}" if this else "" 3771 return f"USE{kind}{this}" 3772 3773 def binary(self, expression: exp.Binary, op: str) -> str: 3774 sqls: t.List[str] = [] 3775 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3776 binary_type = type(expression) 3777 3778 while stack: 3779 node = stack.pop() 3780 3781 if type(node) is binary_type: 3782 op_func = node.args.get("operator") 3783 if op_func: 3784 op = f"OPERATOR({self.sql(op_func)})" 3785 3786 stack.append(node.right) 3787 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3788 stack.append(node.left) 3789 else: 3790 sqls.append(self.sql(node)) 3791 3792 return "".join(sqls) 3793 3794 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3795 to_clause = self.sql(expression, "to") 3796 if to_clause: 3797 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3798 3799 return self.function_fallback_sql(expression) 3800 3801 def function_fallback_sql(self, expression: exp.Func) -> str: 3802 args = [] 3803 3804 for key in expression.arg_types: 3805 arg_value = expression.args.get(key) 3806 3807 if isinstance(arg_value, list): 3808 for value in arg_value: 3809 args.append(value) 3810 elif arg_value is not None: 3811 args.append(arg_value) 3812 3813 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3814 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3815 else: 3816 name = expression.sql_name() 3817 3818 return self.func(name, *args) 3819 3820 def func( 3821 self, 3822 name: str, 3823 *args: t.Optional[exp.Expression | str], 3824 prefix: str = "(", 3825 suffix: str = ")", 3826 normalize: bool = True, 3827 ) -> str: 3828 name = self.normalize_func(name) if normalize else name 3829 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3830 3831 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3832 arg_sqls = tuple( 3833 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3834 ) 3835 if self.pretty and self.too_wide(arg_sqls): 3836 return self.indent( 3837 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3838 ) 3839 return sep.join(arg_sqls) 3840 3841 def too_wide(self, args: t.Iterable) -> bool: 3842 return sum(len(arg) for arg in args) > self.max_text_width 3843 3844 def format_time( 3845 self, 3846 expression: exp.Expression, 3847 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3848 inverse_time_trie: t.Optional[t.Dict] = None, 3849 ) -> t.Optional[str]: 3850 return format_time( 3851 self.sql(expression, "format"), 3852 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3853 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3854 ) 3855 3856 def expressions( 3857 self, 3858 expression: t.Optional[exp.Expression] = None, 3859 key: t.Optional[str] = None, 3860 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3861 flat: bool = False, 3862 indent: bool = True, 3863 skip_first: bool = False, 3864 skip_last: bool = False, 3865 sep: str = ", ", 3866 prefix: str = "", 3867 dynamic: bool = False, 3868 new_line: bool = False, 3869 ) -> str: 3870 expressions = expression.args.get(key or "expressions") if expression else sqls 3871 3872 if not expressions: 3873 return "" 3874 3875 if flat: 3876 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3877 3878 num_sqls = len(expressions) 3879 result_sqls = [] 3880 3881 for i, e in enumerate(expressions): 3882 sql = self.sql(e, comment=False) 3883 if not sql: 3884 continue 3885 3886 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3887 3888 if self.pretty: 3889 if self.leading_comma: 3890 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3891 else: 3892 result_sqls.append( 3893 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3894 ) 3895 else: 3896 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3897 3898 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3899 if new_line: 3900 result_sqls.insert(0, "") 3901 result_sqls.append("") 3902 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3903 else: 3904 result_sql = "".join(result_sqls) 3905 3906 return ( 3907 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3908 if indent 3909 else result_sql 3910 ) 3911 3912 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3913 flat = flat or isinstance(expression.parent, exp.Properties) 3914 expressions_sql = self.expressions(expression, flat=flat) 3915 if flat: 3916 return f"{op} {expressions_sql}" 3917 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3918 3919 def naked_property(self, expression: exp.Property) -> str: 3920 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3921 if not property_name: 3922 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3923 return f"{property_name} {self.sql(expression, 'this')}" 3924 3925 def tag_sql(self, expression: exp.Tag) -> str: 3926 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3927 3928 def token_sql(self, token_type: TokenType) -> str: 3929 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3930 3931 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3932 this = self.sql(expression, "this") 3933 expressions = self.no_identify(self.expressions, expression) 3934 expressions = ( 3935 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3936 ) 3937 return f"{this}{expressions}" if expressions.strip() != "" else this 3938 3939 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3940 this = self.sql(expression, "this") 3941 expressions = self.expressions(expression, flat=True) 3942 return f"{this}({expressions})" 3943 3944 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3945 return self.binary(expression, "=>") 3946 3947 def when_sql(self, expression: exp.When) -> str: 3948 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3949 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3950 condition = self.sql(expression, "condition") 3951 condition = f" AND {condition}" if condition else "" 3952 3953 then_expression = expression.args.get("then") 3954 if isinstance(then_expression, exp.Insert): 3955 this = self.sql(then_expression, "this") 3956 this = f"INSERT {this}" if this else "INSERT" 3957 then = self.sql(then_expression, "expression") 3958 then = f"{this} VALUES {then}" if then else this 3959 elif isinstance(then_expression, exp.Update): 3960 if isinstance(then_expression.args.get("expressions"), exp.Star): 3961 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3962 else: 3963 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3964 else: 3965 then = self.sql(then_expression) 3966 return f"WHEN {matched}{source}{condition} THEN {then}" 3967 3968 def whens_sql(self, expression: exp.Whens) -> str: 3969 return self.expressions(expression, sep=" ", indent=False) 3970 3971 def merge_sql(self, expression: exp.Merge) -> str: 3972 table = expression.this 3973 table_alias = "" 3974 3975 hints = table.args.get("hints") 3976 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3977 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3978 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3979 3980 this = self.sql(table) 3981 using = f"USING {self.sql(expression, 'using')}" 3982 on = f"ON {self.sql(expression, 'on')}" 3983 whens = self.sql(expression, "whens") 3984 3985 returning = self.sql(expression, "returning") 3986 if returning: 3987 whens = f"{whens}{returning}" 3988 3989 sep = self.sep() 3990 3991 return self.prepend_ctes( 3992 expression, 3993 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3994 ) 3995 3996 @unsupported_args("format") 3997 def tochar_sql(self, expression: exp.ToChar) -> str: 3998 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3999 4000 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4001 if not self.SUPPORTS_TO_NUMBER: 4002 self.unsupported("Unsupported TO_NUMBER function") 4003 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4004 4005 fmt = expression.args.get("format") 4006 if not fmt: 4007 self.unsupported("Conversion format is required for TO_NUMBER") 4008 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4009 4010 return self.func("TO_NUMBER", expression.this, fmt) 4011 4012 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4013 this = self.sql(expression, "this") 4014 kind = self.sql(expression, "kind") 4015 settings_sql = self.expressions(expression, key="settings", sep=" ") 4016 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4017 return f"{this}({kind}{args})" 4018 4019 def dictrange_sql(self, expression: exp.DictRange) -> str: 4020 this = self.sql(expression, "this") 4021 max = self.sql(expression, "max") 4022 min = self.sql(expression, "min") 4023 return f"{this}(MIN {min} MAX {max})" 4024 4025 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4026 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4027 4028 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4029 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4030 4031 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4032 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 4033 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 4034 4035 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4036 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4037 expressions = self.expressions(expression, flat=True) 4038 expressions = f" {self.wrap(expressions)}" if expressions else "" 4039 buckets = self.sql(expression, "buckets") 4040 kind = self.sql(expression, "kind") 4041 buckets = f" BUCKETS {buckets}" if buckets else "" 4042 order = self.sql(expression, "order") 4043 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4044 4045 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4046 return "" 4047 4048 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4049 expressions = self.expressions(expression, key="expressions", flat=True) 4050 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4051 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4052 buckets = self.sql(expression, "buckets") 4053 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4054 4055 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4056 this = self.sql(expression, "this") 4057 having = self.sql(expression, "having") 4058 4059 if having: 4060 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4061 4062 return self.func("ANY_VALUE", this) 4063 4064 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4065 transform = self.func("TRANSFORM", *expression.expressions) 4066 row_format_before = self.sql(expression, "row_format_before") 4067 row_format_before = f" {row_format_before}" if row_format_before else "" 4068 record_writer = self.sql(expression, "record_writer") 4069 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4070 using = f" USING {self.sql(expression, 'command_script')}" 4071 schema = self.sql(expression, "schema") 4072 schema = f" AS {schema}" if schema else "" 4073 row_format_after = self.sql(expression, "row_format_after") 4074 row_format_after = f" {row_format_after}" if row_format_after else "" 4075 record_reader = self.sql(expression, "record_reader") 4076 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4077 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4078 4079 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4080 key_block_size = self.sql(expression, "key_block_size") 4081 if key_block_size: 4082 return f"KEY_BLOCK_SIZE = {key_block_size}" 4083 4084 using = self.sql(expression, "using") 4085 if using: 4086 return f"USING {using}" 4087 4088 parser = self.sql(expression, "parser") 4089 if parser: 4090 return f"WITH PARSER {parser}" 4091 4092 comment = self.sql(expression, "comment") 4093 if comment: 4094 return f"COMMENT {comment}" 4095 4096 visible = expression.args.get("visible") 4097 if visible is not None: 4098 return "VISIBLE" if visible else "INVISIBLE" 4099 4100 engine_attr = self.sql(expression, "engine_attr") 4101 if engine_attr: 4102 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4103 4104 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4105 if secondary_engine_attr: 4106 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4107 4108 self.unsupported("Unsupported index constraint option.") 4109 return "" 4110 4111 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4112 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4113 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4114 4115 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4116 kind = self.sql(expression, "kind") 4117 kind = f"{kind} INDEX" if kind else "INDEX" 4118 this = self.sql(expression, "this") 4119 this = f" {this}" if this else "" 4120 index_type = self.sql(expression, "index_type") 4121 index_type = f" USING {index_type}" if index_type else "" 4122 expressions = self.expressions(expression, flat=True) 4123 expressions = f" ({expressions})" if expressions else "" 4124 options = self.expressions(expression, key="options", sep=" ") 4125 options = f" {options}" if options else "" 4126 return f"{kind}{this}{index_type}{expressions}{options}" 4127 4128 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4129 if self.NVL2_SUPPORTED: 4130 return self.function_fallback_sql(expression) 4131 4132 case = exp.Case().when( 4133 expression.this.is_(exp.null()).not_(copy=False), 4134 expression.args["true"], 4135 copy=False, 4136 ) 4137 else_cond = expression.args.get("false") 4138 if else_cond: 4139 case.else_(else_cond, copy=False) 4140 4141 return self.sql(case) 4142 4143 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4144 this = self.sql(expression, "this") 4145 expr = self.sql(expression, "expression") 4146 iterator = self.sql(expression, "iterator") 4147 condition = self.sql(expression, "condition") 4148 condition = f" IF {condition}" if condition else "" 4149 return f"{this} FOR {expr} IN {iterator}{condition}" 4150 4151 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4152 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4153 4154 def opclass_sql(self, expression: exp.Opclass) -> str: 4155 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4156 4157 def predict_sql(self, expression: exp.Predict) -> str: 4158 model = self.sql(expression, "this") 4159 model = f"MODEL {model}" 4160 table = self.sql(expression, "expression") 4161 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4162 parameters = self.sql(expression, "params_struct") 4163 return self.func("PREDICT", model, table, parameters or None) 4164 4165 def forin_sql(self, expression: exp.ForIn) -> str: 4166 this = self.sql(expression, "this") 4167 expression_sql = self.sql(expression, "expression") 4168 return f"FOR {this} DO {expression_sql}" 4169 4170 def refresh_sql(self, expression: exp.Refresh) -> str: 4171 this = self.sql(expression, "this") 4172 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4173 return f"REFRESH {table}{this}" 4174 4175 def toarray_sql(self, expression: exp.ToArray) -> str: 4176 arg = expression.this 4177 if not arg.type: 4178 from sqlglot.optimizer.annotate_types import annotate_types 4179 4180 arg = annotate_types(arg, dialect=self.dialect) 4181 4182 if arg.is_type(exp.DataType.Type.ARRAY): 4183 return self.sql(arg) 4184 4185 cond_for_null = arg.is_(exp.null()) 4186 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4187 4188 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4189 this = expression.this 4190 time_format = self.format_time(expression) 4191 4192 if time_format: 4193 return self.sql( 4194 exp.cast( 4195 exp.StrToTime(this=this, format=expression.args["format"]), 4196 exp.DataType.Type.TIME, 4197 ) 4198 ) 4199 4200 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4201 return self.sql(this) 4202 4203 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4204 4205 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4206 this = expression.this 4207 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4208 return self.sql(this) 4209 4210 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4211 4212 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4213 this = expression.this 4214 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4215 return self.sql(this) 4216 4217 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4218 4219 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4220 this = expression.this 4221 time_format = self.format_time(expression) 4222 4223 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4224 return self.sql( 4225 exp.cast( 4226 exp.StrToTime(this=this, format=expression.args["format"]), 4227 exp.DataType.Type.DATE, 4228 ) 4229 ) 4230 4231 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4232 return self.sql(this) 4233 4234 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4235 4236 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4237 return self.sql( 4238 exp.func( 4239 "DATEDIFF", 4240 expression.this, 4241 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4242 "day", 4243 ) 4244 ) 4245 4246 def lastday_sql(self, expression: exp.LastDay) -> str: 4247 if self.LAST_DAY_SUPPORTS_DATE_PART: 4248 return self.function_fallback_sql(expression) 4249 4250 unit = expression.text("unit") 4251 if unit and unit != "MONTH": 4252 self.unsupported("Date parts are not supported in LAST_DAY.") 4253 4254 return self.func("LAST_DAY", expression.this) 4255 4256 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4257 from sqlglot.dialects.dialect import unit_to_str 4258 4259 return self.func( 4260 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4261 ) 4262 4263 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4264 if self.CAN_IMPLEMENT_ARRAY_ANY: 4265 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4266 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4267 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4268 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4269 4270 from sqlglot.dialects import Dialect 4271 4272 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4273 if self.dialect.__class__ != Dialect: 4274 self.unsupported("ARRAY_ANY is unsupported") 4275 4276 return self.function_fallback_sql(expression) 4277 4278 def struct_sql(self, expression: exp.Struct) -> str: 4279 expression.set( 4280 "expressions", 4281 [ 4282 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4283 if isinstance(e, exp.PropertyEQ) 4284 else e 4285 for e in expression.expressions 4286 ], 4287 ) 4288 4289 return self.function_fallback_sql(expression) 4290 4291 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4292 low = self.sql(expression, "this") 4293 high = self.sql(expression, "expression") 4294 4295 return f"{low} TO {high}" 4296 4297 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4298 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4299 tables = f" {self.expressions(expression)}" 4300 4301 exists = " IF EXISTS" if expression.args.get("exists") else "" 4302 4303 on_cluster = self.sql(expression, "cluster") 4304 on_cluster = f" {on_cluster}" if on_cluster else "" 4305 4306 identity = self.sql(expression, "identity") 4307 identity = f" {identity} IDENTITY" if identity else "" 4308 4309 option = self.sql(expression, "option") 4310 option = f" {option}" if option else "" 4311 4312 partition = self.sql(expression, "partition") 4313 partition = f" {partition}" if partition else "" 4314 4315 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4316 4317 # This transpiles T-SQL's CONVERT function 4318 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4319 def convert_sql(self, expression: exp.Convert) -> str: 4320 to = expression.this 4321 value = expression.expression 4322 style = expression.args.get("style") 4323 safe = expression.args.get("safe") 4324 strict = expression.args.get("strict") 4325 4326 if not to or not value: 4327 return "" 4328 4329 # Retrieve length of datatype and override to default if not specified 4330 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4331 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4332 4333 transformed: t.Optional[exp.Expression] = None 4334 cast = exp.Cast if strict else exp.TryCast 4335 4336 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4337 if isinstance(style, exp.Literal) and style.is_int: 4338 from sqlglot.dialects.tsql import TSQL 4339 4340 style_value = style.name 4341 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4342 if not converted_style: 4343 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4344 4345 fmt = exp.Literal.string(converted_style) 4346 4347 if to.this == exp.DataType.Type.DATE: 4348 transformed = exp.StrToDate(this=value, format=fmt) 4349 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4350 transformed = exp.StrToTime(this=value, format=fmt) 4351 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4352 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4353 elif to.this == exp.DataType.Type.TEXT: 4354 transformed = exp.TimeToStr(this=value, format=fmt) 4355 4356 if not transformed: 4357 transformed = cast(this=value, to=to, safe=safe) 4358 4359 return self.sql(transformed) 4360 4361 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4362 this = expression.this 4363 if isinstance(this, exp.JSONPathWildcard): 4364 this = self.json_path_part(this) 4365 return f".{this}" if this else "" 4366 4367 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4368 return f".{this}" 4369 4370 this = self.json_path_part(this) 4371 return ( 4372 f"[{this}]" 4373 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4374 else f".{this}" 4375 ) 4376 4377 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4378 this = self.json_path_part(expression.this) 4379 return f"[{this}]" if this else "" 4380 4381 def _simplify_unless_literal(self, expression: E) -> E: 4382 if not isinstance(expression, exp.Literal): 4383 from sqlglot.optimizer.simplify import simplify 4384 4385 expression = simplify(expression, dialect=self.dialect) 4386 4387 return expression 4388 4389 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4390 this = expression.this 4391 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4392 self.unsupported( 4393 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4394 ) 4395 return self.sql(this) 4396 4397 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4398 # The first modifier here will be the one closest to the AggFunc's arg 4399 mods = sorted( 4400 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4401 key=lambda x: 0 4402 if isinstance(x, exp.HavingMax) 4403 else (1 if isinstance(x, exp.Order) else 2), 4404 ) 4405 4406 if mods: 4407 mod = mods[0] 4408 this = expression.__class__(this=mod.this.copy()) 4409 this.meta["inline"] = True 4410 mod.this.replace(this) 4411 return self.sql(expression.this) 4412 4413 agg_func = expression.find(exp.AggFunc) 4414 4415 if agg_func: 4416 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4417 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4418 4419 return f"{self.sql(expression, 'this')} {text}" 4420 4421 def _replace_line_breaks(self, string: str) -> str: 4422 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4423 if self.pretty: 4424 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4425 return string 4426 4427 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4428 option = self.sql(expression, "this") 4429 4430 if expression.expressions: 4431 upper = option.upper() 4432 4433 # Snowflake FILE_FORMAT options are separated by whitespace 4434 sep = " " if upper == "FILE_FORMAT" else ", " 4435 4436 # Databricks copy/format options do not set their list of values with EQ 4437 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4438 values = self.expressions(expression, flat=True, sep=sep) 4439 return f"{option}{op}({values})" 4440 4441 value = self.sql(expression, "expression") 4442 4443 if not value: 4444 return option 4445 4446 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4447 4448 return f"{option}{op}{value}" 4449 4450 def credentials_sql(self, expression: exp.Credentials) -> str: 4451 cred_expr = expression.args.get("credentials") 4452 if isinstance(cred_expr, exp.Literal): 4453 # Redshift case: CREDENTIALS <string> 4454 credentials = self.sql(expression, "credentials") 4455 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4456 else: 4457 # Snowflake case: CREDENTIALS = (...) 4458 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4459 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4460 4461 storage = self.sql(expression, "storage") 4462 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4463 4464 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4465 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4466 4467 iam_role = self.sql(expression, "iam_role") 4468 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4469 4470 region = self.sql(expression, "region") 4471 region = f" REGION {region}" if region else "" 4472 4473 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4474 4475 def copy_sql(self, expression: exp.Copy) -> str: 4476 this = self.sql(expression, "this") 4477 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4478 4479 credentials = self.sql(expression, "credentials") 4480 credentials = self.seg(credentials) if credentials else "" 4481 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4482 files = self.expressions(expression, key="files", flat=True) 4483 4484 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4485 params = self.expressions( 4486 expression, 4487 key="params", 4488 sep=sep, 4489 new_line=True, 4490 skip_last=True, 4491 skip_first=True, 4492 indent=self.COPY_PARAMS_ARE_WRAPPED, 4493 ) 4494 4495 if params: 4496 if self.COPY_PARAMS_ARE_WRAPPED: 4497 params = f" WITH ({params})" 4498 elif not self.pretty: 4499 params = f" {params}" 4500 4501 return f"COPY{this}{kind} {files}{credentials}{params}" 4502 4503 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4504 return "" 4505 4506 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4507 on_sql = "ON" if expression.args.get("on") else "OFF" 4508 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4509 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4510 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4511 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4512 4513 if filter_col or retention_period: 4514 on_sql = self.func("ON", filter_col, retention_period) 4515 4516 return f"DATA_DELETION={on_sql}" 4517 4518 def maskingpolicycolumnconstraint_sql( 4519 self, expression: exp.MaskingPolicyColumnConstraint 4520 ) -> str: 4521 this = self.sql(expression, "this") 4522 expressions = self.expressions(expression, flat=True) 4523 expressions = f" USING ({expressions})" if expressions else "" 4524 return f"MASKING POLICY {this}{expressions}" 4525 4526 def gapfill_sql(self, expression: exp.GapFill) -> str: 4527 this = self.sql(expression, "this") 4528 this = f"TABLE {this}" 4529 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4530 4531 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4532 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4533 4534 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4535 this = self.sql(expression, "this") 4536 expr = expression.expression 4537 4538 if isinstance(expr, exp.Func): 4539 # T-SQL's CLR functions are case sensitive 4540 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4541 else: 4542 expr = self.sql(expression, "expression") 4543 4544 return self.scope_resolution(expr, this) 4545 4546 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4547 if self.PARSE_JSON_NAME is None: 4548 return self.sql(expression.this) 4549 4550 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4551 4552 def rand_sql(self, expression: exp.Rand) -> str: 4553 lower = self.sql(expression, "lower") 4554 upper = self.sql(expression, "upper") 4555 4556 if lower and upper: 4557 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4558 return self.func("RAND", expression.this) 4559 4560 def changes_sql(self, expression: exp.Changes) -> str: 4561 information = self.sql(expression, "information") 4562 information = f"INFORMATION => {information}" 4563 at_before = self.sql(expression, "at_before") 4564 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4565 end = self.sql(expression, "end") 4566 end = f"{self.seg('')}{end}" if end else "" 4567 4568 return f"CHANGES ({information}){at_before}{end}" 4569 4570 def pad_sql(self, expression: exp.Pad) -> str: 4571 prefix = "L" if expression.args.get("is_left") else "R" 4572 4573 fill_pattern = self.sql(expression, "fill_pattern") or None 4574 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4575 fill_pattern = "' '" 4576 4577 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4578 4579 def summarize_sql(self, expression: exp.Summarize) -> str: 4580 table = " TABLE" if expression.args.get("table") else "" 4581 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4582 4583 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4584 generate_series = exp.GenerateSeries(**expression.args) 4585 4586 parent = expression.parent 4587 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4588 parent = parent.parent 4589 4590 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4591 return self.sql(exp.Unnest(expressions=[generate_series])) 4592 4593 if isinstance(parent, exp.Select): 4594 self.unsupported("GenerateSeries projection unnesting is not supported.") 4595 4596 return self.sql(generate_series) 4597 4598 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4599 exprs = expression.expressions 4600 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4601 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4602 else: 4603 rhs = self.expressions(expression) 4604 4605 return self.func(name, expression.this, rhs or None) 4606 4607 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4608 if self.SUPPORTS_CONVERT_TIMEZONE: 4609 return self.function_fallback_sql(expression) 4610 4611 source_tz = expression.args.get("source_tz") 4612 target_tz = expression.args.get("target_tz") 4613 timestamp = expression.args.get("timestamp") 4614 4615 if source_tz and timestamp: 4616 timestamp = exp.AtTimeZone( 4617 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4618 ) 4619 4620 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4621 4622 return self.sql(expr) 4623 4624 def json_sql(self, expression: exp.JSON) -> str: 4625 this = self.sql(expression, "this") 4626 this = f" {this}" if this else "" 4627 4628 _with = expression.args.get("with") 4629 4630 if _with is None: 4631 with_sql = "" 4632 elif not _with: 4633 with_sql = " WITHOUT" 4634 else: 4635 with_sql = " WITH" 4636 4637 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4638 4639 return f"JSON{this}{with_sql}{unique_sql}" 4640 4641 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4642 def _generate_on_options(arg: t.Any) -> str: 4643 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4644 4645 path = self.sql(expression, "path") 4646 returning = self.sql(expression, "returning") 4647 returning = f" RETURNING {returning}" if returning else "" 4648 4649 on_condition = self.sql(expression, "on_condition") 4650 on_condition = f" {on_condition}" if on_condition else "" 4651 4652 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4653 4654 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4655 else_ = "ELSE " if expression.args.get("else_") else "" 4656 condition = self.sql(expression, "expression") 4657 condition = f"WHEN {condition} THEN " if condition else else_ 4658 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4659 return f"{condition}{insert}" 4660 4661 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4662 kind = self.sql(expression, "kind") 4663 expressions = self.seg(self.expressions(expression, sep=" ")) 4664 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4665 return res 4666 4667 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4668 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4669 empty = expression.args.get("empty") 4670 empty = ( 4671 f"DEFAULT {empty} ON EMPTY" 4672 if isinstance(empty, exp.Expression) 4673 else self.sql(expression, "empty") 4674 ) 4675 4676 error = expression.args.get("error") 4677 error = ( 4678 f"DEFAULT {error} ON ERROR" 4679 if isinstance(error, exp.Expression) 4680 else self.sql(expression, "error") 4681 ) 4682 4683 if error and empty: 4684 error = ( 4685 f"{empty} {error}" 4686 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4687 else f"{error} {empty}" 4688 ) 4689 empty = "" 4690 4691 null = self.sql(expression, "null") 4692 4693 return f"{empty}{error}{null}" 4694 4695 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4696 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4697 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4698 4699 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4700 this = self.sql(expression, "this") 4701 path = self.sql(expression, "path") 4702 4703 passing = self.expressions(expression, "passing") 4704 passing = f" PASSING {passing}" if passing else "" 4705 4706 on_condition = self.sql(expression, "on_condition") 4707 on_condition = f" {on_condition}" if on_condition else "" 4708 4709 path = f"{path}{passing}{on_condition}" 4710 4711 return self.func("JSON_EXISTS", this, path) 4712 4713 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4714 array_agg = self.function_fallback_sql(expression) 4715 4716 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4717 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4718 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4719 parent = expression.parent 4720 if isinstance(parent, exp.Filter): 4721 parent_cond = parent.expression.this 4722 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4723 else: 4724 this = expression.this 4725 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4726 if this.find(exp.Column): 4727 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4728 this_sql = ( 4729 self.expressions(this) 4730 if isinstance(this, exp.Distinct) 4731 else self.sql(expression, "this") 4732 ) 4733 4734 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4735 4736 return array_agg 4737 4738 def apply_sql(self, expression: exp.Apply) -> str: 4739 this = self.sql(expression, "this") 4740 expr = self.sql(expression, "expression") 4741 4742 return f"{this} APPLY({expr})" 4743 4744 def grant_sql(self, expression: exp.Grant) -> str: 4745 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4746 4747 kind = self.sql(expression, "kind") 4748 kind = f" {kind}" if kind else "" 4749 4750 securable = self.sql(expression, "securable") 4751 securable = f" {securable}" if securable else "" 4752 4753 principals = self.expressions(expression, key="principals", flat=True) 4754 4755 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4756 4757 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4758 4759 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4760 this = self.sql(expression, "this") 4761 columns = self.expressions(expression, flat=True) 4762 columns = f"({columns})" if columns else "" 4763 4764 return f"{this}{columns}" 4765 4766 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4767 this = self.sql(expression, "this") 4768 4769 kind = self.sql(expression, "kind") 4770 kind = f"{kind} " if kind else "" 4771 4772 return f"{kind}{this}" 4773 4774 def columns_sql(self, expression: exp.Columns): 4775 func = self.function_fallback_sql(expression) 4776 if expression.args.get("unpack"): 4777 func = f"*{func}" 4778 4779 return func 4780 4781 def overlay_sql(self, expression: exp.Overlay): 4782 this = self.sql(expression, "this") 4783 expr = self.sql(expression, "expression") 4784 from_sql = self.sql(expression, "from") 4785 for_sql = self.sql(expression, "for") 4786 for_sql = f" FOR {for_sql}" if for_sql else "" 4787 4788 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4789 4790 @unsupported_args("format") 4791 def todouble_sql(self, expression: exp.ToDouble) -> str: 4792 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4793 4794 def string_sql(self, expression: exp.String) -> str: 4795 this = expression.this 4796 zone = expression.args.get("zone") 4797 4798 if zone: 4799 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4800 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4801 # set for source_tz to transpile the time conversion before the STRING cast 4802 this = exp.ConvertTimezone( 4803 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4804 ) 4805 4806 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4807 4808 def median_sql(self, expression: exp.Median): 4809 if not self.SUPPORTS_MEDIAN: 4810 return self.sql( 4811 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4812 ) 4813 4814 return self.function_fallback_sql(expression) 4815 4816 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4817 filler = self.sql(expression, "this") 4818 filler = f" {filler}" if filler else "" 4819 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4820 return f"TRUNCATE{filler} {with_count}" 4821 4822 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4823 if self.SUPPORTS_UNIX_SECONDS: 4824 return self.function_fallback_sql(expression) 4825 4826 start_ts = exp.cast( 4827 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4828 ) 4829 4830 return self.sql( 4831 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4832 ) 4833 4834 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4835 dim = expression.expression 4836 4837 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4838 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4839 if not (dim.is_int and dim.name == "1"): 4840 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4841 dim = None 4842 4843 # If dimension is required but not specified, default initialize it 4844 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4845 dim = exp.Literal.number(1) 4846 4847 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4848 4849 def attach_sql(self, expression: exp.Attach) -> str: 4850 this = self.sql(expression, "this") 4851 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4852 expressions = self.expressions(expression) 4853 expressions = f" ({expressions})" if expressions else "" 4854 4855 return f"ATTACH{exists_sql} {this}{expressions}" 4856 4857 def detach_sql(self, expression: exp.Detach) -> str: 4858 this = self.sql(expression, "this") 4859 # the DATABASE keyword is required if IF EXISTS is set 4860 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4861 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4862 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4863 4864 return f"DETACH{exists_sql} {this}" 4865 4866 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4867 this = self.sql(expression, "this") 4868 value = self.sql(expression, "expression") 4869 value = f" {value}" if value else "" 4870 return f"{this}{value}" 4871 4872 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4873 this_sql = self.sql(expression, "this") 4874 if isinstance(expression.this, exp.Table): 4875 this_sql = f"TABLE {this_sql}" 4876 4877 return self.func( 4878 "FEATURES_AT_TIME", 4879 this_sql, 4880 expression.args.get("time"), 4881 expression.args.get("num_rows"), 4882 expression.args.get("ignore_feature_nulls"), 4883 ) 4884 4885 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4886 return ( 4887 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4888 ) 4889 4890 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4891 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4892 encode = f"{encode} {self.sql(expression, 'this')}" 4893 4894 properties = expression.args.get("properties") 4895 if properties: 4896 encode = f"{encode} {self.properties(properties)}" 4897 4898 return encode 4899 4900 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4901 this = self.sql(expression, "this") 4902 include = f"INCLUDE {this}" 4903 4904 column_def = self.sql(expression, "column_def") 4905 if column_def: 4906 include = f"{include} {column_def}" 4907 4908 alias = self.sql(expression, "alias") 4909 if alias: 4910 include = f"{include} AS {alias}" 4911 4912 return include 4913 4914 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4915 name = f"NAME {self.sql(expression, 'this')}" 4916 return self.func("XMLELEMENT", name, *expression.expressions) 4917 4918 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 4919 this = self.sql(expression, "this") 4920 expr = self.sql(expression, "expression") 4921 expr = f"({expr})" if expr else "" 4922 return f"{this}{expr}" 4923 4924 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4925 partitions = self.expressions(expression, "partition_expressions") 4926 create = self.expressions(expression, "create_expressions") 4927 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4928 4929 def partitionbyrangepropertydynamic_sql( 4930 self, expression: exp.PartitionByRangePropertyDynamic 4931 ) -> str: 4932 start = self.sql(expression, "start") 4933 end = self.sql(expression, "end") 4934 4935 every = expression.args["every"] 4936 if isinstance(every, exp.Interval) and every.this.is_string: 4937 every.this.replace(exp.Literal.number(every.name)) 4938 4939 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4940 4941 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4942 name = self.sql(expression, "this") 4943 values = self.expressions(expression, flat=True) 4944 4945 return f"NAME {name} VALUE {values}" 4946 4947 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4948 kind = self.sql(expression, "kind") 4949 sample = self.sql(expression, "sample") 4950 return f"SAMPLE {sample} {kind}" 4951 4952 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4953 kind = self.sql(expression, "kind") 4954 option = self.sql(expression, "option") 4955 option = f" {option}" if option else "" 4956 this = self.sql(expression, "this") 4957 this = f" {this}" if this else "" 4958 columns = self.expressions(expression) 4959 columns = f" {columns}" if columns else "" 4960 return f"{kind}{option} STATISTICS{this}{columns}" 4961 4962 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4963 this = self.sql(expression, "this") 4964 columns = self.expressions(expression) 4965 inner_expression = self.sql(expression, "expression") 4966 inner_expression = f" {inner_expression}" if inner_expression else "" 4967 update_options = self.sql(expression, "update_options") 4968 update_options = f" {update_options} UPDATE" if update_options else "" 4969 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4970 4971 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4972 kind = self.sql(expression, "kind") 4973 kind = f" {kind}" if kind else "" 4974 return f"DELETE{kind} STATISTICS" 4975 4976 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4977 inner_expression = self.sql(expression, "expression") 4978 return f"LIST CHAINED ROWS{inner_expression}" 4979 4980 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4981 kind = self.sql(expression, "kind") 4982 this = self.sql(expression, "this") 4983 this = f" {this}" if this else "" 4984 inner_expression = self.sql(expression, "expression") 4985 return f"VALIDATE {kind}{this}{inner_expression}" 4986 4987 def analyze_sql(self, expression: exp.Analyze) -> str: 4988 options = self.expressions(expression, key="options", sep=" ") 4989 options = f" {options}" if options else "" 4990 kind = self.sql(expression, "kind") 4991 kind = f" {kind}" if kind else "" 4992 this = self.sql(expression, "this") 4993 this = f" {this}" if this else "" 4994 mode = self.sql(expression, "mode") 4995 mode = f" {mode}" if mode else "" 4996 properties = self.sql(expression, "properties") 4997 properties = f" {properties}" if properties else "" 4998 partition = self.sql(expression, "partition") 4999 partition = f" {partition}" if partition else "" 5000 inner_expression = self.sql(expression, "expression") 5001 inner_expression = f" {inner_expression}" if inner_expression else "" 5002 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5003 5004 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5005 this = self.sql(expression, "this") 5006 namespaces = self.expressions(expression, key="namespaces") 5007 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5008 passing = self.expressions(expression, key="passing") 5009 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5010 columns = self.expressions(expression, key="columns") 5011 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5012 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5013 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5014 5015 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5016 this = self.sql(expression, "this") 5017 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5018 5019 def export_sql(self, expression: exp.Export) -> str: 5020 this = self.sql(expression, "this") 5021 connection = self.sql(expression, "connection") 5022 connection = f"WITH CONNECTION {connection} " if connection else "" 5023 options = self.sql(expression, "options") 5024 return f"EXPORT DATA {connection}{options} AS {this}" 5025 5026 def declare_sql(self, expression: exp.Declare) -> str: 5027 return f"DECLARE {self.expressions(expression, flat=True)}" 5028 5029 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5030 variable = self.sql(expression, "this") 5031 default = self.sql(expression, "default") 5032 default = f" = {default}" if default else "" 5033 5034 kind = self.sql(expression, "kind") 5035 if isinstance(expression.args.get("kind"), exp.Schema): 5036 kind = f"TABLE {kind}" 5037 5038 return f"{variable} AS {kind}{default}" 5039 5040 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5041 kind = self.sql(expression, "kind") 5042 this = self.sql(expression, "this") 5043 set = self.sql(expression, "expression") 5044 using = self.sql(expression, "using") 5045 using = f" USING {using}" if using else "" 5046 5047 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5048 5049 return f"{kind_sql} {this} SET {set}{using}" 5050 5051 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5052 params = self.expressions(expression, key="params", flat=True) 5053 return self.func(expression.name, *expression.expressions) + f"({params})" 5054 5055 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5056 return self.func(expression.name, *expression.expressions) 5057 5058 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5059 return self.anonymousaggfunc_sql(expression) 5060 5061 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5062 return self.parameterizedagg_sql(expression) 5063 5064 def show_sql(self, expression: exp.Show) -> str: 5065 self.unsupported("Unsupported SHOW statement") 5066 return "" 5067 5068 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5069 # Snowflake GET/PUT statements: 5070 # PUT <file> <internalStage> <properties> 5071 # GET <internalStage> <file> <properties> 5072 props = expression.args.get("properties") 5073 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5074 this = self.sql(expression, "this") 5075 target = self.sql(expression, "target") 5076 5077 if isinstance(expression, exp.Put): 5078 return f"PUT {this} {target}{props_sql}" 5079 else: 5080 return f"GET {target} {this}{props_sql}" 5081 5082 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5083 this = self.sql(expression, "this") 5084 expr = self.sql(expression, "expression") 5085 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5086 return f"TRANSLATE({this} USING {expr}{with_error})" 5087 5088 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5089 if self.SUPPORTS_DECODE_CASE: 5090 return self.func("DECODE", *expression.expressions) 5091 5092 expression, *expressions = expression.expressions 5093 5094 ifs = [] 5095 for search, result in zip(expressions[::2], expressions[1::2]): 5096 if isinstance(search, exp.Literal): 5097 ifs.append(exp.If(this=expression.eq(search), true=result)) 5098 elif isinstance(search, exp.Null): 5099 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5100 else: 5101 if isinstance(search, exp.Binary): 5102 search = exp.paren(search) 5103 5104 cond = exp.or_( 5105 expression.eq(search), 5106 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5107 copy=False, 5108 ) 5109 ifs.append(exp.If(this=cond, true=result)) 5110 5111 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5112 return self.sql(case) 5113 5114 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5115 this = self.sql(expression, "this") 5116 this = self.seg(this, sep="") 5117 dimensions = self.expressions( 5118 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5119 ) 5120 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5121 metrics = self.expressions( 5122 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5123 ) 5124 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5125 where = self.sql(expression, "where") 5126 where = self.seg(f"WHERE {where}") if where else "" 5127 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}" 5128 5129 def getextract_sql(self, expression: exp.GetExtract) -> str: 5130 this = expression.this 5131 expr = expression.expression 5132 5133 if not this.type or not expression.type: 5134 from sqlglot.optimizer.annotate_types import annotate_types 5135 5136 this = annotate_types(this, dialect=self.dialect) 5137 5138 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5139 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5140 5141 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5142 5143 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5144 return self.sql( 5145 exp.DateAdd( 5146 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5147 expression=expression.this, 5148 unit=exp.var("DAY"), 5149 ) 5150 ) 5151 5152 def space_sql(self: Generator, expression: exp.Space) -> str: 5153 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
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 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 697 698 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 699 700 __slots__ = ( 701 "pretty", 702 "identify", 703 "normalize", 704 "pad", 705 "_indent", 706 "normalize_functions", 707 "unsupported_level", 708 "max_unsupported", 709 "leading_comma", 710 "max_text_width", 711 "comments", 712 "dialect", 713 "unsupported_messages", 714 "_escaped_quote_end", 715 "_escaped_identifier_end", 716 "_next_name", 717 "_identifier_start", 718 "_identifier_end", 719 "_quote_json_path_key_using_brackets", 720 ) 721 722 def __init__( 723 self, 724 pretty: t.Optional[bool] = None, 725 identify: str | bool = False, 726 normalize: bool = False, 727 pad: int = 2, 728 indent: int = 2, 729 normalize_functions: t.Optional[str | bool] = None, 730 unsupported_level: ErrorLevel = ErrorLevel.WARN, 731 max_unsupported: int = 3, 732 leading_comma: bool = False, 733 max_text_width: int = 80, 734 comments: bool = True, 735 dialect: DialectType = None, 736 ): 737 import sqlglot 738 from sqlglot.dialects import Dialect 739 740 self.pretty = pretty if pretty is not None else sqlglot.pretty 741 self.identify = identify 742 self.normalize = normalize 743 self.pad = pad 744 self._indent = indent 745 self.unsupported_level = unsupported_level 746 self.max_unsupported = max_unsupported 747 self.leading_comma = leading_comma 748 self.max_text_width = max_text_width 749 self.comments = comments 750 self.dialect = Dialect.get_or_raise(dialect) 751 752 # This is both a Dialect property and a Generator argument, so we prioritize the latter 753 self.normalize_functions = ( 754 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 755 ) 756 757 self.unsupported_messages: t.List[str] = [] 758 self._escaped_quote_end: str = ( 759 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 760 ) 761 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 762 763 self._next_name = name_sequence("_t") 764 765 self._identifier_start = self.dialect.IDENTIFIER_START 766 self._identifier_end = self.dialect.IDENTIFIER_END 767 768 self._quote_json_path_key_using_brackets = True 769 770 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 771 """ 772 Generates the SQL string corresponding to the given syntax tree. 773 774 Args: 775 expression: The syntax tree. 776 copy: Whether to copy the expression. The generator performs mutations so 777 it is safer to copy. 778 779 Returns: 780 The SQL string corresponding to `expression`. 781 """ 782 if copy: 783 expression = expression.copy() 784 785 expression = self.preprocess(expression) 786 787 self.unsupported_messages = [] 788 sql = self.sql(expression).strip() 789 790 if self.pretty: 791 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 792 793 if self.unsupported_level == ErrorLevel.IGNORE: 794 return sql 795 796 if self.unsupported_level == ErrorLevel.WARN: 797 for msg in self.unsupported_messages: 798 logger.warning(msg) 799 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 800 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 801 802 return sql 803 804 def preprocess(self, expression: exp.Expression) -> exp.Expression: 805 """Apply generic preprocessing transformations to a given expression.""" 806 expression = self._move_ctes_to_top_level(expression) 807 808 if self.ENSURE_BOOLS: 809 from sqlglot.transforms import ensure_bools 810 811 expression = ensure_bools(expression) 812 813 return expression 814 815 def _move_ctes_to_top_level(self, expression: E) -> E: 816 if ( 817 not expression.parent 818 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 819 and any(node.parent is not expression for node in expression.find_all(exp.With)) 820 ): 821 from sqlglot.transforms import move_ctes_to_top_level 822 823 expression = move_ctes_to_top_level(expression) 824 return expression 825 826 def unsupported(self, message: str) -> None: 827 if self.unsupported_level == ErrorLevel.IMMEDIATE: 828 raise UnsupportedError(message) 829 self.unsupported_messages.append(message) 830 831 def sep(self, sep: str = " ") -> str: 832 return f"{sep.strip()}\n" if self.pretty else sep 833 834 def seg(self, sql: str, sep: str = " ") -> str: 835 return f"{self.sep(sep)}{sql}" 836 837 def sanitize_comment(self, comment: str) -> str: 838 comment = " " + comment if comment[0].strip() else comment 839 comment = comment + " " if comment[-1].strip() else comment 840 841 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 842 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 843 comment = comment.replace("*/", "* /") 844 845 return comment 846 847 def maybe_comment( 848 self, 849 sql: str, 850 expression: t.Optional[exp.Expression] = None, 851 comments: t.Optional[t.List[str]] = None, 852 separated: bool = False, 853 ) -> str: 854 comments = ( 855 ((expression and expression.comments) if comments is None else comments) # type: ignore 856 if self.comments 857 else None 858 ) 859 860 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 861 return sql 862 863 comments_sql = " ".join( 864 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 865 ) 866 867 if not comments_sql: 868 return sql 869 870 comments_sql = self._replace_line_breaks(comments_sql) 871 872 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 873 return ( 874 f"{self.sep()}{comments_sql}{sql}" 875 if not sql or sql[0].isspace() 876 else f"{comments_sql}{self.sep()}{sql}" 877 ) 878 879 return f"{sql} {comments_sql}" 880 881 def wrap(self, expression: exp.Expression | str) -> str: 882 this_sql = ( 883 self.sql(expression) 884 if isinstance(expression, exp.UNWRAPPED_QUERIES) 885 else self.sql(expression, "this") 886 ) 887 if not this_sql: 888 return "()" 889 890 this_sql = self.indent(this_sql, level=1, pad=0) 891 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 892 893 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 894 original = self.identify 895 self.identify = False 896 result = func(*args, **kwargs) 897 self.identify = original 898 return result 899 900 def normalize_func(self, name: str) -> str: 901 if self.normalize_functions == "upper" or self.normalize_functions is True: 902 return name.upper() 903 if self.normalize_functions == "lower": 904 return name.lower() 905 return name 906 907 def indent( 908 self, 909 sql: str, 910 level: int = 0, 911 pad: t.Optional[int] = None, 912 skip_first: bool = False, 913 skip_last: bool = False, 914 ) -> str: 915 if not self.pretty or not sql: 916 return sql 917 918 pad = self.pad if pad is None else pad 919 lines = sql.split("\n") 920 921 return "\n".join( 922 ( 923 line 924 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 925 else f"{' ' * (level * self._indent + pad)}{line}" 926 ) 927 for i, line in enumerate(lines) 928 ) 929 930 def sql( 931 self, 932 expression: t.Optional[str | exp.Expression], 933 key: t.Optional[str] = None, 934 comment: bool = True, 935 ) -> str: 936 if not expression: 937 return "" 938 939 if isinstance(expression, str): 940 return expression 941 942 if key: 943 value = expression.args.get(key) 944 if value: 945 return self.sql(value) 946 return "" 947 948 transform = self.TRANSFORMS.get(expression.__class__) 949 950 if callable(transform): 951 sql = transform(self, expression) 952 elif isinstance(expression, exp.Expression): 953 exp_handler_name = f"{expression.key}_sql" 954 955 if hasattr(self, exp_handler_name): 956 sql = getattr(self, exp_handler_name)(expression) 957 elif isinstance(expression, exp.Func): 958 sql = self.function_fallback_sql(expression) 959 elif isinstance(expression, exp.Property): 960 sql = self.property_sql(expression) 961 else: 962 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 963 else: 964 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 965 966 return self.maybe_comment(sql, expression) if self.comments and comment else sql 967 968 def uncache_sql(self, expression: exp.Uncache) -> str: 969 table = self.sql(expression, "this") 970 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 971 return f"UNCACHE TABLE{exists_sql} {table}" 972 973 def cache_sql(self, expression: exp.Cache) -> str: 974 lazy = " LAZY" if expression.args.get("lazy") else "" 975 table = self.sql(expression, "this") 976 options = expression.args.get("options") 977 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 978 sql = self.sql(expression, "expression") 979 sql = f" AS{self.sep()}{sql}" if sql else "" 980 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 981 return self.prepend_ctes(expression, sql) 982 983 def characterset_sql(self, expression: exp.CharacterSet) -> str: 984 if isinstance(expression.parent, exp.Cast): 985 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 986 default = "DEFAULT " if expression.args.get("default") else "" 987 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 988 989 def column_parts(self, expression: exp.Column) -> str: 990 return ".".join( 991 self.sql(part) 992 for part in ( 993 expression.args.get("catalog"), 994 expression.args.get("db"), 995 expression.args.get("table"), 996 expression.args.get("this"), 997 ) 998 if part 999 ) 1000 1001 def column_sql(self, expression: exp.Column) -> str: 1002 join_mark = " (+)" if expression.args.get("join_mark") else "" 1003 1004 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1005 join_mark = "" 1006 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1007 1008 return f"{self.column_parts(expression)}{join_mark}" 1009 1010 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1011 this = self.sql(expression, "this") 1012 this = f" {this}" if this else "" 1013 position = self.sql(expression, "position") 1014 return f"{position}{this}" 1015 1016 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1017 column = self.sql(expression, "this") 1018 kind = self.sql(expression, "kind") 1019 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1020 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1021 kind = f"{sep}{kind}" if kind else "" 1022 constraints = f" {constraints}" if constraints else "" 1023 position = self.sql(expression, "position") 1024 position = f" {position}" if position else "" 1025 1026 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1027 kind = "" 1028 1029 return f"{exists}{column}{kind}{constraints}{position}" 1030 1031 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1032 this = self.sql(expression, "this") 1033 kind_sql = self.sql(expression, "kind").strip() 1034 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1035 1036 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1037 this = self.sql(expression, "this") 1038 if expression.args.get("not_null"): 1039 persisted = " PERSISTED NOT NULL" 1040 elif expression.args.get("persisted"): 1041 persisted = " PERSISTED" 1042 else: 1043 persisted = "" 1044 1045 return f"AS {this}{persisted}" 1046 1047 def autoincrementcolumnconstraint_sql(self, _) -> str: 1048 return self.token_sql(TokenType.AUTO_INCREMENT) 1049 1050 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1051 if isinstance(expression.this, list): 1052 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1053 else: 1054 this = self.sql(expression, "this") 1055 1056 return f"COMPRESS {this}" 1057 1058 def generatedasidentitycolumnconstraint_sql( 1059 self, expression: exp.GeneratedAsIdentityColumnConstraint 1060 ) -> str: 1061 this = "" 1062 if expression.this is not None: 1063 on_null = " ON NULL" if expression.args.get("on_null") else "" 1064 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1065 1066 start = expression.args.get("start") 1067 start = f"START WITH {start}" if start else "" 1068 increment = expression.args.get("increment") 1069 increment = f" INCREMENT BY {increment}" if increment else "" 1070 minvalue = expression.args.get("minvalue") 1071 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1072 maxvalue = expression.args.get("maxvalue") 1073 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1074 cycle = expression.args.get("cycle") 1075 cycle_sql = "" 1076 1077 if cycle is not None: 1078 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1079 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1080 1081 sequence_opts = "" 1082 if start or increment or cycle_sql: 1083 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1084 sequence_opts = f" ({sequence_opts.strip()})" 1085 1086 expr = self.sql(expression, "expression") 1087 expr = f"({expr})" if expr else "IDENTITY" 1088 1089 return f"GENERATED{this} AS {expr}{sequence_opts}" 1090 1091 def generatedasrowcolumnconstraint_sql( 1092 self, expression: exp.GeneratedAsRowColumnConstraint 1093 ) -> str: 1094 start = "START" if expression.args.get("start") else "END" 1095 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1096 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1097 1098 def periodforsystemtimeconstraint_sql( 1099 self, expression: exp.PeriodForSystemTimeConstraint 1100 ) -> str: 1101 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1102 1103 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1104 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1105 1106 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1107 desc = expression.args.get("desc") 1108 if desc is not None: 1109 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1110 options = self.expressions(expression, key="options", flat=True, sep=" ") 1111 options = f" {options}" if options else "" 1112 return f"PRIMARY KEY{options}" 1113 1114 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1115 this = self.sql(expression, "this") 1116 this = f" {this}" if this else "" 1117 index_type = expression.args.get("index_type") 1118 index_type = f" USING {index_type}" if index_type else "" 1119 on_conflict = self.sql(expression, "on_conflict") 1120 on_conflict = f" {on_conflict}" if on_conflict else "" 1121 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1122 options = self.expressions(expression, key="options", flat=True, sep=" ") 1123 options = f" {options}" if options else "" 1124 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1125 1126 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1127 return self.sql(expression, "this") 1128 1129 def create_sql(self, expression: exp.Create) -> str: 1130 kind = self.sql(expression, "kind") 1131 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1132 properties = expression.args.get("properties") 1133 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1134 1135 this = self.createable_sql(expression, properties_locs) 1136 1137 properties_sql = "" 1138 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1139 exp.Properties.Location.POST_WITH 1140 ): 1141 properties_sql = self.sql( 1142 exp.Properties( 1143 expressions=[ 1144 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1145 *properties_locs[exp.Properties.Location.POST_WITH], 1146 ] 1147 ) 1148 ) 1149 1150 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1151 properties_sql = self.sep() + properties_sql 1152 elif not self.pretty: 1153 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1154 properties_sql = f" {properties_sql}" 1155 1156 begin = " BEGIN" if expression.args.get("begin") else "" 1157 end = " END" if expression.args.get("end") else "" 1158 1159 expression_sql = self.sql(expression, "expression") 1160 if expression_sql: 1161 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1162 1163 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1164 postalias_props_sql = "" 1165 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1166 postalias_props_sql = self.properties( 1167 exp.Properties( 1168 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1169 ), 1170 wrapped=False, 1171 ) 1172 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1173 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1174 1175 postindex_props_sql = "" 1176 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1177 postindex_props_sql = self.properties( 1178 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1179 wrapped=False, 1180 prefix=" ", 1181 ) 1182 1183 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1184 indexes = f" {indexes}" if indexes else "" 1185 index_sql = indexes + postindex_props_sql 1186 1187 replace = " OR REPLACE" if expression.args.get("replace") else "" 1188 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1189 unique = " UNIQUE" if expression.args.get("unique") else "" 1190 1191 clustered = expression.args.get("clustered") 1192 if clustered is None: 1193 clustered_sql = "" 1194 elif clustered: 1195 clustered_sql = " CLUSTERED COLUMNSTORE" 1196 else: 1197 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1198 1199 postcreate_props_sql = "" 1200 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1201 postcreate_props_sql = self.properties( 1202 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1203 sep=" ", 1204 prefix=" ", 1205 wrapped=False, 1206 ) 1207 1208 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1209 1210 postexpression_props_sql = "" 1211 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1212 postexpression_props_sql = self.properties( 1213 exp.Properties( 1214 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1215 ), 1216 sep=" ", 1217 prefix=" ", 1218 wrapped=False, 1219 ) 1220 1221 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1222 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1223 no_schema_binding = ( 1224 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1225 ) 1226 1227 clone = self.sql(expression, "clone") 1228 clone = f" {clone}" if clone else "" 1229 1230 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1231 properties_expression = f"{expression_sql}{properties_sql}" 1232 else: 1233 properties_expression = f"{properties_sql}{expression_sql}" 1234 1235 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1236 return self.prepend_ctes(expression, expression_sql) 1237 1238 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1239 start = self.sql(expression, "start") 1240 start = f"START WITH {start}" if start else "" 1241 increment = self.sql(expression, "increment") 1242 increment = f" INCREMENT BY {increment}" if increment else "" 1243 minvalue = self.sql(expression, "minvalue") 1244 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1245 maxvalue = self.sql(expression, "maxvalue") 1246 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1247 owned = self.sql(expression, "owned") 1248 owned = f" OWNED BY {owned}" if owned else "" 1249 1250 cache = expression.args.get("cache") 1251 if cache is None: 1252 cache_str = "" 1253 elif cache is True: 1254 cache_str = " CACHE" 1255 else: 1256 cache_str = f" CACHE {cache}" 1257 1258 options = self.expressions(expression, key="options", flat=True, sep=" ") 1259 options = f" {options}" if options else "" 1260 1261 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1262 1263 def clone_sql(self, expression: exp.Clone) -> str: 1264 this = self.sql(expression, "this") 1265 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1266 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1267 return f"{shallow}{keyword} {this}" 1268 1269 def describe_sql(self, expression: exp.Describe) -> str: 1270 style = expression.args.get("style") 1271 style = f" {style}" if style else "" 1272 partition = self.sql(expression, "partition") 1273 partition = f" {partition}" if partition else "" 1274 format = self.sql(expression, "format") 1275 format = f" {format}" if format else "" 1276 1277 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1278 1279 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1280 tag = self.sql(expression, "tag") 1281 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1282 1283 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1284 with_ = self.sql(expression, "with") 1285 if with_: 1286 sql = f"{with_}{self.sep()}{sql}" 1287 return sql 1288 1289 def with_sql(self, expression: exp.With) -> str: 1290 sql = self.expressions(expression, flat=True) 1291 recursive = ( 1292 "RECURSIVE " 1293 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1294 else "" 1295 ) 1296 search = self.sql(expression, "search") 1297 search = f" {search}" if search else "" 1298 1299 return f"WITH {recursive}{sql}{search}" 1300 1301 def cte_sql(self, expression: exp.CTE) -> str: 1302 alias = expression.args.get("alias") 1303 if alias: 1304 alias.add_comments(expression.pop_comments()) 1305 1306 alias_sql = self.sql(expression, "alias") 1307 1308 materialized = expression.args.get("materialized") 1309 if materialized is False: 1310 materialized = "NOT MATERIALIZED " 1311 elif materialized: 1312 materialized = "MATERIALIZED " 1313 1314 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1315 1316 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1317 alias = self.sql(expression, "this") 1318 columns = self.expressions(expression, key="columns", flat=True) 1319 columns = f"({columns})" if columns else "" 1320 1321 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1322 columns = "" 1323 self.unsupported("Named columns are not supported in table alias.") 1324 1325 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1326 alias = self._next_name() 1327 1328 return f"{alias}{columns}" 1329 1330 def bitstring_sql(self, expression: exp.BitString) -> str: 1331 this = self.sql(expression, "this") 1332 if self.dialect.BIT_START: 1333 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1334 return f"{int(this, 2)}" 1335 1336 def hexstring_sql( 1337 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1338 ) -> str: 1339 this = self.sql(expression, "this") 1340 is_integer_type = expression.args.get("is_integer") 1341 1342 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1343 not self.dialect.HEX_START and not binary_function_repr 1344 ): 1345 # Integer representation will be returned if: 1346 # - The read dialect treats the hex value as integer literal but not the write 1347 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1348 return f"{int(this, 16)}" 1349 1350 if not is_integer_type: 1351 # Read dialect treats the hex value as BINARY/BLOB 1352 if binary_function_repr: 1353 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1354 return self.func(binary_function_repr, exp.Literal.string(this)) 1355 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1356 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1357 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1358 1359 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1360 1361 def bytestring_sql(self, expression: exp.ByteString) -> str: 1362 this = self.sql(expression, "this") 1363 if self.dialect.BYTE_START: 1364 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1365 return this 1366 1367 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1368 this = self.sql(expression, "this") 1369 escape = expression.args.get("escape") 1370 1371 if self.dialect.UNICODE_START: 1372 escape_substitute = r"\\\1" 1373 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1374 else: 1375 escape_substitute = r"\\u\1" 1376 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1377 1378 if escape: 1379 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1380 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1381 else: 1382 escape_pattern = ESCAPED_UNICODE_RE 1383 escape_sql = "" 1384 1385 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1386 this = escape_pattern.sub(escape_substitute, this) 1387 1388 return f"{left_quote}{this}{right_quote}{escape_sql}" 1389 1390 def rawstring_sql(self, expression: exp.RawString) -> str: 1391 string = expression.this 1392 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1393 string = string.replace("\\", "\\\\") 1394 1395 string = self.escape_str(string, escape_backslash=False) 1396 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1397 1398 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1399 this = self.sql(expression, "this") 1400 specifier = self.sql(expression, "expression") 1401 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1402 return f"{this}{specifier}" 1403 1404 def datatype_sql(self, expression: exp.DataType) -> str: 1405 nested = "" 1406 values = "" 1407 interior = self.expressions(expression, flat=True) 1408 1409 type_value = expression.this 1410 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1411 type_sql = self.sql(expression, "kind") 1412 else: 1413 type_sql = ( 1414 self.TYPE_MAPPING.get(type_value, type_value.value) 1415 if isinstance(type_value, exp.DataType.Type) 1416 else type_value 1417 ) 1418 1419 if interior: 1420 if expression.args.get("nested"): 1421 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1422 if expression.args.get("values") is not None: 1423 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1424 values = self.expressions(expression, key="values", flat=True) 1425 values = f"{delimiters[0]}{values}{delimiters[1]}" 1426 elif type_value == exp.DataType.Type.INTERVAL: 1427 nested = f" {interior}" 1428 else: 1429 nested = f"({interior})" 1430 1431 type_sql = f"{type_sql}{nested}{values}" 1432 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1433 exp.DataType.Type.TIMETZ, 1434 exp.DataType.Type.TIMESTAMPTZ, 1435 ): 1436 type_sql = f"{type_sql} WITH TIME ZONE" 1437 1438 return type_sql 1439 1440 def directory_sql(self, expression: exp.Directory) -> str: 1441 local = "LOCAL " if expression.args.get("local") else "" 1442 row_format = self.sql(expression, "row_format") 1443 row_format = f" {row_format}" if row_format else "" 1444 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1445 1446 def delete_sql(self, expression: exp.Delete) -> str: 1447 this = self.sql(expression, "this") 1448 this = f" FROM {this}" if this else "" 1449 using = self.sql(expression, "using") 1450 using = f" USING {using}" if using else "" 1451 cluster = self.sql(expression, "cluster") 1452 cluster = f" {cluster}" if cluster else "" 1453 where = self.sql(expression, "where") 1454 returning = self.sql(expression, "returning") 1455 limit = self.sql(expression, "limit") 1456 tables = self.expressions(expression, key="tables") 1457 tables = f" {tables}" if tables else "" 1458 if self.RETURNING_END: 1459 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1460 else: 1461 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1462 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1463 1464 def drop_sql(self, expression: exp.Drop) -> str: 1465 this = self.sql(expression, "this") 1466 expressions = self.expressions(expression, flat=True) 1467 expressions = f" ({expressions})" if expressions else "" 1468 kind = expression.args["kind"] 1469 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1470 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1471 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1472 on_cluster = self.sql(expression, "cluster") 1473 on_cluster = f" {on_cluster}" if on_cluster else "" 1474 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1475 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1476 cascade = " CASCADE" if expression.args.get("cascade") else "" 1477 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1478 purge = " PURGE" if expression.args.get("purge") else "" 1479 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1480 1481 def set_operation(self, expression: exp.SetOperation) -> str: 1482 op_type = type(expression) 1483 op_name = op_type.key.upper() 1484 1485 distinct = expression.args.get("distinct") 1486 if ( 1487 distinct is False 1488 and op_type in (exp.Except, exp.Intersect) 1489 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1490 ): 1491 self.unsupported(f"{op_name} ALL is not supported") 1492 1493 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1494 1495 if distinct is None: 1496 distinct = default_distinct 1497 if distinct is None: 1498 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1499 1500 if distinct is default_distinct: 1501 distinct_or_all = "" 1502 else: 1503 distinct_or_all = " DISTINCT" if distinct else " ALL" 1504 1505 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1506 side_kind = f"{side_kind} " if side_kind else "" 1507 1508 by_name = " BY NAME" if expression.args.get("by_name") else "" 1509 on = self.expressions(expression, key="on", flat=True) 1510 on = f" ON ({on})" if on else "" 1511 1512 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1513 1514 def set_operations(self, expression: exp.SetOperation) -> str: 1515 if not self.SET_OP_MODIFIERS: 1516 limit = expression.args.get("limit") 1517 order = expression.args.get("order") 1518 1519 if limit or order: 1520 select = self._move_ctes_to_top_level( 1521 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1522 ) 1523 1524 if limit: 1525 select = select.limit(limit.pop(), copy=False) 1526 if order: 1527 select = select.order_by(order.pop(), copy=False) 1528 return self.sql(select) 1529 1530 sqls: t.List[str] = [] 1531 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1532 1533 while stack: 1534 node = stack.pop() 1535 1536 if isinstance(node, exp.SetOperation): 1537 stack.append(node.expression) 1538 stack.append( 1539 self.maybe_comment( 1540 self.set_operation(node), comments=node.comments, separated=True 1541 ) 1542 ) 1543 stack.append(node.this) 1544 else: 1545 sqls.append(self.sql(node)) 1546 1547 this = self.sep().join(sqls) 1548 this = self.query_modifiers(expression, this) 1549 return self.prepend_ctes(expression, this) 1550 1551 def fetch_sql(self, expression: exp.Fetch) -> str: 1552 direction = expression.args.get("direction") 1553 direction = f" {direction}" if direction else "" 1554 count = self.sql(expression, "count") 1555 count = f" {count}" if count else "" 1556 limit_options = self.sql(expression, "limit_options") 1557 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1558 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1559 1560 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1561 percent = " PERCENT" if expression.args.get("percent") else "" 1562 rows = " ROWS" if expression.args.get("rows") else "" 1563 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1564 if not with_ties and rows: 1565 with_ties = " ONLY" 1566 return f"{percent}{rows}{with_ties}" 1567 1568 def filter_sql(self, expression: exp.Filter) -> str: 1569 if self.AGGREGATE_FILTER_SUPPORTED: 1570 this = self.sql(expression, "this") 1571 where = self.sql(expression, "expression").strip() 1572 return f"{this} FILTER({where})" 1573 1574 agg = expression.this 1575 agg_arg = agg.this 1576 cond = expression.expression.this 1577 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1578 return self.sql(agg) 1579 1580 def hint_sql(self, expression: exp.Hint) -> str: 1581 if not self.QUERY_HINTS: 1582 self.unsupported("Hints are not supported") 1583 return "" 1584 1585 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1586 1587 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1588 using = self.sql(expression, "using") 1589 using = f" USING {using}" if using else "" 1590 columns = self.expressions(expression, key="columns", flat=True) 1591 columns = f"({columns})" if columns else "" 1592 partition_by = self.expressions(expression, key="partition_by", flat=True) 1593 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1594 where = self.sql(expression, "where") 1595 include = self.expressions(expression, key="include", flat=True) 1596 if include: 1597 include = f" INCLUDE ({include})" 1598 with_storage = self.expressions(expression, key="with_storage", flat=True) 1599 with_storage = f" WITH ({with_storage})" if with_storage else "" 1600 tablespace = self.sql(expression, "tablespace") 1601 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1602 on = self.sql(expression, "on") 1603 on = f" ON {on}" if on else "" 1604 1605 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1606 1607 def index_sql(self, expression: exp.Index) -> str: 1608 unique = "UNIQUE " if expression.args.get("unique") else "" 1609 primary = "PRIMARY " if expression.args.get("primary") else "" 1610 amp = "AMP " if expression.args.get("amp") else "" 1611 name = self.sql(expression, "this") 1612 name = f"{name} " if name else "" 1613 table = self.sql(expression, "table") 1614 table = f"{self.INDEX_ON} {table}" if table else "" 1615 1616 index = "INDEX " if not table else "" 1617 1618 params = self.sql(expression, "params") 1619 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1620 1621 def identifier_sql(self, expression: exp.Identifier) -> str: 1622 text = expression.name 1623 lower = text.lower() 1624 text = lower if self.normalize and not expression.quoted else text 1625 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1626 if ( 1627 expression.quoted 1628 or self.dialect.can_identify(text, self.identify) 1629 or lower in self.RESERVED_KEYWORDS 1630 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1631 ): 1632 text = f"{self._identifier_start}{text}{self._identifier_end}" 1633 return text 1634 1635 def hex_sql(self, expression: exp.Hex) -> str: 1636 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1637 if self.dialect.HEX_LOWERCASE: 1638 text = self.func("LOWER", text) 1639 1640 return text 1641 1642 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1643 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1644 if not self.dialect.HEX_LOWERCASE: 1645 text = self.func("LOWER", text) 1646 return text 1647 1648 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1649 input_format = self.sql(expression, "input_format") 1650 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1651 output_format = self.sql(expression, "output_format") 1652 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1653 return self.sep().join((input_format, output_format)) 1654 1655 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1656 string = self.sql(exp.Literal.string(expression.name)) 1657 return f"{prefix}{string}" 1658 1659 def partition_sql(self, expression: exp.Partition) -> str: 1660 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1661 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1662 1663 def properties_sql(self, expression: exp.Properties) -> str: 1664 root_properties = [] 1665 with_properties = [] 1666 1667 for p in expression.expressions: 1668 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1669 if p_loc == exp.Properties.Location.POST_WITH: 1670 with_properties.append(p) 1671 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1672 root_properties.append(p) 1673 1674 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1675 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1676 1677 if root_props and with_props and not self.pretty: 1678 with_props = " " + with_props 1679 1680 return root_props + with_props 1681 1682 def root_properties(self, properties: exp.Properties) -> str: 1683 if properties.expressions: 1684 return self.expressions(properties, indent=False, sep=" ") 1685 return "" 1686 1687 def properties( 1688 self, 1689 properties: exp.Properties, 1690 prefix: str = "", 1691 sep: str = ", ", 1692 suffix: str = "", 1693 wrapped: bool = True, 1694 ) -> str: 1695 if properties.expressions: 1696 expressions = self.expressions(properties, sep=sep, indent=False) 1697 if expressions: 1698 expressions = self.wrap(expressions) if wrapped else expressions 1699 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1700 return "" 1701 1702 def with_properties(self, properties: exp.Properties) -> str: 1703 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1704 1705 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1706 properties_locs = defaultdict(list) 1707 for p in properties.expressions: 1708 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1709 if p_loc != exp.Properties.Location.UNSUPPORTED: 1710 properties_locs[p_loc].append(p) 1711 else: 1712 self.unsupported(f"Unsupported property {p.key}") 1713 1714 return properties_locs 1715 1716 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1717 if isinstance(expression.this, exp.Dot): 1718 return self.sql(expression, "this") 1719 return f"'{expression.name}'" if string_key else expression.name 1720 1721 def property_sql(self, expression: exp.Property) -> str: 1722 property_cls = expression.__class__ 1723 if property_cls == exp.Property: 1724 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1725 1726 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1727 if not property_name: 1728 self.unsupported(f"Unsupported property {expression.key}") 1729 1730 return f"{property_name}={self.sql(expression, 'this')}" 1731 1732 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1733 if self.SUPPORTS_CREATE_TABLE_LIKE: 1734 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1735 options = f" {options}" if options else "" 1736 1737 like = f"LIKE {self.sql(expression, 'this')}{options}" 1738 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1739 like = f"({like})" 1740 1741 return like 1742 1743 if expression.expressions: 1744 self.unsupported("Transpilation of LIKE property options is unsupported") 1745 1746 select = exp.select("*").from_(expression.this).limit(0) 1747 return f"AS {self.sql(select)}" 1748 1749 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1750 no = "NO " if expression.args.get("no") else "" 1751 protection = " PROTECTION" if expression.args.get("protection") else "" 1752 return f"{no}FALLBACK{protection}" 1753 1754 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1755 no = "NO " if expression.args.get("no") else "" 1756 local = expression.args.get("local") 1757 local = f"{local} " if local else "" 1758 dual = "DUAL " if expression.args.get("dual") else "" 1759 before = "BEFORE " if expression.args.get("before") else "" 1760 after = "AFTER " if expression.args.get("after") else "" 1761 return f"{no}{local}{dual}{before}{after}JOURNAL" 1762 1763 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1764 freespace = self.sql(expression, "this") 1765 percent = " PERCENT" if expression.args.get("percent") else "" 1766 return f"FREESPACE={freespace}{percent}" 1767 1768 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1769 if expression.args.get("default"): 1770 property = "DEFAULT" 1771 elif expression.args.get("on"): 1772 property = "ON" 1773 else: 1774 property = "OFF" 1775 return f"CHECKSUM={property}" 1776 1777 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1778 if expression.args.get("no"): 1779 return "NO MERGEBLOCKRATIO" 1780 if expression.args.get("default"): 1781 return "DEFAULT MERGEBLOCKRATIO" 1782 1783 percent = " PERCENT" if expression.args.get("percent") else "" 1784 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1785 1786 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1787 default = expression.args.get("default") 1788 minimum = expression.args.get("minimum") 1789 maximum = expression.args.get("maximum") 1790 if default or minimum or maximum: 1791 if default: 1792 prop = "DEFAULT" 1793 elif minimum: 1794 prop = "MINIMUM" 1795 else: 1796 prop = "MAXIMUM" 1797 return f"{prop} DATABLOCKSIZE" 1798 units = expression.args.get("units") 1799 units = f" {units}" if units else "" 1800 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1801 1802 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1803 autotemp = expression.args.get("autotemp") 1804 always = expression.args.get("always") 1805 default = expression.args.get("default") 1806 manual = expression.args.get("manual") 1807 never = expression.args.get("never") 1808 1809 if autotemp is not None: 1810 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1811 elif always: 1812 prop = "ALWAYS" 1813 elif default: 1814 prop = "DEFAULT" 1815 elif manual: 1816 prop = "MANUAL" 1817 elif never: 1818 prop = "NEVER" 1819 return f"BLOCKCOMPRESSION={prop}" 1820 1821 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1822 no = expression.args.get("no") 1823 no = " NO" if no else "" 1824 concurrent = expression.args.get("concurrent") 1825 concurrent = " CONCURRENT" if concurrent else "" 1826 target = self.sql(expression, "target") 1827 target = f" {target}" if target else "" 1828 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1829 1830 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1831 if isinstance(expression.this, list): 1832 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1833 if expression.this: 1834 modulus = self.sql(expression, "this") 1835 remainder = self.sql(expression, "expression") 1836 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1837 1838 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1839 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1840 return f"FROM ({from_expressions}) TO ({to_expressions})" 1841 1842 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1843 this = self.sql(expression, "this") 1844 1845 for_values_or_default = expression.expression 1846 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1847 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1848 else: 1849 for_values_or_default = " DEFAULT" 1850 1851 return f"PARTITION OF {this}{for_values_or_default}" 1852 1853 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1854 kind = expression.args.get("kind") 1855 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1856 for_or_in = expression.args.get("for_or_in") 1857 for_or_in = f" {for_or_in}" if for_or_in else "" 1858 lock_type = expression.args.get("lock_type") 1859 override = " OVERRIDE" if expression.args.get("override") else "" 1860 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1861 1862 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1863 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1864 statistics = expression.args.get("statistics") 1865 statistics_sql = "" 1866 if statistics is not None: 1867 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1868 return f"{data_sql}{statistics_sql}" 1869 1870 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1871 this = self.sql(expression, "this") 1872 this = f"HISTORY_TABLE={this}" if this else "" 1873 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1874 data_consistency = ( 1875 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1876 ) 1877 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1878 retention_period = ( 1879 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1880 ) 1881 1882 if this: 1883 on_sql = self.func("ON", this, data_consistency, retention_period) 1884 else: 1885 on_sql = "ON" if expression.args.get("on") else "OFF" 1886 1887 sql = f"SYSTEM_VERSIONING={on_sql}" 1888 1889 return f"WITH({sql})" if expression.args.get("with") else sql 1890 1891 def insert_sql(self, expression: exp.Insert) -> str: 1892 hint = self.sql(expression, "hint") 1893 overwrite = expression.args.get("overwrite") 1894 1895 if isinstance(expression.this, exp.Directory): 1896 this = " OVERWRITE" if overwrite else " INTO" 1897 else: 1898 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1899 1900 stored = self.sql(expression, "stored") 1901 stored = f" {stored}" if stored else "" 1902 alternative = expression.args.get("alternative") 1903 alternative = f" OR {alternative}" if alternative else "" 1904 ignore = " IGNORE" if expression.args.get("ignore") else "" 1905 is_function = expression.args.get("is_function") 1906 if is_function: 1907 this = f"{this} FUNCTION" 1908 this = f"{this} {self.sql(expression, 'this')}" 1909 1910 exists = " IF EXISTS" if expression.args.get("exists") else "" 1911 where = self.sql(expression, "where") 1912 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1913 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1914 on_conflict = self.sql(expression, "conflict") 1915 on_conflict = f" {on_conflict}" if on_conflict else "" 1916 by_name = " BY NAME" if expression.args.get("by_name") else "" 1917 returning = self.sql(expression, "returning") 1918 1919 if self.RETURNING_END: 1920 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1921 else: 1922 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1923 1924 partition_by = self.sql(expression, "partition") 1925 partition_by = f" {partition_by}" if partition_by else "" 1926 settings = self.sql(expression, "settings") 1927 settings = f" {settings}" if settings else "" 1928 1929 source = self.sql(expression, "source") 1930 source = f"TABLE {source}" if source else "" 1931 1932 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1933 return self.prepend_ctes(expression, sql) 1934 1935 def introducer_sql(self, expression: exp.Introducer) -> str: 1936 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1937 1938 def kill_sql(self, expression: exp.Kill) -> str: 1939 kind = self.sql(expression, "kind") 1940 kind = f" {kind}" if kind else "" 1941 this = self.sql(expression, "this") 1942 this = f" {this}" if this else "" 1943 return f"KILL{kind}{this}" 1944 1945 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1946 return expression.name 1947 1948 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1949 return expression.name 1950 1951 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1952 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1953 1954 constraint = self.sql(expression, "constraint") 1955 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1956 1957 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1958 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1959 action = self.sql(expression, "action") 1960 1961 expressions = self.expressions(expression, flat=True) 1962 if expressions: 1963 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1964 expressions = f" {set_keyword}{expressions}" 1965 1966 where = self.sql(expression, "where") 1967 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1968 1969 def returning_sql(self, expression: exp.Returning) -> str: 1970 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1971 1972 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1973 fields = self.sql(expression, "fields") 1974 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1975 escaped = self.sql(expression, "escaped") 1976 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1977 items = self.sql(expression, "collection_items") 1978 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1979 keys = self.sql(expression, "map_keys") 1980 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1981 lines = self.sql(expression, "lines") 1982 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1983 null = self.sql(expression, "null") 1984 null = f" NULL DEFINED AS {null}" if null else "" 1985 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1986 1987 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1988 return f"WITH ({self.expressions(expression, flat=True)})" 1989 1990 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1991 this = f"{self.sql(expression, 'this')} INDEX" 1992 target = self.sql(expression, "target") 1993 target = f" FOR {target}" if target else "" 1994 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1995 1996 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1997 this = self.sql(expression, "this") 1998 kind = self.sql(expression, "kind") 1999 expr = self.sql(expression, "expression") 2000 return f"{this} ({kind} => {expr})" 2001 2002 def table_parts(self, expression: exp.Table) -> str: 2003 return ".".join( 2004 self.sql(part) 2005 for part in ( 2006 expression.args.get("catalog"), 2007 expression.args.get("db"), 2008 expression.args.get("this"), 2009 ) 2010 if part is not None 2011 ) 2012 2013 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2014 table = self.table_parts(expression) 2015 only = "ONLY " if expression.args.get("only") else "" 2016 partition = self.sql(expression, "partition") 2017 partition = f" {partition}" if partition else "" 2018 version = self.sql(expression, "version") 2019 version = f" {version}" if version else "" 2020 alias = self.sql(expression, "alias") 2021 alias = f"{sep}{alias}" if alias else "" 2022 2023 sample = self.sql(expression, "sample") 2024 if self.dialect.ALIAS_POST_TABLESAMPLE: 2025 sample_pre_alias = sample 2026 sample_post_alias = "" 2027 else: 2028 sample_pre_alias = "" 2029 sample_post_alias = sample 2030 2031 hints = self.expressions(expression, key="hints", sep=" ") 2032 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2033 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2034 joins = self.indent( 2035 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2036 ) 2037 laterals = self.expressions(expression, key="laterals", sep="") 2038 2039 file_format = self.sql(expression, "format") 2040 if file_format: 2041 pattern = self.sql(expression, "pattern") 2042 pattern = f", PATTERN => {pattern}" if pattern else "" 2043 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2044 2045 ordinality = expression.args.get("ordinality") or "" 2046 if ordinality: 2047 ordinality = f" WITH ORDINALITY{alias}" 2048 alias = "" 2049 2050 when = self.sql(expression, "when") 2051 if when: 2052 table = f"{table} {when}" 2053 2054 changes = self.sql(expression, "changes") 2055 changes = f" {changes}" if changes else "" 2056 2057 rows_from = self.expressions(expression, key="rows_from") 2058 if rows_from: 2059 table = f"ROWS FROM {self.wrap(rows_from)}" 2060 2061 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2062 2063 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2064 table = self.func("TABLE", expression.this) 2065 alias = self.sql(expression, "alias") 2066 alias = f" AS {alias}" if alias else "" 2067 sample = self.sql(expression, "sample") 2068 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2069 joins = self.indent( 2070 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2071 ) 2072 return f"{table}{alias}{pivots}{sample}{joins}" 2073 2074 def tablesample_sql( 2075 self, 2076 expression: exp.TableSample, 2077 tablesample_keyword: t.Optional[str] = None, 2078 ) -> str: 2079 method = self.sql(expression, "method") 2080 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2081 numerator = self.sql(expression, "bucket_numerator") 2082 denominator = self.sql(expression, "bucket_denominator") 2083 field = self.sql(expression, "bucket_field") 2084 field = f" ON {field}" if field else "" 2085 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2086 seed = self.sql(expression, "seed") 2087 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2088 2089 size = self.sql(expression, "size") 2090 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2091 size = f"{size} ROWS" 2092 2093 percent = self.sql(expression, "percent") 2094 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2095 percent = f"{percent} PERCENT" 2096 2097 expr = f"{bucket}{percent}{size}" 2098 if self.TABLESAMPLE_REQUIRES_PARENS: 2099 expr = f"({expr})" 2100 2101 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2102 2103 def pivot_sql(self, expression: exp.Pivot) -> str: 2104 expressions = self.expressions(expression, flat=True) 2105 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2106 2107 group = self.sql(expression, "group") 2108 2109 if expression.this: 2110 this = self.sql(expression, "this") 2111 if not expressions: 2112 return f"UNPIVOT {this}" 2113 2114 on = f"{self.seg('ON')} {expressions}" 2115 into = self.sql(expression, "into") 2116 into = f"{self.seg('INTO')} {into}" if into else "" 2117 using = self.expressions(expression, key="using", flat=True) 2118 using = f"{self.seg('USING')} {using}" if using else "" 2119 return f"{direction} {this}{on}{into}{using}{group}" 2120 2121 alias = self.sql(expression, "alias") 2122 alias = f" AS {alias}" if alias else "" 2123 2124 fields = self.expressions( 2125 expression, 2126 "fields", 2127 sep=" ", 2128 dynamic=True, 2129 new_line=True, 2130 skip_first=True, 2131 skip_last=True, 2132 ) 2133 2134 include_nulls = expression.args.get("include_nulls") 2135 if include_nulls is not None: 2136 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2137 else: 2138 nulls = "" 2139 2140 default_on_null = self.sql(expression, "default_on_null") 2141 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2142 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2143 2144 def version_sql(self, expression: exp.Version) -> str: 2145 this = f"FOR {expression.name}" 2146 kind = expression.text("kind") 2147 expr = self.sql(expression, "expression") 2148 return f"{this} {kind} {expr}" 2149 2150 def tuple_sql(self, expression: exp.Tuple) -> str: 2151 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2152 2153 def update_sql(self, expression: exp.Update) -> str: 2154 this = self.sql(expression, "this") 2155 set_sql = self.expressions(expression, flat=True) 2156 from_sql = self.sql(expression, "from") 2157 where_sql = self.sql(expression, "where") 2158 returning = self.sql(expression, "returning") 2159 order = self.sql(expression, "order") 2160 limit = self.sql(expression, "limit") 2161 if self.RETURNING_END: 2162 expression_sql = f"{from_sql}{where_sql}{returning}" 2163 else: 2164 expression_sql = f"{returning}{from_sql}{where_sql}" 2165 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2166 return self.prepend_ctes(expression, sql) 2167 2168 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2169 values_as_table = values_as_table and self.VALUES_AS_TABLE 2170 2171 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2172 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2173 args = self.expressions(expression) 2174 alias = self.sql(expression, "alias") 2175 values = f"VALUES{self.seg('')}{args}" 2176 values = ( 2177 f"({values})" 2178 if self.WRAP_DERIVED_VALUES 2179 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2180 else values 2181 ) 2182 return f"{values} AS {alias}" if alias else values 2183 2184 # Converts `VALUES...` expression into a series of select unions. 2185 alias_node = expression.args.get("alias") 2186 column_names = alias_node and alias_node.columns 2187 2188 selects: t.List[exp.Query] = [] 2189 2190 for i, tup in enumerate(expression.expressions): 2191 row = tup.expressions 2192 2193 if i == 0 and column_names: 2194 row = [ 2195 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2196 ] 2197 2198 selects.append(exp.Select(expressions=row)) 2199 2200 if self.pretty: 2201 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2202 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2203 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2204 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2205 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2206 2207 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2208 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2209 return f"({unions}){alias}" 2210 2211 def var_sql(self, expression: exp.Var) -> str: 2212 return self.sql(expression, "this") 2213 2214 @unsupported_args("expressions") 2215 def into_sql(self, expression: exp.Into) -> str: 2216 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2217 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2218 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2219 2220 def from_sql(self, expression: exp.From) -> str: 2221 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2222 2223 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2224 grouping_sets = self.expressions(expression, indent=False) 2225 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2226 2227 def rollup_sql(self, expression: exp.Rollup) -> str: 2228 expressions = self.expressions(expression, indent=False) 2229 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2230 2231 def cube_sql(self, expression: exp.Cube) -> str: 2232 expressions = self.expressions(expression, indent=False) 2233 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2234 2235 def group_sql(self, expression: exp.Group) -> str: 2236 group_by_all = expression.args.get("all") 2237 if group_by_all is True: 2238 modifier = " ALL" 2239 elif group_by_all is False: 2240 modifier = " DISTINCT" 2241 else: 2242 modifier = "" 2243 2244 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2245 2246 grouping_sets = self.expressions(expression, key="grouping_sets") 2247 cube = self.expressions(expression, key="cube") 2248 rollup = self.expressions(expression, key="rollup") 2249 2250 groupings = csv( 2251 self.seg(grouping_sets) if grouping_sets else "", 2252 self.seg(cube) if cube else "", 2253 self.seg(rollup) if rollup else "", 2254 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2255 sep=self.GROUPINGS_SEP, 2256 ) 2257 2258 if ( 2259 expression.expressions 2260 and groupings 2261 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2262 ): 2263 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2264 2265 return f"{group_by}{groupings}" 2266 2267 def having_sql(self, expression: exp.Having) -> str: 2268 this = self.indent(self.sql(expression, "this")) 2269 return f"{self.seg('HAVING')}{self.sep()}{this}" 2270 2271 def connect_sql(self, expression: exp.Connect) -> str: 2272 start = self.sql(expression, "start") 2273 start = self.seg(f"START WITH {start}") if start else "" 2274 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2275 connect = self.sql(expression, "connect") 2276 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2277 return start + connect 2278 2279 def prior_sql(self, expression: exp.Prior) -> str: 2280 return f"PRIOR {self.sql(expression, 'this')}" 2281 2282 def join_sql(self, expression: exp.Join) -> str: 2283 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2284 side = None 2285 else: 2286 side = expression.side 2287 2288 op_sql = " ".join( 2289 op 2290 for op in ( 2291 expression.method, 2292 "GLOBAL" if expression.args.get("global") else None, 2293 side, 2294 expression.kind, 2295 expression.hint if self.JOIN_HINTS else None, 2296 ) 2297 if op 2298 ) 2299 match_cond = self.sql(expression, "match_condition") 2300 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2301 on_sql = self.sql(expression, "on") 2302 using = expression.args.get("using") 2303 2304 if not on_sql and using: 2305 on_sql = csv(*(self.sql(column) for column in using)) 2306 2307 this = expression.this 2308 this_sql = self.sql(this) 2309 2310 exprs = self.expressions(expression) 2311 if exprs: 2312 this_sql = f"{this_sql},{self.seg(exprs)}" 2313 2314 if on_sql: 2315 on_sql = self.indent(on_sql, skip_first=True) 2316 space = self.seg(" " * self.pad) if self.pretty else " " 2317 if using: 2318 on_sql = f"{space}USING ({on_sql})" 2319 else: 2320 on_sql = f"{space}ON {on_sql}" 2321 elif not op_sql: 2322 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2323 return f" {this_sql}" 2324 2325 return f", {this_sql}" 2326 2327 if op_sql != "STRAIGHT_JOIN": 2328 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2329 2330 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2331 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2332 2333 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2334 args = self.expressions(expression, flat=True) 2335 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2336 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2337 2338 def lateral_op(self, expression: exp.Lateral) -> str: 2339 cross_apply = expression.args.get("cross_apply") 2340 2341 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2342 if cross_apply is True: 2343 op = "INNER JOIN " 2344 elif cross_apply is False: 2345 op = "LEFT JOIN " 2346 else: 2347 op = "" 2348 2349 return f"{op}LATERAL" 2350 2351 def lateral_sql(self, expression: exp.Lateral) -> str: 2352 this = self.sql(expression, "this") 2353 2354 if expression.args.get("view"): 2355 alias = expression.args["alias"] 2356 columns = self.expressions(alias, key="columns", flat=True) 2357 table = f" {alias.name}" if alias.name else "" 2358 columns = f" AS {columns}" if columns else "" 2359 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2360 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2361 2362 alias = self.sql(expression, "alias") 2363 alias = f" AS {alias}" if alias else "" 2364 2365 ordinality = expression.args.get("ordinality") or "" 2366 if ordinality: 2367 ordinality = f" WITH ORDINALITY{alias}" 2368 alias = "" 2369 2370 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2371 2372 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2373 this = self.sql(expression, "this") 2374 2375 args = [ 2376 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2377 for e in (expression.args.get(k) for k in ("offset", "expression")) 2378 if e 2379 ] 2380 2381 args_sql = ", ".join(self.sql(e) for e in args) 2382 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2383 expressions = self.expressions(expression, flat=True) 2384 limit_options = self.sql(expression, "limit_options") 2385 expressions = f" BY {expressions}" if expressions else "" 2386 2387 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2388 2389 def offset_sql(self, expression: exp.Offset) -> str: 2390 this = self.sql(expression, "this") 2391 value = expression.expression 2392 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2393 expressions = self.expressions(expression, flat=True) 2394 expressions = f" BY {expressions}" if expressions else "" 2395 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2396 2397 def setitem_sql(self, expression: exp.SetItem) -> str: 2398 kind = self.sql(expression, "kind") 2399 kind = f"{kind} " if kind else "" 2400 this = self.sql(expression, "this") 2401 expressions = self.expressions(expression) 2402 collate = self.sql(expression, "collate") 2403 collate = f" COLLATE {collate}" if collate else "" 2404 global_ = "GLOBAL " if expression.args.get("global") else "" 2405 return f"{global_}{kind}{this}{expressions}{collate}" 2406 2407 def set_sql(self, expression: exp.Set) -> str: 2408 expressions = f" {self.expressions(expression, flat=True)}" 2409 tag = " TAG" if expression.args.get("tag") else "" 2410 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2411 2412 def queryband_sql(self, expression: exp.QueryBand) -> str: 2413 this = self.sql(expression, "this") 2414 update = " UPDATE" if expression.args.get("update") else "" 2415 scope = self.sql(expression, "scope") 2416 scope = f" FOR {scope}" if scope else "" 2417 2418 return f"QUERY_BAND = {this}{update}{scope}" 2419 2420 def pragma_sql(self, expression: exp.Pragma) -> str: 2421 return f"PRAGMA {self.sql(expression, 'this')}" 2422 2423 def lock_sql(self, expression: exp.Lock) -> str: 2424 if not self.LOCKING_READS_SUPPORTED: 2425 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2426 return "" 2427 2428 update = expression.args["update"] 2429 key = expression.args.get("key") 2430 if update: 2431 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2432 else: 2433 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2434 expressions = self.expressions(expression, flat=True) 2435 expressions = f" OF {expressions}" if expressions else "" 2436 wait = expression.args.get("wait") 2437 2438 if wait is not None: 2439 if isinstance(wait, exp.Literal): 2440 wait = f" WAIT {self.sql(wait)}" 2441 else: 2442 wait = " NOWAIT" if wait else " SKIP LOCKED" 2443 2444 return f"{lock_type}{expressions}{wait or ''}" 2445 2446 def literal_sql(self, expression: exp.Literal) -> str: 2447 text = expression.this or "" 2448 if expression.is_string: 2449 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2450 return text 2451 2452 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2453 if self.dialect.ESCAPED_SEQUENCES: 2454 to_escaped = self.dialect.ESCAPED_SEQUENCES 2455 text = "".join( 2456 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2457 ) 2458 2459 return self._replace_line_breaks(text).replace( 2460 self.dialect.QUOTE_END, self._escaped_quote_end 2461 ) 2462 2463 def loaddata_sql(self, expression: exp.LoadData) -> str: 2464 local = " LOCAL" if expression.args.get("local") else "" 2465 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2466 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2467 this = f" INTO TABLE {self.sql(expression, 'this')}" 2468 partition = self.sql(expression, "partition") 2469 partition = f" {partition}" if partition else "" 2470 input_format = self.sql(expression, "input_format") 2471 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2472 serde = self.sql(expression, "serde") 2473 serde = f" SERDE {serde}" if serde else "" 2474 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2475 2476 def null_sql(self, *_) -> str: 2477 return "NULL" 2478 2479 def boolean_sql(self, expression: exp.Boolean) -> str: 2480 return "TRUE" if expression.this else "FALSE" 2481 2482 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2483 this = self.sql(expression, "this") 2484 this = f"{this} " if this else this 2485 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2486 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2487 2488 def withfill_sql(self, expression: exp.WithFill) -> str: 2489 from_sql = self.sql(expression, "from") 2490 from_sql = f" FROM {from_sql}" if from_sql else "" 2491 to_sql = self.sql(expression, "to") 2492 to_sql = f" TO {to_sql}" if to_sql else "" 2493 step_sql = self.sql(expression, "step") 2494 step_sql = f" STEP {step_sql}" if step_sql else "" 2495 interpolated_values = [ 2496 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2497 if isinstance(e, exp.Alias) 2498 else self.sql(e, "this") 2499 for e in expression.args.get("interpolate") or [] 2500 ] 2501 interpolate = ( 2502 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2503 ) 2504 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2505 2506 def cluster_sql(self, expression: exp.Cluster) -> str: 2507 return self.op_expressions("CLUSTER BY", expression) 2508 2509 def distribute_sql(self, expression: exp.Distribute) -> str: 2510 return self.op_expressions("DISTRIBUTE BY", expression) 2511 2512 def sort_sql(self, expression: exp.Sort) -> str: 2513 return self.op_expressions("SORT BY", expression) 2514 2515 def ordered_sql(self, expression: exp.Ordered) -> str: 2516 desc = expression.args.get("desc") 2517 asc = not desc 2518 2519 nulls_first = expression.args.get("nulls_first") 2520 nulls_last = not nulls_first 2521 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2522 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2523 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2524 2525 this = self.sql(expression, "this") 2526 2527 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2528 nulls_sort_change = "" 2529 if nulls_first and ( 2530 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2531 ): 2532 nulls_sort_change = " NULLS FIRST" 2533 elif ( 2534 nulls_last 2535 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2536 and not nulls_are_last 2537 ): 2538 nulls_sort_change = " NULLS LAST" 2539 2540 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2541 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2542 window = expression.find_ancestor(exp.Window, exp.Select) 2543 if isinstance(window, exp.Window) and window.args.get("spec"): 2544 self.unsupported( 2545 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2546 ) 2547 nulls_sort_change = "" 2548 elif self.NULL_ORDERING_SUPPORTED is False and ( 2549 (asc and nulls_sort_change == " NULLS LAST") 2550 or (desc and nulls_sort_change == " NULLS FIRST") 2551 ): 2552 # BigQuery does not allow these ordering/nulls combinations when used under 2553 # an aggregation func or under a window containing one 2554 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2555 2556 if isinstance(ancestor, exp.Window): 2557 ancestor = ancestor.this 2558 if isinstance(ancestor, exp.AggFunc): 2559 self.unsupported( 2560 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2561 ) 2562 nulls_sort_change = "" 2563 elif self.NULL_ORDERING_SUPPORTED is None: 2564 if expression.this.is_int: 2565 self.unsupported( 2566 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2567 ) 2568 elif not isinstance(expression.this, exp.Rand): 2569 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2570 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2571 nulls_sort_change = "" 2572 2573 with_fill = self.sql(expression, "with_fill") 2574 with_fill = f" {with_fill}" if with_fill else "" 2575 2576 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2577 2578 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2579 window_frame = self.sql(expression, "window_frame") 2580 window_frame = f"{window_frame} " if window_frame else "" 2581 2582 this = self.sql(expression, "this") 2583 2584 return f"{window_frame}{this}" 2585 2586 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2587 partition = self.partition_by_sql(expression) 2588 order = self.sql(expression, "order") 2589 measures = self.expressions(expression, key="measures") 2590 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2591 rows = self.sql(expression, "rows") 2592 rows = self.seg(rows) if rows else "" 2593 after = self.sql(expression, "after") 2594 after = self.seg(after) if after else "" 2595 pattern = self.sql(expression, "pattern") 2596 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2597 definition_sqls = [ 2598 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2599 for definition in expression.args.get("define", []) 2600 ] 2601 definitions = self.expressions(sqls=definition_sqls) 2602 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2603 body = "".join( 2604 ( 2605 partition, 2606 order, 2607 measures, 2608 rows, 2609 after, 2610 pattern, 2611 define, 2612 ) 2613 ) 2614 alias = self.sql(expression, "alias") 2615 alias = f" {alias}" if alias else "" 2616 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2617 2618 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2619 limit = expression.args.get("limit") 2620 2621 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2622 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2623 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2624 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2625 2626 return csv( 2627 *sqls, 2628 *[self.sql(join) for join in expression.args.get("joins") or []], 2629 self.sql(expression, "match"), 2630 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2631 self.sql(expression, "prewhere"), 2632 self.sql(expression, "where"), 2633 self.sql(expression, "connect"), 2634 self.sql(expression, "group"), 2635 self.sql(expression, "having"), 2636 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2637 self.sql(expression, "order"), 2638 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2639 *self.after_limit_modifiers(expression), 2640 self.options_modifier(expression), 2641 self.for_modifiers(expression), 2642 sep="", 2643 ) 2644 2645 def options_modifier(self, expression: exp.Expression) -> str: 2646 options = self.expressions(expression, key="options") 2647 return f" {options}" if options else "" 2648 2649 def for_modifiers(self, expression: exp.Expression) -> str: 2650 for_modifiers = self.expressions(expression, key="for") 2651 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2652 2653 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2654 self.unsupported("Unsupported query option.") 2655 return "" 2656 2657 def offset_limit_modifiers( 2658 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2659 ) -> t.List[str]: 2660 return [ 2661 self.sql(expression, "offset") if fetch else self.sql(limit), 2662 self.sql(limit) if fetch else self.sql(expression, "offset"), 2663 ] 2664 2665 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2666 locks = self.expressions(expression, key="locks", sep=" ") 2667 locks = f" {locks}" if locks else "" 2668 return [locks, self.sql(expression, "sample")] 2669 2670 def select_sql(self, expression: exp.Select) -> str: 2671 into = expression.args.get("into") 2672 if not self.SUPPORTS_SELECT_INTO and into: 2673 into.pop() 2674 2675 hint = self.sql(expression, "hint") 2676 distinct = self.sql(expression, "distinct") 2677 distinct = f" {distinct}" if distinct else "" 2678 kind = self.sql(expression, "kind") 2679 2680 limit = expression.args.get("limit") 2681 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2682 top = self.limit_sql(limit, top=True) 2683 limit.pop() 2684 else: 2685 top = "" 2686 2687 expressions = self.expressions(expression) 2688 2689 if kind: 2690 if kind in self.SELECT_KINDS: 2691 kind = f" AS {kind}" 2692 else: 2693 if kind == "STRUCT": 2694 expressions = self.expressions( 2695 sqls=[ 2696 self.sql( 2697 exp.Struct( 2698 expressions=[ 2699 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2700 if isinstance(e, exp.Alias) 2701 else e 2702 for e in expression.expressions 2703 ] 2704 ) 2705 ) 2706 ] 2707 ) 2708 kind = "" 2709 2710 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2711 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2712 2713 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2714 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2715 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2716 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2717 sql = self.query_modifiers( 2718 expression, 2719 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2720 self.sql(expression, "into", comment=False), 2721 self.sql(expression, "from", comment=False), 2722 ) 2723 2724 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2725 if expression.args.get("with"): 2726 sql = self.maybe_comment(sql, expression) 2727 expression.pop_comments() 2728 2729 sql = self.prepend_ctes(expression, sql) 2730 2731 if not self.SUPPORTS_SELECT_INTO and into: 2732 if into.args.get("temporary"): 2733 table_kind = " TEMPORARY" 2734 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2735 table_kind = " UNLOGGED" 2736 else: 2737 table_kind = "" 2738 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2739 2740 return sql 2741 2742 def schema_sql(self, expression: exp.Schema) -> str: 2743 this = self.sql(expression, "this") 2744 sql = self.schema_columns_sql(expression) 2745 return f"{this} {sql}" if this and sql else this or sql 2746 2747 def schema_columns_sql(self, expression: exp.Schema) -> str: 2748 if expression.expressions: 2749 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2750 return "" 2751 2752 def star_sql(self, expression: exp.Star) -> str: 2753 except_ = self.expressions(expression, key="except", flat=True) 2754 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2755 replace = self.expressions(expression, key="replace", flat=True) 2756 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2757 rename = self.expressions(expression, key="rename", flat=True) 2758 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2759 return f"*{except_}{replace}{rename}" 2760 2761 def parameter_sql(self, expression: exp.Parameter) -> str: 2762 this = self.sql(expression, "this") 2763 return f"{self.PARAMETER_TOKEN}{this}" 2764 2765 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2766 this = self.sql(expression, "this") 2767 kind = expression.text("kind") 2768 if kind: 2769 kind = f"{kind}." 2770 return f"@@{kind}{this}" 2771 2772 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2773 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2774 2775 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2776 alias = self.sql(expression, "alias") 2777 alias = f"{sep}{alias}" if alias else "" 2778 sample = self.sql(expression, "sample") 2779 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2780 alias = f"{sample}{alias}" 2781 2782 # Set to None so it's not generated again by self.query_modifiers() 2783 expression.set("sample", None) 2784 2785 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2786 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2787 return self.prepend_ctes(expression, sql) 2788 2789 def qualify_sql(self, expression: exp.Qualify) -> str: 2790 this = self.indent(self.sql(expression, "this")) 2791 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2792 2793 def unnest_sql(self, expression: exp.Unnest) -> str: 2794 args = self.expressions(expression, flat=True) 2795 2796 alias = expression.args.get("alias") 2797 offset = expression.args.get("offset") 2798 2799 if self.UNNEST_WITH_ORDINALITY: 2800 if alias and isinstance(offset, exp.Expression): 2801 alias.append("columns", offset) 2802 2803 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2804 columns = alias.columns 2805 alias = self.sql(columns[0]) if columns else "" 2806 else: 2807 alias = self.sql(alias) 2808 2809 alias = f" AS {alias}" if alias else alias 2810 if self.UNNEST_WITH_ORDINALITY: 2811 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2812 else: 2813 if isinstance(offset, exp.Expression): 2814 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2815 elif offset: 2816 suffix = f"{alias} WITH OFFSET" 2817 else: 2818 suffix = alias 2819 2820 return f"UNNEST({args}){suffix}" 2821 2822 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2823 return "" 2824 2825 def where_sql(self, expression: exp.Where) -> str: 2826 this = self.indent(self.sql(expression, "this")) 2827 return f"{self.seg('WHERE')}{self.sep()}{this}" 2828 2829 def window_sql(self, expression: exp.Window) -> str: 2830 this = self.sql(expression, "this") 2831 partition = self.partition_by_sql(expression) 2832 order = expression.args.get("order") 2833 order = self.order_sql(order, flat=True) if order else "" 2834 spec = self.sql(expression, "spec") 2835 alias = self.sql(expression, "alias") 2836 over = self.sql(expression, "over") or "OVER" 2837 2838 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2839 2840 first = expression.args.get("first") 2841 if first is None: 2842 first = "" 2843 else: 2844 first = "FIRST" if first else "LAST" 2845 2846 if not partition and not order and not spec and alias: 2847 return f"{this} {alias}" 2848 2849 args = self.format_args( 2850 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2851 ) 2852 return f"{this} ({args})" 2853 2854 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2855 partition = self.expressions(expression, key="partition_by", flat=True) 2856 return f"PARTITION BY {partition}" if partition else "" 2857 2858 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2859 kind = self.sql(expression, "kind") 2860 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2861 end = ( 2862 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2863 or "CURRENT ROW" 2864 ) 2865 2866 window_spec = f"{kind} BETWEEN {start} AND {end}" 2867 2868 exclude = self.sql(expression, "exclude") 2869 if exclude: 2870 if self.SUPPORTS_WINDOW_EXCLUDE: 2871 window_spec += f" EXCLUDE {exclude}" 2872 else: 2873 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2874 2875 return window_spec 2876 2877 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2878 this = self.sql(expression, "this") 2879 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2880 return f"{this} WITHIN GROUP ({expression_sql})" 2881 2882 def between_sql(self, expression: exp.Between) -> str: 2883 this = self.sql(expression, "this") 2884 low = self.sql(expression, "low") 2885 high = self.sql(expression, "high") 2886 symmetric = expression.args.get("symmetric") 2887 2888 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2889 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2890 2891 flag = ( 2892 " SYMMETRIC" 2893 if symmetric 2894 else " ASYMMETRIC" 2895 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2896 else "" # silently drop ASYMMETRIC – semantics identical 2897 ) 2898 return f"{this} BETWEEN{flag} {low} AND {high}" 2899 2900 def bracket_offset_expressions( 2901 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2902 ) -> t.List[exp.Expression]: 2903 return apply_index_offset( 2904 expression.this, 2905 expression.expressions, 2906 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2907 dialect=self.dialect, 2908 ) 2909 2910 def bracket_sql(self, expression: exp.Bracket) -> str: 2911 expressions = self.bracket_offset_expressions(expression) 2912 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2913 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2914 2915 def all_sql(self, expression: exp.All) -> str: 2916 this = self.sql(expression, "this") 2917 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2918 this = self.wrap(this) 2919 return f"ALL {this}" 2920 2921 def any_sql(self, expression: exp.Any) -> str: 2922 this = self.sql(expression, "this") 2923 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2924 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2925 this = self.wrap(this) 2926 return f"ANY{this}" 2927 return f"ANY {this}" 2928 2929 def exists_sql(self, expression: exp.Exists) -> str: 2930 return f"EXISTS{self.wrap(expression)}" 2931 2932 def case_sql(self, expression: exp.Case) -> str: 2933 this = self.sql(expression, "this") 2934 statements = [f"CASE {this}" if this else "CASE"] 2935 2936 for e in expression.args["ifs"]: 2937 statements.append(f"WHEN {self.sql(e, 'this')}") 2938 statements.append(f"THEN {self.sql(e, 'true')}") 2939 2940 default = self.sql(expression, "default") 2941 2942 if default: 2943 statements.append(f"ELSE {default}") 2944 2945 statements.append("END") 2946 2947 if self.pretty and self.too_wide(statements): 2948 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2949 2950 return " ".join(statements) 2951 2952 def constraint_sql(self, expression: exp.Constraint) -> str: 2953 this = self.sql(expression, "this") 2954 expressions = self.expressions(expression, flat=True) 2955 return f"CONSTRAINT {this} {expressions}" 2956 2957 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2958 order = expression.args.get("order") 2959 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2960 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2961 2962 def extract_sql(self, expression: exp.Extract) -> str: 2963 from sqlglot.dialects.dialect import map_date_part 2964 2965 this = ( 2966 map_date_part(expression.this, self.dialect) 2967 if self.NORMALIZE_EXTRACT_DATE_PARTS 2968 else expression.this 2969 ) 2970 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2971 expression_sql = self.sql(expression, "expression") 2972 2973 return f"EXTRACT({this_sql} FROM {expression_sql})" 2974 2975 def trim_sql(self, expression: exp.Trim) -> str: 2976 trim_type = self.sql(expression, "position") 2977 2978 if trim_type == "LEADING": 2979 func_name = "LTRIM" 2980 elif trim_type == "TRAILING": 2981 func_name = "RTRIM" 2982 else: 2983 func_name = "TRIM" 2984 2985 return self.func(func_name, expression.this, expression.expression) 2986 2987 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2988 args = expression.expressions 2989 if isinstance(expression, exp.ConcatWs): 2990 args = args[1:] # Skip the delimiter 2991 2992 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2993 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2994 2995 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2996 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2997 2998 return args 2999 3000 def concat_sql(self, expression: exp.Concat) -> str: 3001 expressions = self.convert_concat_args(expression) 3002 3003 # Some dialects don't allow a single-argument CONCAT call 3004 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3005 return self.sql(expressions[0]) 3006 3007 return self.func("CONCAT", *expressions) 3008 3009 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3010 return self.func( 3011 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3012 ) 3013 3014 def check_sql(self, expression: exp.Check) -> str: 3015 this = self.sql(expression, key="this") 3016 return f"CHECK ({this})" 3017 3018 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3019 expressions = self.expressions(expression, flat=True) 3020 expressions = f" ({expressions})" if expressions else "" 3021 reference = self.sql(expression, "reference") 3022 reference = f" {reference}" if reference else "" 3023 delete = self.sql(expression, "delete") 3024 delete = f" ON DELETE {delete}" if delete else "" 3025 update = self.sql(expression, "update") 3026 update = f" ON UPDATE {update}" if update else "" 3027 options = self.expressions(expression, key="options", flat=True, sep=" ") 3028 options = f" {options}" if options else "" 3029 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3030 3031 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3032 expressions = self.expressions(expression, flat=True) 3033 include = self.sql(expression, "include") 3034 options = self.expressions(expression, key="options", flat=True, sep=" ") 3035 options = f" {options}" if options else "" 3036 return f"PRIMARY KEY ({expressions}){include}{options}" 3037 3038 def if_sql(self, expression: exp.If) -> str: 3039 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3040 3041 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3042 modifier = expression.args.get("modifier") 3043 modifier = f" {modifier}" if modifier else "" 3044 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3045 3046 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3047 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3048 3049 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3050 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3051 3052 if expression.args.get("escape"): 3053 path = self.escape_str(path) 3054 3055 if self.QUOTE_JSON_PATH: 3056 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3057 3058 return path 3059 3060 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3061 if isinstance(expression, exp.JSONPathPart): 3062 transform = self.TRANSFORMS.get(expression.__class__) 3063 if not callable(transform): 3064 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3065 return "" 3066 3067 return transform(self, expression) 3068 3069 if isinstance(expression, int): 3070 return str(expression) 3071 3072 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3073 escaped = expression.replace("'", "\\'") 3074 escaped = f"\\'{expression}\\'" 3075 else: 3076 escaped = expression.replace('"', '\\"') 3077 escaped = f'"{escaped}"' 3078 3079 return escaped 3080 3081 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3082 return f"{self.sql(expression, 'this')} FORMAT JSON" 3083 3084 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3085 # Output the Teradata column FORMAT override. 3086 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3087 this = self.sql(expression, "this") 3088 fmt = self.sql(expression, "format") 3089 return f"{this} (FORMAT {fmt})" 3090 3091 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3092 null_handling = expression.args.get("null_handling") 3093 null_handling = f" {null_handling}" if null_handling else "" 3094 3095 unique_keys = expression.args.get("unique_keys") 3096 if unique_keys is not None: 3097 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3098 else: 3099 unique_keys = "" 3100 3101 return_type = self.sql(expression, "return_type") 3102 return_type = f" RETURNING {return_type}" if return_type else "" 3103 encoding = self.sql(expression, "encoding") 3104 encoding = f" ENCODING {encoding}" if encoding else "" 3105 3106 return self.func( 3107 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3108 *expression.expressions, 3109 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3110 ) 3111 3112 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3113 return self.jsonobject_sql(expression) 3114 3115 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3116 null_handling = expression.args.get("null_handling") 3117 null_handling = f" {null_handling}" if null_handling else "" 3118 return_type = self.sql(expression, "return_type") 3119 return_type = f" RETURNING {return_type}" if return_type else "" 3120 strict = " STRICT" if expression.args.get("strict") else "" 3121 return self.func( 3122 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3123 ) 3124 3125 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3126 this = self.sql(expression, "this") 3127 order = self.sql(expression, "order") 3128 null_handling = expression.args.get("null_handling") 3129 null_handling = f" {null_handling}" if null_handling else "" 3130 return_type = self.sql(expression, "return_type") 3131 return_type = f" RETURNING {return_type}" if return_type else "" 3132 strict = " STRICT" if expression.args.get("strict") else "" 3133 return self.func( 3134 "JSON_ARRAYAGG", 3135 this, 3136 suffix=f"{order}{null_handling}{return_type}{strict})", 3137 ) 3138 3139 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3140 path = self.sql(expression, "path") 3141 path = f" PATH {path}" if path else "" 3142 nested_schema = self.sql(expression, "nested_schema") 3143 3144 if nested_schema: 3145 return f"NESTED{path} {nested_schema}" 3146 3147 this = self.sql(expression, "this") 3148 kind = self.sql(expression, "kind") 3149 kind = f" {kind}" if kind else "" 3150 return f"{this}{kind}{path}" 3151 3152 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3153 return self.func("COLUMNS", *expression.expressions) 3154 3155 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3156 this = self.sql(expression, "this") 3157 path = self.sql(expression, "path") 3158 path = f", {path}" if path else "" 3159 error_handling = expression.args.get("error_handling") 3160 error_handling = f" {error_handling}" if error_handling else "" 3161 empty_handling = expression.args.get("empty_handling") 3162 empty_handling = f" {empty_handling}" if empty_handling else "" 3163 schema = self.sql(expression, "schema") 3164 return self.func( 3165 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3166 ) 3167 3168 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3169 this = self.sql(expression, "this") 3170 kind = self.sql(expression, "kind") 3171 path = self.sql(expression, "path") 3172 path = f" {path}" if path else "" 3173 as_json = " AS JSON" if expression.args.get("as_json") else "" 3174 return f"{this} {kind}{path}{as_json}" 3175 3176 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3177 this = self.sql(expression, "this") 3178 path = self.sql(expression, "path") 3179 path = f", {path}" if path else "" 3180 expressions = self.expressions(expression) 3181 with_ = ( 3182 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3183 if expressions 3184 else "" 3185 ) 3186 return f"OPENJSON({this}{path}){with_}" 3187 3188 def in_sql(self, expression: exp.In) -> str: 3189 query = expression.args.get("query") 3190 unnest = expression.args.get("unnest") 3191 field = expression.args.get("field") 3192 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3193 3194 if query: 3195 in_sql = self.sql(query) 3196 elif unnest: 3197 in_sql = self.in_unnest_op(unnest) 3198 elif field: 3199 in_sql = self.sql(field) 3200 else: 3201 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3202 3203 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3204 3205 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3206 return f"(SELECT {self.sql(unnest)})" 3207 3208 def interval_sql(self, expression: exp.Interval) -> str: 3209 unit = self.sql(expression, "unit") 3210 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3211 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3212 unit = f" {unit}" if unit else "" 3213 3214 if self.SINGLE_STRING_INTERVAL: 3215 this = expression.this.name if expression.this else "" 3216 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3217 3218 this = self.sql(expression, "this") 3219 if this: 3220 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3221 this = f" {this}" if unwrapped else f" ({this})" 3222 3223 return f"INTERVAL{this}{unit}" 3224 3225 def return_sql(self, expression: exp.Return) -> str: 3226 return f"RETURN {self.sql(expression, 'this')}" 3227 3228 def reference_sql(self, expression: exp.Reference) -> str: 3229 this = self.sql(expression, "this") 3230 expressions = self.expressions(expression, flat=True) 3231 expressions = f"({expressions})" if expressions else "" 3232 options = self.expressions(expression, key="options", flat=True, sep=" ") 3233 options = f" {options}" if options else "" 3234 return f"REFERENCES {this}{expressions}{options}" 3235 3236 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3237 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3238 parent = expression.parent 3239 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3240 return self.func( 3241 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3242 ) 3243 3244 def paren_sql(self, expression: exp.Paren) -> str: 3245 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3246 return f"({sql}{self.seg(')', sep='')}" 3247 3248 def neg_sql(self, expression: exp.Neg) -> str: 3249 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3250 this_sql = self.sql(expression, "this") 3251 sep = " " if this_sql[0] == "-" else "" 3252 return f"-{sep}{this_sql}" 3253 3254 def not_sql(self, expression: exp.Not) -> str: 3255 return f"NOT {self.sql(expression, 'this')}" 3256 3257 def alias_sql(self, expression: exp.Alias) -> str: 3258 alias = self.sql(expression, "alias") 3259 alias = f" AS {alias}" if alias else "" 3260 return f"{self.sql(expression, 'this')}{alias}" 3261 3262 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3263 alias = expression.args["alias"] 3264 3265 parent = expression.parent 3266 pivot = parent and parent.parent 3267 3268 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3269 identifier_alias = isinstance(alias, exp.Identifier) 3270 literal_alias = isinstance(alias, exp.Literal) 3271 3272 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3273 alias.replace(exp.Literal.string(alias.output_name)) 3274 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3275 alias.replace(exp.to_identifier(alias.output_name)) 3276 3277 return self.alias_sql(expression) 3278 3279 def aliases_sql(self, expression: exp.Aliases) -> str: 3280 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3281 3282 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3283 this = self.sql(expression, "this") 3284 index = self.sql(expression, "expression") 3285 return f"{this} AT {index}" 3286 3287 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3288 this = self.sql(expression, "this") 3289 zone = self.sql(expression, "zone") 3290 return f"{this} AT TIME ZONE {zone}" 3291 3292 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3293 this = self.sql(expression, "this") 3294 zone = self.sql(expression, "zone") 3295 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3296 3297 def add_sql(self, expression: exp.Add) -> str: 3298 return self.binary(expression, "+") 3299 3300 def and_sql( 3301 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3302 ) -> str: 3303 return self.connector_sql(expression, "AND", stack) 3304 3305 def or_sql( 3306 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3307 ) -> str: 3308 return self.connector_sql(expression, "OR", stack) 3309 3310 def xor_sql( 3311 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3312 ) -> str: 3313 return self.connector_sql(expression, "XOR", stack) 3314 3315 def connector_sql( 3316 self, 3317 expression: exp.Connector, 3318 op: str, 3319 stack: t.Optional[t.List[str | exp.Expression]] = None, 3320 ) -> str: 3321 if stack is not None: 3322 if expression.expressions: 3323 stack.append(self.expressions(expression, sep=f" {op} ")) 3324 else: 3325 stack.append(expression.right) 3326 if expression.comments and self.comments: 3327 for comment in expression.comments: 3328 if comment: 3329 op += f" /*{self.sanitize_comment(comment)}*/" 3330 stack.extend((op, expression.left)) 3331 return op 3332 3333 stack = [expression] 3334 sqls: t.List[str] = [] 3335 ops = set() 3336 3337 while stack: 3338 node = stack.pop() 3339 if isinstance(node, exp.Connector): 3340 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3341 else: 3342 sql = self.sql(node) 3343 if sqls and sqls[-1] in ops: 3344 sqls[-1] += f" {sql}" 3345 else: 3346 sqls.append(sql) 3347 3348 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3349 return sep.join(sqls) 3350 3351 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3352 return self.binary(expression, "&") 3353 3354 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3355 return self.binary(expression, "<<") 3356 3357 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3358 return f"~{self.sql(expression, 'this')}" 3359 3360 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3361 return self.binary(expression, "|") 3362 3363 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3364 return self.binary(expression, ">>") 3365 3366 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3367 return self.binary(expression, "^") 3368 3369 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3370 format_sql = self.sql(expression, "format") 3371 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3372 to_sql = self.sql(expression, "to") 3373 to_sql = f" {to_sql}" if to_sql else "" 3374 action = self.sql(expression, "action") 3375 action = f" {action}" if action else "" 3376 default = self.sql(expression, "default") 3377 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3378 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3379 3380 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3381 zone = self.sql(expression, "this") 3382 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3383 3384 def collate_sql(self, expression: exp.Collate) -> str: 3385 if self.COLLATE_IS_FUNC: 3386 return self.function_fallback_sql(expression) 3387 return self.binary(expression, "COLLATE") 3388 3389 def command_sql(self, expression: exp.Command) -> str: 3390 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3391 3392 def comment_sql(self, expression: exp.Comment) -> str: 3393 this = self.sql(expression, "this") 3394 kind = expression.args["kind"] 3395 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3396 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3397 expression_sql = self.sql(expression, "expression") 3398 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3399 3400 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3401 this = self.sql(expression, "this") 3402 delete = " DELETE" if expression.args.get("delete") else "" 3403 recompress = self.sql(expression, "recompress") 3404 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3405 to_disk = self.sql(expression, "to_disk") 3406 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3407 to_volume = self.sql(expression, "to_volume") 3408 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3409 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3410 3411 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3412 where = self.sql(expression, "where") 3413 group = self.sql(expression, "group") 3414 aggregates = self.expressions(expression, key="aggregates") 3415 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3416 3417 if not (where or group or aggregates) and len(expression.expressions) == 1: 3418 return f"TTL {self.expressions(expression, flat=True)}" 3419 3420 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3421 3422 def transaction_sql(self, expression: exp.Transaction) -> str: 3423 return "BEGIN" 3424 3425 def commit_sql(self, expression: exp.Commit) -> str: 3426 chain = expression.args.get("chain") 3427 if chain is not None: 3428 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3429 3430 return f"COMMIT{chain or ''}" 3431 3432 def rollback_sql(self, expression: exp.Rollback) -> str: 3433 savepoint = expression.args.get("savepoint") 3434 savepoint = f" TO {savepoint}" if savepoint else "" 3435 return f"ROLLBACK{savepoint}" 3436 3437 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3438 this = self.sql(expression, "this") 3439 3440 dtype = self.sql(expression, "dtype") 3441 if dtype: 3442 collate = self.sql(expression, "collate") 3443 collate = f" COLLATE {collate}" if collate else "" 3444 using = self.sql(expression, "using") 3445 using = f" USING {using}" if using else "" 3446 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3447 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3448 3449 default = self.sql(expression, "default") 3450 if default: 3451 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3452 3453 comment = self.sql(expression, "comment") 3454 if comment: 3455 return f"ALTER COLUMN {this} COMMENT {comment}" 3456 3457 visible = expression.args.get("visible") 3458 if visible: 3459 return f"ALTER COLUMN {this} SET {visible}" 3460 3461 allow_null = expression.args.get("allow_null") 3462 drop = expression.args.get("drop") 3463 3464 if not drop and not allow_null: 3465 self.unsupported("Unsupported ALTER COLUMN syntax") 3466 3467 if allow_null is not None: 3468 keyword = "DROP" if drop else "SET" 3469 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3470 3471 return f"ALTER COLUMN {this} DROP DEFAULT" 3472 3473 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3474 this = self.sql(expression, "this") 3475 3476 visible = expression.args.get("visible") 3477 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3478 3479 return f"ALTER INDEX {this} {visible_sql}" 3480 3481 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3482 this = self.sql(expression, "this") 3483 if not isinstance(expression.this, exp.Var): 3484 this = f"KEY DISTKEY {this}" 3485 return f"ALTER DISTSTYLE {this}" 3486 3487 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3488 compound = " COMPOUND" if expression.args.get("compound") else "" 3489 this = self.sql(expression, "this") 3490 expressions = self.expressions(expression, flat=True) 3491 expressions = f"({expressions})" if expressions else "" 3492 return f"ALTER{compound} SORTKEY {this or expressions}" 3493 3494 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3495 if not self.RENAME_TABLE_WITH_DB: 3496 # Remove db from tables 3497 expression = expression.transform( 3498 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3499 ).assert_is(exp.AlterRename) 3500 this = self.sql(expression, "this") 3501 to_kw = " TO" if include_to else "" 3502 return f"RENAME{to_kw} {this}" 3503 3504 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3505 exists = " IF EXISTS" if expression.args.get("exists") else "" 3506 old_column = self.sql(expression, "this") 3507 new_column = self.sql(expression, "to") 3508 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3509 3510 def alterset_sql(self, expression: exp.AlterSet) -> str: 3511 exprs = self.expressions(expression, flat=True) 3512 if self.ALTER_SET_WRAPPED: 3513 exprs = f"({exprs})" 3514 3515 return f"SET {exprs}" 3516 3517 def alter_sql(self, expression: exp.Alter) -> str: 3518 actions = expression.args["actions"] 3519 3520 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3521 actions[0], exp.ColumnDef 3522 ): 3523 actions_sql = self.expressions(expression, key="actions", flat=True) 3524 actions_sql = f"ADD {actions_sql}" 3525 else: 3526 actions_list = [] 3527 for action in actions: 3528 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3529 action_sql = self.add_column_sql(action) 3530 else: 3531 action_sql = self.sql(action) 3532 if isinstance(action, exp.Query): 3533 action_sql = f"AS {action_sql}" 3534 3535 actions_list.append(action_sql) 3536 3537 actions_sql = self.format_args(*actions_list).lstrip("\n") 3538 3539 exists = " IF EXISTS" if expression.args.get("exists") else "" 3540 on_cluster = self.sql(expression, "cluster") 3541 on_cluster = f" {on_cluster}" if on_cluster else "" 3542 only = " ONLY" if expression.args.get("only") else "" 3543 options = self.expressions(expression, key="options") 3544 options = f", {options}" if options else "" 3545 kind = self.sql(expression, "kind") 3546 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3547 3548 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}" 3549 3550 def add_column_sql(self, expression: exp.Expression) -> str: 3551 sql = self.sql(expression) 3552 if isinstance(expression, exp.Schema): 3553 column_text = " COLUMNS" 3554 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3555 column_text = " COLUMN" 3556 else: 3557 column_text = "" 3558 3559 return f"ADD{column_text} {sql}" 3560 3561 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3562 expressions = self.expressions(expression) 3563 exists = " IF EXISTS " if expression.args.get("exists") else " " 3564 return f"DROP{exists}{expressions}" 3565 3566 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3567 return f"ADD {self.expressions(expression, indent=False)}" 3568 3569 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3570 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3571 location = self.sql(expression, "location") 3572 location = f" {location}" if location else "" 3573 return f"ADD {exists}{self.sql(expression.this)}{location}" 3574 3575 def distinct_sql(self, expression: exp.Distinct) -> str: 3576 this = self.expressions(expression, flat=True) 3577 3578 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3579 case = exp.case() 3580 for arg in expression.expressions: 3581 case = case.when(arg.is_(exp.null()), exp.null()) 3582 this = self.sql(case.else_(f"({this})")) 3583 3584 this = f" {this}" if this else "" 3585 3586 on = self.sql(expression, "on") 3587 on = f" ON {on}" if on else "" 3588 return f"DISTINCT{this}{on}" 3589 3590 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3591 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3592 3593 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3594 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3595 3596 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3597 this_sql = self.sql(expression, "this") 3598 expression_sql = self.sql(expression, "expression") 3599 kind = "MAX" if expression.args.get("max") else "MIN" 3600 return f"{this_sql} HAVING {kind} {expression_sql}" 3601 3602 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3603 return self.sql( 3604 exp.Cast( 3605 this=exp.Div(this=expression.this, expression=expression.expression), 3606 to=exp.DataType(this=exp.DataType.Type.INT), 3607 ) 3608 ) 3609 3610 def dpipe_sql(self, expression: exp.DPipe) -> str: 3611 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3612 return self.func( 3613 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3614 ) 3615 return self.binary(expression, "||") 3616 3617 def div_sql(self, expression: exp.Div) -> str: 3618 l, r = expression.left, expression.right 3619 3620 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3621 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3622 3623 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3624 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3625 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3626 3627 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3628 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3629 return self.sql( 3630 exp.cast( 3631 l / r, 3632 to=exp.DataType.Type.BIGINT, 3633 ) 3634 ) 3635 3636 return self.binary(expression, "/") 3637 3638 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3639 n = exp._wrap(expression.this, exp.Binary) 3640 d = exp._wrap(expression.expression, exp.Binary) 3641 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3642 3643 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3644 return self.binary(expression, "OVERLAPS") 3645 3646 def distance_sql(self, expression: exp.Distance) -> str: 3647 return self.binary(expression, "<->") 3648 3649 def dot_sql(self, expression: exp.Dot) -> str: 3650 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3651 3652 def eq_sql(self, expression: exp.EQ) -> str: 3653 return self.binary(expression, "=") 3654 3655 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3656 return self.binary(expression, ":=") 3657 3658 def escape_sql(self, expression: exp.Escape) -> str: 3659 return self.binary(expression, "ESCAPE") 3660 3661 def glob_sql(self, expression: exp.Glob) -> str: 3662 return self.binary(expression, "GLOB") 3663 3664 def gt_sql(self, expression: exp.GT) -> str: 3665 return self.binary(expression, ">") 3666 3667 def gte_sql(self, expression: exp.GTE) -> str: 3668 return self.binary(expression, ">=") 3669 3670 def is_sql(self, expression: exp.Is) -> str: 3671 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3672 return self.sql( 3673 expression.this if expression.expression.this else exp.not_(expression.this) 3674 ) 3675 return self.binary(expression, "IS") 3676 3677 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3678 this = expression.this 3679 rhs = expression.expression 3680 3681 if isinstance(expression, exp.Like): 3682 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3683 op = "LIKE" 3684 else: 3685 exp_class = exp.ILike 3686 op = "ILIKE" 3687 3688 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3689 exprs = rhs.this.unnest() 3690 3691 if isinstance(exprs, exp.Tuple): 3692 exprs = exprs.expressions 3693 3694 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3695 3696 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3697 for expr in exprs[1:]: 3698 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3699 3700 return self.sql(like_expr) 3701 3702 return self.binary(expression, op) 3703 3704 def like_sql(self, expression: exp.Like) -> str: 3705 return self._like_sql(expression) 3706 3707 def ilike_sql(self, expression: exp.ILike) -> str: 3708 return self._like_sql(expression) 3709 3710 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3711 return self.binary(expression, "SIMILAR TO") 3712 3713 def lt_sql(self, expression: exp.LT) -> str: 3714 return self.binary(expression, "<") 3715 3716 def lte_sql(self, expression: exp.LTE) -> str: 3717 return self.binary(expression, "<=") 3718 3719 def mod_sql(self, expression: exp.Mod) -> str: 3720 return self.binary(expression, "%") 3721 3722 def mul_sql(self, expression: exp.Mul) -> str: 3723 return self.binary(expression, "*") 3724 3725 def neq_sql(self, expression: exp.NEQ) -> str: 3726 return self.binary(expression, "<>") 3727 3728 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3729 return self.binary(expression, "IS NOT DISTINCT FROM") 3730 3731 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3732 return self.binary(expression, "IS DISTINCT FROM") 3733 3734 def slice_sql(self, expression: exp.Slice) -> str: 3735 return self.binary(expression, ":") 3736 3737 def sub_sql(self, expression: exp.Sub) -> str: 3738 return self.binary(expression, "-") 3739 3740 def trycast_sql(self, expression: exp.TryCast) -> str: 3741 return self.cast_sql(expression, safe_prefix="TRY_") 3742 3743 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3744 return self.cast_sql(expression) 3745 3746 def try_sql(self, expression: exp.Try) -> str: 3747 if not self.TRY_SUPPORTED: 3748 self.unsupported("Unsupported TRY function") 3749 return self.sql(expression, "this") 3750 3751 return self.func("TRY", expression.this) 3752 3753 def log_sql(self, expression: exp.Log) -> str: 3754 this = expression.this 3755 expr = expression.expression 3756 3757 if self.dialect.LOG_BASE_FIRST is False: 3758 this, expr = expr, this 3759 elif self.dialect.LOG_BASE_FIRST is None and expr: 3760 if this.name in ("2", "10"): 3761 return self.func(f"LOG{this.name}", expr) 3762 3763 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3764 3765 return self.func("LOG", this, expr) 3766 3767 def use_sql(self, expression: exp.Use) -> str: 3768 kind = self.sql(expression, "kind") 3769 kind = f" {kind}" if kind else "" 3770 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3771 this = f" {this}" if this else "" 3772 return f"USE{kind}{this}" 3773 3774 def binary(self, expression: exp.Binary, op: str) -> str: 3775 sqls: t.List[str] = [] 3776 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3777 binary_type = type(expression) 3778 3779 while stack: 3780 node = stack.pop() 3781 3782 if type(node) is binary_type: 3783 op_func = node.args.get("operator") 3784 if op_func: 3785 op = f"OPERATOR({self.sql(op_func)})" 3786 3787 stack.append(node.right) 3788 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3789 stack.append(node.left) 3790 else: 3791 sqls.append(self.sql(node)) 3792 3793 return "".join(sqls) 3794 3795 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3796 to_clause = self.sql(expression, "to") 3797 if to_clause: 3798 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3799 3800 return self.function_fallback_sql(expression) 3801 3802 def function_fallback_sql(self, expression: exp.Func) -> str: 3803 args = [] 3804 3805 for key in expression.arg_types: 3806 arg_value = expression.args.get(key) 3807 3808 if isinstance(arg_value, list): 3809 for value in arg_value: 3810 args.append(value) 3811 elif arg_value is not None: 3812 args.append(arg_value) 3813 3814 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3815 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3816 else: 3817 name = expression.sql_name() 3818 3819 return self.func(name, *args) 3820 3821 def func( 3822 self, 3823 name: str, 3824 *args: t.Optional[exp.Expression | str], 3825 prefix: str = "(", 3826 suffix: str = ")", 3827 normalize: bool = True, 3828 ) -> str: 3829 name = self.normalize_func(name) if normalize else name 3830 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3831 3832 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3833 arg_sqls = tuple( 3834 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3835 ) 3836 if self.pretty and self.too_wide(arg_sqls): 3837 return self.indent( 3838 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3839 ) 3840 return sep.join(arg_sqls) 3841 3842 def too_wide(self, args: t.Iterable) -> bool: 3843 return sum(len(arg) for arg in args) > self.max_text_width 3844 3845 def format_time( 3846 self, 3847 expression: exp.Expression, 3848 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3849 inverse_time_trie: t.Optional[t.Dict] = None, 3850 ) -> t.Optional[str]: 3851 return format_time( 3852 self.sql(expression, "format"), 3853 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3854 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3855 ) 3856 3857 def expressions( 3858 self, 3859 expression: t.Optional[exp.Expression] = None, 3860 key: t.Optional[str] = None, 3861 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3862 flat: bool = False, 3863 indent: bool = True, 3864 skip_first: bool = False, 3865 skip_last: bool = False, 3866 sep: str = ", ", 3867 prefix: str = "", 3868 dynamic: bool = False, 3869 new_line: bool = False, 3870 ) -> str: 3871 expressions = expression.args.get(key or "expressions") if expression else sqls 3872 3873 if not expressions: 3874 return "" 3875 3876 if flat: 3877 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3878 3879 num_sqls = len(expressions) 3880 result_sqls = [] 3881 3882 for i, e in enumerate(expressions): 3883 sql = self.sql(e, comment=False) 3884 if not sql: 3885 continue 3886 3887 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3888 3889 if self.pretty: 3890 if self.leading_comma: 3891 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3892 else: 3893 result_sqls.append( 3894 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3895 ) 3896 else: 3897 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3898 3899 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3900 if new_line: 3901 result_sqls.insert(0, "") 3902 result_sqls.append("") 3903 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3904 else: 3905 result_sql = "".join(result_sqls) 3906 3907 return ( 3908 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3909 if indent 3910 else result_sql 3911 ) 3912 3913 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3914 flat = flat or isinstance(expression.parent, exp.Properties) 3915 expressions_sql = self.expressions(expression, flat=flat) 3916 if flat: 3917 return f"{op} {expressions_sql}" 3918 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3919 3920 def naked_property(self, expression: exp.Property) -> str: 3921 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3922 if not property_name: 3923 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3924 return f"{property_name} {self.sql(expression, 'this')}" 3925 3926 def tag_sql(self, expression: exp.Tag) -> str: 3927 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3928 3929 def token_sql(self, token_type: TokenType) -> str: 3930 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3931 3932 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3933 this = self.sql(expression, "this") 3934 expressions = self.no_identify(self.expressions, expression) 3935 expressions = ( 3936 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3937 ) 3938 return f"{this}{expressions}" if expressions.strip() != "" else this 3939 3940 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3941 this = self.sql(expression, "this") 3942 expressions = self.expressions(expression, flat=True) 3943 return f"{this}({expressions})" 3944 3945 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3946 return self.binary(expression, "=>") 3947 3948 def when_sql(self, expression: exp.When) -> str: 3949 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3950 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3951 condition = self.sql(expression, "condition") 3952 condition = f" AND {condition}" if condition else "" 3953 3954 then_expression = expression.args.get("then") 3955 if isinstance(then_expression, exp.Insert): 3956 this = self.sql(then_expression, "this") 3957 this = f"INSERT {this}" if this else "INSERT" 3958 then = self.sql(then_expression, "expression") 3959 then = f"{this} VALUES {then}" if then else this 3960 elif isinstance(then_expression, exp.Update): 3961 if isinstance(then_expression.args.get("expressions"), exp.Star): 3962 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3963 else: 3964 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3965 else: 3966 then = self.sql(then_expression) 3967 return f"WHEN {matched}{source}{condition} THEN {then}" 3968 3969 def whens_sql(self, expression: exp.Whens) -> str: 3970 return self.expressions(expression, sep=" ", indent=False) 3971 3972 def merge_sql(self, expression: exp.Merge) -> str: 3973 table = expression.this 3974 table_alias = "" 3975 3976 hints = table.args.get("hints") 3977 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3978 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3979 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3980 3981 this = self.sql(table) 3982 using = f"USING {self.sql(expression, 'using')}" 3983 on = f"ON {self.sql(expression, 'on')}" 3984 whens = self.sql(expression, "whens") 3985 3986 returning = self.sql(expression, "returning") 3987 if returning: 3988 whens = f"{whens}{returning}" 3989 3990 sep = self.sep() 3991 3992 return self.prepend_ctes( 3993 expression, 3994 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3995 ) 3996 3997 @unsupported_args("format") 3998 def tochar_sql(self, expression: exp.ToChar) -> str: 3999 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4000 4001 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4002 if not self.SUPPORTS_TO_NUMBER: 4003 self.unsupported("Unsupported TO_NUMBER function") 4004 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4005 4006 fmt = expression.args.get("format") 4007 if not fmt: 4008 self.unsupported("Conversion format is required for TO_NUMBER") 4009 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4010 4011 return self.func("TO_NUMBER", expression.this, fmt) 4012 4013 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4014 this = self.sql(expression, "this") 4015 kind = self.sql(expression, "kind") 4016 settings_sql = self.expressions(expression, key="settings", sep=" ") 4017 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4018 return f"{this}({kind}{args})" 4019 4020 def dictrange_sql(self, expression: exp.DictRange) -> str: 4021 this = self.sql(expression, "this") 4022 max = self.sql(expression, "max") 4023 min = self.sql(expression, "min") 4024 return f"{this}(MIN {min} MAX {max})" 4025 4026 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4027 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4028 4029 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4030 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4031 4032 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4033 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 4034 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 4035 4036 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4037 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4038 expressions = self.expressions(expression, flat=True) 4039 expressions = f" {self.wrap(expressions)}" if expressions else "" 4040 buckets = self.sql(expression, "buckets") 4041 kind = self.sql(expression, "kind") 4042 buckets = f" BUCKETS {buckets}" if buckets else "" 4043 order = self.sql(expression, "order") 4044 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4045 4046 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4047 return "" 4048 4049 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4050 expressions = self.expressions(expression, key="expressions", flat=True) 4051 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4052 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4053 buckets = self.sql(expression, "buckets") 4054 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4055 4056 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4057 this = self.sql(expression, "this") 4058 having = self.sql(expression, "having") 4059 4060 if having: 4061 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4062 4063 return self.func("ANY_VALUE", this) 4064 4065 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4066 transform = self.func("TRANSFORM", *expression.expressions) 4067 row_format_before = self.sql(expression, "row_format_before") 4068 row_format_before = f" {row_format_before}" if row_format_before else "" 4069 record_writer = self.sql(expression, "record_writer") 4070 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4071 using = f" USING {self.sql(expression, 'command_script')}" 4072 schema = self.sql(expression, "schema") 4073 schema = f" AS {schema}" if schema else "" 4074 row_format_after = self.sql(expression, "row_format_after") 4075 row_format_after = f" {row_format_after}" if row_format_after else "" 4076 record_reader = self.sql(expression, "record_reader") 4077 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4078 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4079 4080 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4081 key_block_size = self.sql(expression, "key_block_size") 4082 if key_block_size: 4083 return f"KEY_BLOCK_SIZE = {key_block_size}" 4084 4085 using = self.sql(expression, "using") 4086 if using: 4087 return f"USING {using}" 4088 4089 parser = self.sql(expression, "parser") 4090 if parser: 4091 return f"WITH PARSER {parser}" 4092 4093 comment = self.sql(expression, "comment") 4094 if comment: 4095 return f"COMMENT {comment}" 4096 4097 visible = expression.args.get("visible") 4098 if visible is not None: 4099 return "VISIBLE" if visible else "INVISIBLE" 4100 4101 engine_attr = self.sql(expression, "engine_attr") 4102 if engine_attr: 4103 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4104 4105 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4106 if secondary_engine_attr: 4107 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4108 4109 self.unsupported("Unsupported index constraint option.") 4110 return "" 4111 4112 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4113 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4114 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4115 4116 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4117 kind = self.sql(expression, "kind") 4118 kind = f"{kind} INDEX" if kind else "INDEX" 4119 this = self.sql(expression, "this") 4120 this = f" {this}" if this else "" 4121 index_type = self.sql(expression, "index_type") 4122 index_type = f" USING {index_type}" if index_type else "" 4123 expressions = self.expressions(expression, flat=True) 4124 expressions = f" ({expressions})" if expressions else "" 4125 options = self.expressions(expression, key="options", sep=" ") 4126 options = f" {options}" if options else "" 4127 return f"{kind}{this}{index_type}{expressions}{options}" 4128 4129 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4130 if self.NVL2_SUPPORTED: 4131 return self.function_fallback_sql(expression) 4132 4133 case = exp.Case().when( 4134 expression.this.is_(exp.null()).not_(copy=False), 4135 expression.args["true"], 4136 copy=False, 4137 ) 4138 else_cond = expression.args.get("false") 4139 if else_cond: 4140 case.else_(else_cond, copy=False) 4141 4142 return self.sql(case) 4143 4144 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4145 this = self.sql(expression, "this") 4146 expr = self.sql(expression, "expression") 4147 iterator = self.sql(expression, "iterator") 4148 condition = self.sql(expression, "condition") 4149 condition = f" IF {condition}" if condition else "" 4150 return f"{this} FOR {expr} IN {iterator}{condition}" 4151 4152 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4153 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4154 4155 def opclass_sql(self, expression: exp.Opclass) -> str: 4156 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4157 4158 def predict_sql(self, expression: exp.Predict) -> str: 4159 model = self.sql(expression, "this") 4160 model = f"MODEL {model}" 4161 table = self.sql(expression, "expression") 4162 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4163 parameters = self.sql(expression, "params_struct") 4164 return self.func("PREDICT", model, table, parameters or None) 4165 4166 def forin_sql(self, expression: exp.ForIn) -> str: 4167 this = self.sql(expression, "this") 4168 expression_sql = self.sql(expression, "expression") 4169 return f"FOR {this} DO {expression_sql}" 4170 4171 def refresh_sql(self, expression: exp.Refresh) -> str: 4172 this = self.sql(expression, "this") 4173 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4174 return f"REFRESH {table}{this}" 4175 4176 def toarray_sql(self, expression: exp.ToArray) -> str: 4177 arg = expression.this 4178 if not arg.type: 4179 from sqlglot.optimizer.annotate_types import annotate_types 4180 4181 arg = annotate_types(arg, dialect=self.dialect) 4182 4183 if arg.is_type(exp.DataType.Type.ARRAY): 4184 return self.sql(arg) 4185 4186 cond_for_null = arg.is_(exp.null()) 4187 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4188 4189 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4190 this = expression.this 4191 time_format = self.format_time(expression) 4192 4193 if time_format: 4194 return self.sql( 4195 exp.cast( 4196 exp.StrToTime(this=this, format=expression.args["format"]), 4197 exp.DataType.Type.TIME, 4198 ) 4199 ) 4200 4201 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4202 return self.sql(this) 4203 4204 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4205 4206 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4207 this = expression.this 4208 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4209 return self.sql(this) 4210 4211 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4212 4213 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4214 this = expression.this 4215 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4216 return self.sql(this) 4217 4218 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4219 4220 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4221 this = expression.this 4222 time_format = self.format_time(expression) 4223 4224 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4225 return self.sql( 4226 exp.cast( 4227 exp.StrToTime(this=this, format=expression.args["format"]), 4228 exp.DataType.Type.DATE, 4229 ) 4230 ) 4231 4232 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4233 return self.sql(this) 4234 4235 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4236 4237 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4238 return self.sql( 4239 exp.func( 4240 "DATEDIFF", 4241 expression.this, 4242 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4243 "day", 4244 ) 4245 ) 4246 4247 def lastday_sql(self, expression: exp.LastDay) -> str: 4248 if self.LAST_DAY_SUPPORTS_DATE_PART: 4249 return self.function_fallback_sql(expression) 4250 4251 unit = expression.text("unit") 4252 if unit and unit != "MONTH": 4253 self.unsupported("Date parts are not supported in LAST_DAY.") 4254 4255 return self.func("LAST_DAY", expression.this) 4256 4257 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4258 from sqlglot.dialects.dialect import unit_to_str 4259 4260 return self.func( 4261 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4262 ) 4263 4264 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4265 if self.CAN_IMPLEMENT_ARRAY_ANY: 4266 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4267 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4268 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4269 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4270 4271 from sqlglot.dialects import Dialect 4272 4273 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4274 if self.dialect.__class__ != Dialect: 4275 self.unsupported("ARRAY_ANY is unsupported") 4276 4277 return self.function_fallback_sql(expression) 4278 4279 def struct_sql(self, expression: exp.Struct) -> str: 4280 expression.set( 4281 "expressions", 4282 [ 4283 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4284 if isinstance(e, exp.PropertyEQ) 4285 else e 4286 for e in expression.expressions 4287 ], 4288 ) 4289 4290 return self.function_fallback_sql(expression) 4291 4292 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4293 low = self.sql(expression, "this") 4294 high = self.sql(expression, "expression") 4295 4296 return f"{low} TO {high}" 4297 4298 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4299 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4300 tables = f" {self.expressions(expression)}" 4301 4302 exists = " IF EXISTS" if expression.args.get("exists") else "" 4303 4304 on_cluster = self.sql(expression, "cluster") 4305 on_cluster = f" {on_cluster}" if on_cluster else "" 4306 4307 identity = self.sql(expression, "identity") 4308 identity = f" {identity} IDENTITY" if identity else "" 4309 4310 option = self.sql(expression, "option") 4311 option = f" {option}" if option else "" 4312 4313 partition = self.sql(expression, "partition") 4314 partition = f" {partition}" if partition else "" 4315 4316 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4317 4318 # This transpiles T-SQL's CONVERT function 4319 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4320 def convert_sql(self, expression: exp.Convert) -> str: 4321 to = expression.this 4322 value = expression.expression 4323 style = expression.args.get("style") 4324 safe = expression.args.get("safe") 4325 strict = expression.args.get("strict") 4326 4327 if not to or not value: 4328 return "" 4329 4330 # Retrieve length of datatype and override to default if not specified 4331 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4332 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4333 4334 transformed: t.Optional[exp.Expression] = None 4335 cast = exp.Cast if strict else exp.TryCast 4336 4337 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4338 if isinstance(style, exp.Literal) and style.is_int: 4339 from sqlglot.dialects.tsql import TSQL 4340 4341 style_value = style.name 4342 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4343 if not converted_style: 4344 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4345 4346 fmt = exp.Literal.string(converted_style) 4347 4348 if to.this == exp.DataType.Type.DATE: 4349 transformed = exp.StrToDate(this=value, format=fmt) 4350 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4351 transformed = exp.StrToTime(this=value, format=fmt) 4352 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4353 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4354 elif to.this == exp.DataType.Type.TEXT: 4355 transformed = exp.TimeToStr(this=value, format=fmt) 4356 4357 if not transformed: 4358 transformed = cast(this=value, to=to, safe=safe) 4359 4360 return self.sql(transformed) 4361 4362 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4363 this = expression.this 4364 if isinstance(this, exp.JSONPathWildcard): 4365 this = self.json_path_part(this) 4366 return f".{this}" if this else "" 4367 4368 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4369 return f".{this}" 4370 4371 this = self.json_path_part(this) 4372 return ( 4373 f"[{this}]" 4374 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4375 else f".{this}" 4376 ) 4377 4378 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4379 this = self.json_path_part(expression.this) 4380 return f"[{this}]" if this else "" 4381 4382 def _simplify_unless_literal(self, expression: E) -> E: 4383 if not isinstance(expression, exp.Literal): 4384 from sqlglot.optimizer.simplify import simplify 4385 4386 expression = simplify(expression, dialect=self.dialect) 4387 4388 return expression 4389 4390 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4391 this = expression.this 4392 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4393 self.unsupported( 4394 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4395 ) 4396 return self.sql(this) 4397 4398 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4399 # The first modifier here will be the one closest to the AggFunc's arg 4400 mods = sorted( 4401 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4402 key=lambda x: 0 4403 if isinstance(x, exp.HavingMax) 4404 else (1 if isinstance(x, exp.Order) else 2), 4405 ) 4406 4407 if mods: 4408 mod = mods[0] 4409 this = expression.__class__(this=mod.this.copy()) 4410 this.meta["inline"] = True 4411 mod.this.replace(this) 4412 return self.sql(expression.this) 4413 4414 agg_func = expression.find(exp.AggFunc) 4415 4416 if agg_func: 4417 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4418 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4419 4420 return f"{self.sql(expression, 'this')} {text}" 4421 4422 def _replace_line_breaks(self, string: str) -> str: 4423 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4424 if self.pretty: 4425 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4426 return string 4427 4428 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4429 option = self.sql(expression, "this") 4430 4431 if expression.expressions: 4432 upper = option.upper() 4433 4434 # Snowflake FILE_FORMAT options are separated by whitespace 4435 sep = " " if upper == "FILE_FORMAT" else ", " 4436 4437 # Databricks copy/format options do not set their list of values with EQ 4438 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4439 values = self.expressions(expression, flat=True, sep=sep) 4440 return f"{option}{op}({values})" 4441 4442 value = self.sql(expression, "expression") 4443 4444 if not value: 4445 return option 4446 4447 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4448 4449 return f"{option}{op}{value}" 4450 4451 def credentials_sql(self, expression: exp.Credentials) -> str: 4452 cred_expr = expression.args.get("credentials") 4453 if isinstance(cred_expr, exp.Literal): 4454 # Redshift case: CREDENTIALS <string> 4455 credentials = self.sql(expression, "credentials") 4456 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4457 else: 4458 # Snowflake case: CREDENTIALS = (...) 4459 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4460 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4461 4462 storage = self.sql(expression, "storage") 4463 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4464 4465 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4466 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4467 4468 iam_role = self.sql(expression, "iam_role") 4469 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4470 4471 region = self.sql(expression, "region") 4472 region = f" REGION {region}" if region else "" 4473 4474 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4475 4476 def copy_sql(self, expression: exp.Copy) -> str: 4477 this = self.sql(expression, "this") 4478 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4479 4480 credentials = self.sql(expression, "credentials") 4481 credentials = self.seg(credentials) if credentials else "" 4482 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4483 files = self.expressions(expression, key="files", flat=True) 4484 4485 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4486 params = self.expressions( 4487 expression, 4488 key="params", 4489 sep=sep, 4490 new_line=True, 4491 skip_last=True, 4492 skip_first=True, 4493 indent=self.COPY_PARAMS_ARE_WRAPPED, 4494 ) 4495 4496 if params: 4497 if self.COPY_PARAMS_ARE_WRAPPED: 4498 params = f" WITH ({params})" 4499 elif not self.pretty: 4500 params = f" {params}" 4501 4502 return f"COPY{this}{kind} {files}{credentials}{params}" 4503 4504 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4505 return "" 4506 4507 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4508 on_sql = "ON" if expression.args.get("on") else "OFF" 4509 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4510 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4511 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4512 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4513 4514 if filter_col or retention_period: 4515 on_sql = self.func("ON", filter_col, retention_period) 4516 4517 return f"DATA_DELETION={on_sql}" 4518 4519 def maskingpolicycolumnconstraint_sql( 4520 self, expression: exp.MaskingPolicyColumnConstraint 4521 ) -> str: 4522 this = self.sql(expression, "this") 4523 expressions = self.expressions(expression, flat=True) 4524 expressions = f" USING ({expressions})" if expressions else "" 4525 return f"MASKING POLICY {this}{expressions}" 4526 4527 def gapfill_sql(self, expression: exp.GapFill) -> str: 4528 this = self.sql(expression, "this") 4529 this = f"TABLE {this}" 4530 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4531 4532 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4533 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4534 4535 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4536 this = self.sql(expression, "this") 4537 expr = expression.expression 4538 4539 if isinstance(expr, exp.Func): 4540 # T-SQL's CLR functions are case sensitive 4541 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4542 else: 4543 expr = self.sql(expression, "expression") 4544 4545 return self.scope_resolution(expr, this) 4546 4547 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4548 if self.PARSE_JSON_NAME is None: 4549 return self.sql(expression.this) 4550 4551 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4552 4553 def rand_sql(self, expression: exp.Rand) -> str: 4554 lower = self.sql(expression, "lower") 4555 upper = self.sql(expression, "upper") 4556 4557 if lower and upper: 4558 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4559 return self.func("RAND", expression.this) 4560 4561 def changes_sql(self, expression: exp.Changes) -> str: 4562 information = self.sql(expression, "information") 4563 information = f"INFORMATION => {information}" 4564 at_before = self.sql(expression, "at_before") 4565 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4566 end = self.sql(expression, "end") 4567 end = f"{self.seg('')}{end}" if end else "" 4568 4569 return f"CHANGES ({information}){at_before}{end}" 4570 4571 def pad_sql(self, expression: exp.Pad) -> str: 4572 prefix = "L" if expression.args.get("is_left") else "R" 4573 4574 fill_pattern = self.sql(expression, "fill_pattern") or None 4575 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4576 fill_pattern = "' '" 4577 4578 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4579 4580 def summarize_sql(self, expression: exp.Summarize) -> str: 4581 table = " TABLE" if expression.args.get("table") else "" 4582 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4583 4584 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4585 generate_series = exp.GenerateSeries(**expression.args) 4586 4587 parent = expression.parent 4588 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4589 parent = parent.parent 4590 4591 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4592 return self.sql(exp.Unnest(expressions=[generate_series])) 4593 4594 if isinstance(parent, exp.Select): 4595 self.unsupported("GenerateSeries projection unnesting is not supported.") 4596 4597 return self.sql(generate_series) 4598 4599 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4600 exprs = expression.expressions 4601 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4602 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4603 else: 4604 rhs = self.expressions(expression) 4605 4606 return self.func(name, expression.this, rhs or None) 4607 4608 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4609 if self.SUPPORTS_CONVERT_TIMEZONE: 4610 return self.function_fallback_sql(expression) 4611 4612 source_tz = expression.args.get("source_tz") 4613 target_tz = expression.args.get("target_tz") 4614 timestamp = expression.args.get("timestamp") 4615 4616 if source_tz and timestamp: 4617 timestamp = exp.AtTimeZone( 4618 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4619 ) 4620 4621 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4622 4623 return self.sql(expr) 4624 4625 def json_sql(self, expression: exp.JSON) -> str: 4626 this = self.sql(expression, "this") 4627 this = f" {this}" if this else "" 4628 4629 _with = expression.args.get("with") 4630 4631 if _with is None: 4632 with_sql = "" 4633 elif not _with: 4634 with_sql = " WITHOUT" 4635 else: 4636 with_sql = " WITH" 4637 4638 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4639 4640 return f"JSON{this}{with_sql}{unique_sql}" 4641 4642 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4643 def _generate_on_options(arg: t.Any) -> str: 4644 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4645 4646 path = self.sql(expression, "path") 4647 returning = self.sql(expression, "returning") 4648 returning = f" RETURNING {returning}" if returning else "" 4649 4650 on_condition = self.sql(expression, "on_condition") 4651 on_condition = f" {on_condition}" if on_condition else "" 4652 4653 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4654 4655 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4656 else_ = "ELSE " if expression.args.get("else_") else "" 4657 condition = self.sql(expression, "expression") 4658 condition = f"WHEN {condition} THEN " if condition else else_ 4659 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4660 return f"{condition}{insert}" 4661 4662 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4663 kind = self.sql(expression, "kind") 4664 expressions = self.seg(self.expressions(expression, sep=" ")) 4665 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4666 return res 4667 4668 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4669 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4670 empty = expression.args.get("empty") 4671 empty = ( 4672 f"DEFAULT {empty} ON EMPTY" 4673 if isinstance(empty, exp.Expression) 4674 else self.sql(expression, "empty") 4675 ) 4676 4677 error = expression.args.get("error") 4678 error = ( 4679 f"DEFAULT {error} ON ERROR" 4680 if isinstance(error, exp.Expression) 4681 else self.sql(expression, "error") 4682 ) 4683 4684 if error and empty: 4685 error = ( 4686 f"{empty} {error}" 4687 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4688 else f"{error} {empty}" 4689 ) 4690 empty = "" 4691 4692 null = self.sql(expression, "null") 4693 4694 return f"{empty}{error}{null}" 4695 4696 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4697 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4698 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4699 4700 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4701 this = self.sql(expression, "this") 4702 path = self.sql(expression, "path") 4703 4704 passing = self.expressions(expression, "passing") 4705 passing = f" PASSING {passing}" if passing else "" 4706 4707 on_condition = self.sql(expression, "on_condition") 4708 on_condition = f" {on_condition}" if on_condition else "" 4709 4710 path = f"{path}{passing}{on_condition}" 4711 4712 return self.func("JSON_EXISTS", this, path) 4713 4714 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4715 array_agg = self.function_fallback_sql(expression) 4716 4717 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4718 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4719 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4720 parent = expression.parent 4721 if isinstance(parent, exp.Filter): 4722 parent_cond = parent.expression.this 4723 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4724 else: 4725 this = expression.this 4726 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4727 if this.find(exp.Column): 4728 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4729 this_sql = ( 4730 self.expressions(this) 4731 if isinstance(this, exp.Distinct) 4732 else self.sql(expression, "this") 4733 ) 4734 4735 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4736 4737 return array_agg 4738 4739 def apply_sql(self, expression: exp.Apply) -> str: 4740 this = self.sql(expression, "this") 4741 expr = self.sql(expression, "expression") 4742 4743 return f"{this} APPLY({expr})" 4744 4745 def grant_sql(self, expression: exp.Grant) -> str: 4746 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4747 4748 kind = self.sql(expression, "kind") 4749 kind = f" {kind}" if kind else "" 4750 4751 securable = self.sql(expression, "securable") 4752 securable = f" {securable}" if securable else "" 4753 4754 principals = self.expressions(expression, key="principals", flat=True) 4755 4756 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4757 4758 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4759 4760 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4761 this = self.sql(expression, "this") 4762 columns = self.expressions(expression, flat=True) 4763 columns = f"({columns})" if columns else "" 4764 4765 return f"{this}{columns}" 4766 4767 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4768 this = self.sql(expression, "this") 4769 4770 kind = self.sql(expression, "kind") 4771 kind = f"{kind} " if kind else "" 4772 4773 return f"{kind}{this}" 4774 4775 def columns_sql(self, expression: exp.Columns): 4776 func = self.function_fallback_sql(expression) 4777 if expression.args.get("unpack"): 4778 func = f"*{func}" 4779 4780 return func 4781 4782 def overlay_sql(self, expression: exp.Overlay): 4783 this = self.sql(expression, "this") 4784 expr = self.sql(expression, "expression") 4785 from_sql = self.sql(expression, "from") 4786 for_sql = self.sql(expression, "for") 4787 for_sql = f" FOR {for_sql}" if for_sql else "" 4788 4789 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4790 4791 @unsupported_args("format") 4792 def todouble_sql(self, expression: exp.ToDouble) -> str: 4793 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4794 4795 def string_sql(self, expression: exp.String) -> str: 4796 this = expression.this 4797 zone = expression.args.get("zone") 4798 4799 if zone: 4800 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4801 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4802 # set for source_tz to transpile the time conversion before the STRING cast 4803 this = exp.ConvertTimezone( 4804 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4805 ) 4806 4807 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4808 4809 def median_sql(self, expression: exp.Median): 4810 if not self.SUPPORTS_MEDIAN: 4811 return self.sql( 4812 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4813 ) 4814 4815 return self.function_fallback_sql(expression) 4816 4817 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4818 filler = self.sql(expression, "this") 4819 filler = f" {filler}" if filler else "" 4820 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4821 return f"TRUNCATE{filler} {with_count}" 4822 4823 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4824 if self.SUPPORTS_UNIX_SECONDS: 4825 return self.function_fallback_sql(expression) 4826 4827 start_ts = exp.cast( 4828 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4829 ) 4830 4831 return self.sql( 4832 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4833 ) 4834 4835 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4836 dim = expression.expression 4837 4838 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4839 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4840 if not (dim.is_int and dim.name == "1"): 4841 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4842 dim = None 4843 4844 # If dimension is required but not specified, default initialize it 4845 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4846 dim = exp.Literal.number(1) 4847 4848 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4849 4850 def attach_sql(self, expression: exp.Attach) -> str: 4851 this = self.sql(expression, "this") 4852 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4853 expressions = self.expressions(expression) 4854 expressions = f" ({expressions})" if expressions else "" 4855 4856 return f"ATTACH{exists_sql} {this}{expressions}" 4857 4858 def detach_sql(self, expression: exp.Detach) -> str: 4859 this = self.sql(expression, "this") 4860 # the DATABASE keyword is required if IF EXISTS is set 4861 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4862 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4863 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4864 4865 return f"DETACH{exists_sql} {this}" 4866 4867 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4868 this = self.sql(expression, "this") 4869 value = self.sql(expression, "expression") 4870 value = f" {value}" if value else "" 4871 return f"{this}{value}" 4872 4873 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4874 this_sql = self.sql(expression, "this") 4875 if isinstance(expression.this, exp.Table): 4876 this_sql = f"TABLE {this_sql}" 4877 4878 return self.func( 4879 "FEATURES_AT_TIME", 4880 this_sql, 4881 expression.args.get("time"), 4882 expression.args.get("num_rows"), 4883 expression.args.get("ignore_feature_nulls"), 4884 ) 4885 4886 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4887 return ( 4888 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4889 ) 4890 4891 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4892 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4893 encode = f"{encode} {self.sql(expression, 'this')}" 4894 4895 properties = expression.args.get("properties") 4896 if properties: 4897 encode = f"{encode} {self.properties(properties)}" 4898 4899 return encode 4900 4901 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4902 this = self.sql(expression, "this") 4903 include = f"INCLUDE {this}" 4904 4905 column_def = self.sql(expression, "column_def") 4906 if column_def: 4907 include = f"{include} {column_def}" 4908 4909 alias = self.sql(expression, "alias") 4910 if alias: 4911 include = f"{include} AS {alias}" 4912 4913 return include 4914 4915 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4916 name = f"NAME {self.sql(expression, 'this')}" 4917 return self.func("XMLELEMENT", name, *expression.expressions) 4918 4919 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 4920 this = self.sql(expression, "this") 4921 expr = self.sql(expression, "expression") 4922 expr = f"({expr})" if expr else "" 4923 return f"{this}{expr}" 4924 4925 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4926 partitions = self.expressions(expression, "partition_expressions") 4927 create = self.expressions(expression, "create_expressions") 4928 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4929 4930 def partitionbyrangepropertydynamic_sql( 4931 self, expression: exp.PartitionByRangePropertyDynamic 4932 ) -> str: 4933 start = self.sql(expression, "start") 4934 end = self.sql(expression, "end") 4935 4936 every = expression.args["every"] 4937 if isinstance(every, exp.Interval) and every.this.is_string: 4938 every.this.replace(exp.Literal.number(every.name)) 4939 4940 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4941 4942 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4943 name = self.sql(expression, "this") 4944 values = self.expressions(expression, flat=True) 4945 4946 return f"NAME {name} VALUE {values}" 4947 4948 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4949 kind = self.sql(expression, "kind") 4950 sample = self.sql(expression, "sample") 4951 return f"SAMPLE {sample} {kind}" 4952 4953 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4954 kind = self.sql(expression, "kind") 4955 option = self.sql(expression, "option") 4956 option = f" {option}" if option else "" 4957 this = self.sql(expression, "this") 4958 this = f" {this}" if this else "" 4959 columns = self.expressions(expression) 4960 columns = f" {columns}" if columns else "" 4961 return f"{kind}{option} STATISTICS{this}{columns}" 4962 4963 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4964 this = self.sql(expression, "this") 4965 columns = self.expressions(expression) 4966 inner_expression = self.sql(expression, "expression") 4967 inner_expression = f" {inner_expression}" if inner_expression else "" 4968 update_options = self.sql(expression, "update_options") 4969 update_options = f" {update_options} UPDATE" if update_options else "" 4970 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4971 4972 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4973 kind = self.sql(expression, "kind") 4974 kind = f" {kind}" if kind else "" 4975 return f"DELETE{kind} STATISTICS" 4976 4977 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4978 inner_expression = self.sql(expression, "expression") 4979 return f"LIST CHAINED ROWS{inner_expression}" 4980 4981 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4982 kind = self.sql(expression, "kind") 4983 this = self.sql(expression, "this") 4984 this = f" {this}" if this else "" 4985 inner_expression = self.sql(expression, "expression") 4986 return f"VALIDATE {kind}{this}{inner_expression}" 4987 4988 def analyze_sql(self, expression: exp.Analyze) -> str: 4989 options = self.expressions(expression, key="options", sep=" ") 4990 options = f" {options}" if options else "" 4991 kind = self.sql(expression, "kind") 4992 kind = f" {kind}" if kind else "" 4993 this = self.sql(expression, "this") 4994 this = f" {this}" if this else "" 4995 mode = self.sql(expression, "mode") 4996 mode = f" {mode}" if mode else "" 4997 properties = self.sql(expression, "properties") 4998 properties = f" {properties}" if properties else "" 4999 partition = self.sql(expression, "partition") 5000 partition = f" {partition}" if partition else "" 5001 inner_expression = self.sql(expression, "expression") 5002 inner_expression = f" {inner_expression}" if inner_expression else "" 5003 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5004 5005 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5006 this = self.sql(expression, "this") 5007 namespaces = self.expressions(expression, key="namespaces") 5008 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5009 passing = self.expressions(expression, key="passing") 5010 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5011 columns = self.expressions(expression, key="columns") 5012 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5013 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5014 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5015 5016 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5017 this = self.sql(expression, "this") 5018 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5019 5020 def export_sql(self, expression: exp.Export) -> str: 5021 this = self.sql(expression, "this") 5022 connection = self.sql(expression, "connection") 5023 connection = f"WITH CONNECTION {connection} " if connection else "" 5024 options = self.sql(expression, "options") 5025 return f"EXPORT DATA {connection}{options} AS {this}" 5026 5027 def declare_sql(self, expression: exp.Declare) -> str: 5028 return f"DECLARE {self.expressions(expression, flat=True)}" 5029 5030 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5031 variable = self.sql(expression, "this") 5032 default = self.sql(expression, "default") 5033 default = f" = {default}" if default else "" 5034 5035 kind = self.sql(expression, "kind") 5036 if isinstance(expression.args.get("kind"), exp.Schema): 5037 kind = f"TABLE {kind}" 5038 5039 return f"{variable} AS {kind}{default}" 5040 5041 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5042 kind = self.sql(expression, "kind") 5043 this = self.sql(expression, "this") 5044 set = self.sql(expression, "expression") 5045 using = self.sql(expression, "using") 5046 using = f" USING {using}" if using else "" 5047 5048 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5049 5050 return f"{kind_sql} {this} SET {set}{using}" 5051 5052 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5053 params = self.expressions(expression, key="params", flat=True) 5054 return self.func(expression.name, *expression.expressions) + f"({params})" 5055 5056 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5057 return self.func(expression.name, *expression.expressions) 5058 5059 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5060 return self.anonymousaggfunc_sql(expression) 5061 5062 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5063 return self.parameterizedagg_sql(expression) 5064 5065 def show_sql(self, expression: exp.Show) -> str: 5066 self.unsupported("Unsupported SHOW statement") 5067 return "" 5068 5069 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5070 # Snowflake GET/PUT statements: 5071 # PUT <file> <internalStage> <properties> 5072 # GET <internalStage> <file> <properties> 5073 props = expression.args.get("properties") 5074 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5075 this = self.sql(expression, "this") 5076 target = self.sql(expression, "target") 5077 5078 if isinstance(expression, exp.Put): 5079 return f"PUT {this} {target}{props_sql}" 5080 else: 5081 return f"GET {target} {this}{props_sql}" 5082 5083 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5084 this = self.sql(expression, "this") 5085 expr = self.sql(expression, "expression") 5086 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5087 return f"TRANSLATE({this} USING {expr}{with_error})" 5088 5089 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5090 if self.SUPPORTS_DECODE_CASE: 5091 return self.func("DECODE", *expression.expressions) 5092 5093 expression, *expressions = expression.expressions 5094 5095 ifs = [] 5096 for search, result in zip(expressions[::2], expressions[1::2]): 5097 if isinstance(search, exp.Literal): 5098 ifs.append(exp.If(this=expression.eq(search), true=result)) 5099 elif isinstance(search, exp.Null): 5100 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5101 else: 5102 if isinstance(search, exp.Binary): 5103 search = exp.paren(search) 5104 5105 cond = exp.or_( 5106 expression.eq(search), 5107 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5108 copy=False, 5109 ) 5110 ifs.append(exp.If(this=cond, true=result)) 5111 5112 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5113 return self.sql(case) 5114 5115 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5116 this = self.sql(expression, "this") 5117 this = self.seg(this, sep="") 5118 dimensions = self.expressions( 5119 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5120 ) 5121 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5122 metrics = self.expressions( 5123 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5124 ) 5125 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5126 where = self.sql(expression, "where") 5127 where = self.seg(f"WHERE {where}") if where else "" 5128 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}" 5129 5130 def getextract_sql(self, expression: exp.GetExtract) -> str: 5131 this = expression.this 5132 expr = expression.expression 5133 5134 if not this.type or not expression.type: 5135 from sqlglot.optimizer.annotate_types import annotate_types 5136 5137 this = annotate_types(this, dialect=self.dialect) 5138 5139 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5140 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5141 5142 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5143 5144 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5145 return self.sql( 5146 exp.DateAdd( 5147 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5148 expression=expression.this, 5149 unit=exp.var("DAY"), 5150 ) 5151 ) 5152 5153 def space_sql(self: Generator, expression: exp.Space) -> str: 5154 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this))
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)
722 def __init__( 723 self, 724 pretty: t.Optional[bool] = None, 725 identify: str | bool = False, 726 normalize: bool = False, 727 pad: int = 2, 728 indent: int = 2, 729 normalize_functions: t.Optional[str | bool] = None, 730 unsupported_level: ErrorLevel = ErrorLevel.WARN, 731 max_unsupported: int = 3, 732 leading_comma: bool = False, 733 max_text_width: int = 80, 734 comments: bool = True, 735 dialect: DialectType = None, 736 ): 737 import sqlglot 738 from sqlglot.dialects import Dialect 739 740 self.pretty = pretty if pretty is not None else sqlglot.pretty 741 self.identify = identify 742 self.normalize = normalize 743 self.pad = pad 744 self._indent = indent 745 self.unsupported_level = unsupported_level 746 self.max_unsupported = max_unsupported 747 self.leading_comma = leading_comma 748 self.max_text_width = max_text_width 749 self.comments = comments 750 self.dialect = Dialect.get_or_raise(dialect) 751 752 # This is both a Dialect property and a Generator argument, so we prioritize the latter 753 self.normalize_functions = ( 754 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 755 ) 756 757 self.unsupported_messages: t.List[str] = [] 758 self._escaped_quote_end: str = ( 759 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 760 ) 761 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 762 763 self._next_name = name_sequence("_t") 764 765 self._identifier_start = self.dialect.IDENTIFIER_START 766 self._identifier_end = self.dialect.IDENTIFIER_END 767 768 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.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'>, <class 'sqlglot.expressions.JSONPathSlice'>}
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.NCHAR: 'NCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.VARCHAR: 'VARCHAR'>}
770 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 771 """ 772 Generates the SQL string corresponding to the given syntax tree. 773 774 Args: 775 expression: The syntax tree. 776 copy: Whether to copy the expression. The generator performs mutations so 777 it is safer to copy. 778 779 Returns: 780 The SQL string corresponding to `expression`. 781 """ 782 if copy: 783 expression = expression.copy() 784 785 expression = self.preprocess(expression) 786 787 self.unsupported_messages = [] 788 sql = self.sql(expression).strip() 789 790 if self.pretty: 791 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 792 793 if self.unsupported_level == ErrorLevel.IGNORE: 794 return sql 795 796 if self.unsupported_level == ErrorLevel.WARN: 797 for msg in self.unsupported_messages: 798 logger.warning(msg) 799 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 800 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 801 802 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:
804 def preprocess(self, expression: exp.Expression) -> exp.Expression: 805 """Apply generic preprocessing transformations to a given expression.""" 806 expression = self._move_ctes_to_top_level(expression) 807 808 if self.ENSURE_BOOLS: 809 from sqlglot.transforms import ensure_bools 810 811 expression = ensure_bools(expression) 812 813 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
837 def sanitize_comment(self, comment: str) -> str: 838 comment = " " + comment if comment[0].strip() else comment 839 comment = comment + " " if comment[-1].strip() else comment 840 841 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 842 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 843 comment = comment.replace("*/", "* /") 844 845 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
847 def maybe_comment( 848 self, 849 sql: str, 850 expression: t.Optional[exp.Expression] = None, 851 comments: t.Optional[t.List[str]] = None, 852 separated: bool = False, 853 ) -> str: 854 comments = ( 855 ((expression and expression.comments) if comments is None else comments) # type: ignore 856 if self.comments 857 else None 858 ) 859 860 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 861 return sql 862 863 comments_sql = " ".join( 864 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 865 ) 866 867 if not comments_sql: 868 return sql 869 870 comments_sql = self._replace_line_breaks(comments_sql) 871 872 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 873 return ( 874 f"{self.sep()}{comments_sql}{sql}" 875 if not sql or sql[0].isspace() 876 else f"{comments_sql}{self.sep()}{sql}" 877 ) 878 879 return f"{sql} {comments_sql}"
881 def wrap(self, expression: exp.Expression | str) -> str: 882 this_sql = ( 883 self.sql(expression) 884 if isinstance(expression, exp.UNWRAPPED_QUERIES) 885 else self.sql(expression, "this") 886 ) 887 if not this_sql: 888 return "()" 889 890 this_sql = self.indent(this_sql, level=1, pad=0) 891 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:
907 def indent( 908 self, 909 sql: str, 910 level: int = 0, 911 pad: t.Optional[int] = None, 912 skip_first: bool = False, 913 skip_last: bool = False, 914 ) -> str: 915 if not self.pretty or not sql: 916 return sql 917 918 pad = self.pad if pad is None else pad 919 lines = sql.split("\n") 920 921 return "\n".join( 922 ( 923 line 924 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 925 else f"{' ' * (level * self._indent + pad)}{line}" 926 ) 927 for i, line in enumerate(lines) 928 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
930 def sql( 931 self, 932 expression: t.Optional[str | exp.Expression], 933 key: t.Optional[str] = None, 934 comment: bool = True, 935 ) -> str: 936 if not expression: 937 return "" 938 939 if isinstance(expression, str): 940 return expression 941 942 if key: 943 value = expression.args.get(key) 944 if value: 945 return self.sql(value) 946 return "" 947 948 transform = self.TRANSFORMS.get(expression.__class__) 949 950 if callable(transform): 951 sql = transform(self, expression) 952 elif isinstance(expression, exp.Expression): 953 exp_handler_name = f"{expression.key}_sql" 954 955 if hasattr(self, exp_handler_name): 956 sql = getattr(self, exp_handler_name)(expression) 957 elif isinstance(expression, exp.Func): 958 sql = self.function_fallback_sql(expression) 959 elif isinstance(expression, exp.Property): 960 sql = self.property_sql(expression) 961 else: 962 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 963 else: 964 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 965 966 return self.maybe_comment(sql, expression) if self.comments and comment else sql
973 def cache_sql(self, expression: exp.Cache) -> str: 974 lazy = " LAZY" if expression.args.get("lazy") else "" 975 table = self.sql(expression, "this") 976 options = expression.args.get("options") 977 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 978 sql = self.sql(expression, "expression") 979 sql = f" AS{self.sep()}{sql}" if sql else "" 980 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 981 return self.prepend_ctes(expression, sql)
983 def characterset_sql(self, expression: exp.CharacterSet) -> str: 984 if isinstance(expression.parent, exp.Cast): 985 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 986 default = "DEFAULT " if expression.args.get("default") else "" 987 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1001 def column_sql(self, expression: exp.Column) -> str: 1002 join_mark = " (+)" if expression.args.get("join_mark") else "" 1003 1004 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1005 join_mark = "" 1006 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1007 1008 return f"{self.column_parts(expression)}{join_mark}"
1016 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1017 column = self.sql(expression, "this") 1018 kind = self.sql(expression, "kind") 1019 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1020 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1021 kind = f"{sep}{kind}" if kind else "" 1022 constraints = f" {constraints}" if constraints else "" 1023 position = self.sql(expression, "position") 1024 position = f" {position}" if position else "" 1025 1026 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1027 kind = "" 1028 1029 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1036 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1037 this = self.sql(expression, "this") 1038 if expression.args.get("not_null"): 1039 persisted = " PERSISTED NOT NULL" 1040 elif expression.args.get("persisted"): 1041 persisted = " PERSISTED" 1042 else: 1043 persisted = "" 1044 1045 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1058 def generatedasidentitycolumnconstraint_sql( 1059 self, expression: exp.GeneratedAsIdentityColumnConstraint 1060 ) -> str: 1061 this = "" 1062 if expression.this is not None: 1063 on_null = " ON NULL" if expression.args.get("on_null") else "" 1064 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1065 1066 start = expression.args.get("start") 1067 start = f"START WITH {start}" if start else "" 1068 increment = expression.args.get("increment") 1069 increment = f" INCREMENT BY {increment}" if increment else "" 1070 minvalue = expression.args.get("minvalue") 1071 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1072 maxvalue = expression.args.get("maxvalue") 1073 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1074 cycle = expression.args.get("cycle") 1075 cycle_sql = "" 1076 1077 if cycle is not None: 1078 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1079 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1080 1081 sequence_opts = "" 1082 if start or increment or cycle_sql: 1083 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1084 sequence_opts = f" ({sequence_opts.strip()})" 1085 1086 expr = self.sql(expression, "expression") 1087 expr = f"({expr})" if expr else "IDENTITY" 1088 1089 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1091 def generatedasrowcolumnconstraint_sql( 1092 self, expression: exp.GeneratedAsRowColumnConstraint 1093 ) -> str: 1094 start = "START" if expression.args.get("start") else "END" 1095 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1096 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:
1106 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1107 desc = expression.args.get("desc") 1108 if desc is not None: 1109 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1110 options = self.expressions(expression, key="options", flat=True, sep=" ") 1111 options = f" {options}" if options else "" 1112 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1114 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1115 this = self.sql(expression, "this") 1116 this = f" {this}" if this else "" 1117 index_type = expression.args.get("index_type") 1118 index_type = f" USING {index_type}" if index_type else "" 1119 on_conflict = self.sql(expression, "on_conflict") 1120 on_conflict = f" {on_conflict}" if on_conflict else "" 1121 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1122 options = self.expressions(expression, key="options", flat=True, sep=" ") 1123 options = f" {options}" if options else "" 1124 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1129 def create_sql(self, expression: exp.Create) -> str: 1130 kind = self.sql(expression, "kind") 1131 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1132 properties = expression.args.get("properties") 1133 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1134 1135 this = self.createable_sql(expression, properties_locs) 1136 1137 properties_sql = "" 1138 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1139 exp.Properties.Location.POST_WITH 1140 ): 1141 properties_sql = self.sql( 1142 exp.Properties( 1143 expressions=[ 1144 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1145 *properties_locs[exp.Properties.Location.POST_WITH], 1146 ] 1147 ) 1148 ) 1149 1150 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1151 properties_sql = self.sep() + properties_sql 1152 elif not self.pretty: 1153 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1154 properties_sql = f" {properties_sql}" 1155 1156 begin = " BEGIN" if expression.args.get("begin") else "" 1157 end = " END" if expression.args.get("end") else "" 1158 1159 expression_sql = self.sql(expression, "expression") 1160 if expression_sql: 1161 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1162 1163 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1164 postalias_props_sql = "" 1165 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1166 postalias_props_sql = self.properties( 1167 exp.Properties( 1168 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1169 ), 1170 wrapped=False, 1171 ) 1172 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1173 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1174 1175 postindex_props_sql = "" 1176 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1177 postindex_props_sql = self.properties( 1178 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1179 wrapped=False, 1180 prefix=" ", 1181 ) 1182 1183 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1184 indexes = f" {indexes}" if indexes else "" 1185 index_sql = indexes + postindex_props_sql 1186 1187 replace = " OR REPLACE" if expression.args.get("replace") else "" 1188 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1189 unique = " UNIQUE" if expression.args.get("unique") else "" 1190 1191 clustered = expression.args.get("clustered") 1192 if clustered is None: 1193 clustered_sql = "" 1194 elif clustered: 1195 clustered_sql = " CLUSTERED COLUMNSTORE" 1196 else: 1197 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1198 1199 postcreate_props_sql = "" 1200 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1201 postcreate_props_sql = self.properties( 1202 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1203 sep=" ", 1204 prefix=" ", 1205 wrapped=False, 1206 ) 1207 1208 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1209 1210 postexpression_props_sql = "" 1211 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1212 postexpression_props_sql = self.properties( 1213 exp.Properties( 1214 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1215 ), 1216 sep=" ", 1217 prefix=" ", 1218 wrapped=False, 1219 ) 1220 1221 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1222 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1223 no_schema_binding = ( 1224 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1225 ) 1226 1227 clone = self.sql(expression, "clone") 1228 clone = f" {clone}" if clone else "" 1229 1230 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1231 properties_expression = f"{expression_sql}{properties_sql}" 1232 else: 1233 properties_expression = f"{properties_sql}{expression_sql}" 1234 1235 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1236 return self.prepend_ctes(expression, expression_sql)
1238 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1239 start = self.sql(expression, "start") 1240 start = f"START WITH {start}" if start else "" 1241 increment = self.sql(expression, "increment") 1242 increment = f" INCREMENT BY {increment}" if increment else "" 1243 minvalue = self.sql(expression, "minvalue") 1244 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1245 maxvalue = self.sql(expression, "maxvalue") 1246 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1247 owned = self.sql(expression, "owned") 1248 owned = f" OWNED BY {owned}" if owned else "" 1249 1250 cache = expression.args.get("cache") 1251 if cache is None: 1252 cache_str = "" 1253 elif cache is True: 1254 cache_str = " CACHE" 1255 else: 1256 cache_str = f" CACHE {cache}" 1257 1258 options = self.expressions(expression, key="options", flat=True, sep=" ") 1259 options = f" {options}" if options else "" 1260 1261 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1263 def clone_sql(self, expression: exp.Clone) -> str: 1264 this = self.sql(expression, "this") 1265 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1266 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1267 return f"{shallow}{keyword} {this}"
1269 def describe_sql(self, expression: exp.Describe) -> str: 1270 style = expression.args.get("style") 1271 style = f" {style}" if style else "" 1272 partition = self.sql(expression, "partition") 1273 partition = f" {partition}" if partition else "" 1274 format = self.sql(expression, "format") 1275 format = f" {format}" if format else "" 1276 1277 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1289 def with_sql(self, expression: exp.With) -> str: 1290 sql = self.expressions(expression, flat=True) 1291 recursive = ( 1292 "RECURSIVE " 1293 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1294 else "" 1295 ) 1296 search = self.sql(expression, "search") 1297 search = f" {search}" if search else "" 1298 1299 return f"WITH {recursive}{sql}{search}"
1301 def cte_sql(self, expression: exp.CTE) -> str: 1302 alias = expression.args.get("alias") 1303 if alias: 1304 alias.add_comments(expression.pop_comments()) 1305 1306 alias_sql = self.sql(expression, "alias") 1307 1308 materialized = expression.args.get("materialized") 1309 if materialized is False: 1310 materialized = "NOT MATERIALIZED " 1311 elif materialized: 1312 materialized = "MATERIALIZED " 1313 1314 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1316 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1317 alias = self.sql(expression, "this") 1318 columns = self.expressions(expression, key="columns", flat=True) 1319 columns = f"({columns})" if columns else "" 1320 1321 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1322 columns = "" 1323 self.unsupported("Named columns are not supported in table alias.") 1324 1325 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1326 alias = self._next_name() 1327 1328 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1336 def hexstring_sql( 1337 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1338 ) -> str: 1339 this = self.sql(expression, "this") 1340 is_integer_type = expression.args.get("is_integer") 1341 1342 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1343 not self.dialect.HEX_START and not binary_function_repr 1344 ): 1345 # Integer representation will be returned if: 1346 # - The read dialect treats the hex value as integer literal but not the write 1347 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1348 return f"{int(this, 16)}" 1349 1350 if not is_integer_type: 1351 # Read dialect treats the hex value as BINARY/BLOB 1352 if binary_function_repr: 1353 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1354 return self.func(binary_function_repr, exp.Literal.string(this)) 1355 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1356 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1357 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1358 1359 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1367 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1368 this = self.sql(expression, "this") 1369 escape = expression.args.get("escape") 1370 1371 if self.dialect.UNICODE_START: 1372 escape_substitute = r"\\\1" 1373 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1374 else: 1375 escape_substitute = r"\\u\1" 1376 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1377 1378 if escape: 1379 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1380 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1381 else: 1382 escape_pattern = ESCAPED_UNICODE_RE 1383 escape_sql = "" 1384 1385 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1386 this = escape_pattern.sub(escape_substitute, this) 1387 1388 return f"{left_quote}{this}{right_quote}{escape_sql}"
1390 def rawstring_sql(self, expression: exp.RawString) -> str: 1391 string = expression.this 1392 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1393 string = string.replace("\\", "\\\\") 1394 1395 string = self.escape_str(string, escape_backslash=False) 1396 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1404 def datatype_sql(self, expression: exp.DataType) -> str: 1405 nested = "" 1406 values = "" 1407 interior = self.expressions(expression, flat=True) 1408 1409 type_value = expression.this 1410 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1411 type_sql = self.sql(expression, "kind") 1412 else: 1413 type_sql = ( 1414 self.TYPE_MAPPING.get(type_value, type_value.value) 1415 if isinstance(type_value, exp.DataType.Type) 1416 else type_value 1417 ) 1418 1419 if interior: 1420 if expression.args.get("nested"): 1421 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1422 if expression.args.get("values") is not None: 1423 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1424 values = self.expressions(expression, key="values", flat=True) 1425 values = f"{delimiters[0]}{values}{delimiters[1]}" 1426 elif type_value == exp.DataType.Type.INTERVAL: 1427 nested = f" {interior}" 1428 else: 1429 nested = f"({interior})" 1430 1431 type_sql = f"{type_sql}{nested}{values}" 1432 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1433 exp.DataType.Type.TIMETZ, 1434 exp.DataType.Type.TIMESTAMPTZ, 1435 ): 1436 type_sql = f"{type_sql} WITH TIME ZONE" 1437 1438 return type_sql
1440 def directory_sql(self, expression: exp.Directory) -> str: 1441 local = "LOCAL " if expression.args.get("local") else "" 1442 row_format = self.sql(expression, "row_format") 1443 row_format = f" {row_format}" if row_format else "" 1444 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1446 def delete_sql(self, expression: exp.Delete) -> str: 1447 this = self.sql(expression, "this") 1448 this = f" FROM {this}" if this else "" 1449 using = self.sql(expression, "using") 1450 using = f" USING {using}" if using else "" 1451 cluster = self.sql(expression, "cluster") 1452 cluster = f" {cluster}" if cluster else "" 1453 where = self.sql(expression, "where") 1454 returning = self.sql(expression, "returning") 1455 limit = self.sql(expression, "limit") 1456 tables = self.expressions(expression, key="tables") 1457 tables = f" {tables}" if tables else "" 1458 if self.RETURNING_END: 1459 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1460 else: 1461 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1462 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1464 def drop_sql(self, expression: exp.Drop) -> str: 1465 this = self.sql(expression, "this") 1466 expressions = self.expressions(expression, flat=True) 1467 expressions = f" ({expressions})" if expressions else "" 1468 kind = expression.args["kind"] 1469 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1470 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1471 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1472 on_cluster = self.sql(expression, "cluster") 1473 on_cluster = f" {on_cluster}" if on_cluster else "" 1474 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1475 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1476 cascade = " CASCADE" if expression.args.get("cascade") else "" 1477 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1478 purge = " PURGE" if expression.args.get("purge") else "" 1479 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1481 def set_operation(self, expression: exp.SetOperation) -> str: 1482 op_type = type(expression) 1483 op_name = op_type.key.upper() 1484 1485 distinct = expression.args.get("distinct") 1486 if ( 1487 distinct is False 1488 and op_type in (exp.Except, exp.Intersect) 1489 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1490 ): 1491 self.unsupported(f"{op_name} ALL is not supported") 1492 1493 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1494 1495 if distinct is None: 1496 distinct = default_distinct 1497 if distinct is None: 1498 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1499 1500 if distinct is default_distinct: 1501 distinct_or_all = "" 1502 else: 1503 distinct_or_all = " DISTINCT" if distinct else " ALL" 1504 1505 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1506 side_kind = f"{side_kind} " if side_kind else "" 1507 1508 by_name = " BY NAME" if expression.args.get("by_name") else "" 1509 on = self.expressions(expression, key="on", flat=True) 1510 on = f" ON ({on})" if on else "" 1511 1512 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1514 def set_operations(self, expression: exp.SetOperation) -> str: 1515 if not self.SET_OP_MODIFIERS: 1516 limit = expression.args.get("limit") 1517 order = expression.args.get("order") 1518 1519 if limit or order: 1520 select = self._move_ctes_to_top_level( 1521 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1522 ) 1523 1524 if limit: 1525 select = select.limit(limit.pop(), copy=False) 1526 if order: 1527 select = select.order_by(order.pop(), copy=False) 1528 return self.sql(select) 1529 1530 sqls: t.List[str] = [] 1531 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1532 1533 while stack: 1534 node = stack.pop() 1535 1536 if isinstance(node, exp.SetOperation): 1537 stack.append(node.expression) 1538 stack.append( 1539 self.maybe_comment( 1540 self.set_operation(node), comments=node.comments, separated=True 1541 ) 1542 ) 1543 stack.append(node.this) 1544 else: 1545 sqls.append(self.sql(node)) 1546 1547 this = self.sep().join(sqls) 1548 this = self.query_modifiers(expression, this) 1549 return self.prepend_ctes(expression, this)
1551 def fetch_sql(self, expression: exp.Fetch) -> str: 1552 direction = expression.args.get("direction") 1553 direction = f" {direction}" if direction else "" 1554 count = self.sql(expression, "count") 1555 count = f" {count}" if count else "" 1556 limit_options = self.sql(expression, "limit_options") 1557 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1558 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1560 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1561 percent = " PERCENT" if expression.args.get("percent") else "" 1562 rows = " ROWS" if expression.args.get("rows") else "" 1563 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1564 if not with_ties and rows: 1565 with_ties = " ONLY" 1566 return f"{percent}{rows}{with_ties}"
1568 def filter_sql(self, expression: exp.Filter) -> str: 1569 if self.AGGREGATE_FILTER_SUPPORTED: 1570 this = self.sql(expression, "this") 1571 where = self.sql(expression, "expression").strip() 1572 return f"{this} FILTER({where})" 1573 1574 agg = expression.this 1575 agg_arg = agg.this 1576 cond = expression.expression.this 1577 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1578 return self.sql(agg)
1587 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1588 using = self.sql(expression, "using") 1589 using = f" USING {using}" if using else "" 1590 columns = self.expressions(expression, key="columns", flat=True) 1591 columns = f"({columns})" if columns else "" 1592 partition_by = self.expressions(expression, key="partition_by", flat=True) 1593 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1594 where = self.sql(expression, "where") 1595 include = self.expressions(expression, key="include", flat=True) 1596 if include: 1597 include = f" INCLUDE ({include})" 1598 with_storage = self.expressions(expression, key="with_storage", flat=True) 1599 with_storage = f" WITH ({with_storage})" if with_storage else "" 1600 tablespace = self.sql(expression, "tablespace") 1601 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1602 on = self.sql(expression, "on") 1603 on = f" ON {on}" if on else "" 1604 1605 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1607 def index_sql(self, expression: exp.Index) -> str: 1608 unique = "UNIQUE " if expression.args.get("unique") else "" 1609 primary = "PRIMARY " if expression.args.get("primary") else "" 1610 amp = "AMP " if expression.args.get("amp") else "" 1611 name = self.sql(expression, "this") 1612 name = f"{name} " if name else "" 1613 table = self.sql(expression, "table") 1614 table = f"{self.INDEX_ON} {table}" if table else "" 1615 1616 index = "INDEX " if not table else "" 1617 1618 params = self.sql(expression, "params") 1619 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1621 def identifier_sql(self, expression: exp.Identifier) -> str: 1622 text = expression.name 1623 lower = text.lower() 1624 text = lower if self.normalize and not expression.quoted else text 1625 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1626 if ( 1627 expression.quoted 1628 or self.dialect.can_identify(text, self.identify) 1629 or lower in self.RESERVED_KEYWORDS 1630 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1631 ): 1632 text = f"{self._identifier_start}{text}{self._identifier_end}" 1633 return text
1648 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1649 input_format = self.sql(expression, "input_format") 1650 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1651 output_format = self.sql(expression, "output_format") 1652 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1653 return self.sep().join((input_format, output_format))
1663 def properties_sql(self, expression: exp.Properties) -> str: 1664 root_properties = [] 1665 with_properties = [] 1666 1667 for p in expression.expressions: 1668 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1669 if p_loc == exp.Properties.Location.POST_WITH: 1670 with_properties.append(p) 1671 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1672 root_properties.append(p) 1673 1674 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1675 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1676 1677 if root_props and with_props and not self.pretty: 1678 with_props = " " + with_props 1679 1680 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1687 def properties( 1688 self, 1689 properties: exp.Properties, 1690 prefix: str = "", 1691 sep: str = ", ", 1692 suffix: str = "", 1693 wrapped: bool = True, 1694 ) -> str: 1695 if properties.expressions: 1696 expressions = self.expressions(properties, sep=sep, indent=False) 1697 if expressions: 1698 expressions = self.wrap(expressions) if wrapped else expressions 1699 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1700 return ""
1705 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1706 properties_locs = defaultdict(list) 1707 for p in properties.expressions: 1708 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1709 if p_loc != exp.Properties.Location.UNSUPPORTED: 1710 properties_locs[p_loc].append(p) 1711 else: 1712 self.unsupported(f"Unsupported property {p.key}") 1713 1714 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1721 def property_sql(self, expression: exp.Property) -> str: 1722 property_cls = expression.__class__ 1723 if property_cls == exp.Property: 1724 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1725 1726 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1727 if not property_name: 1728 self.unsupported(f"Unsupported property {expression.key}") 1729 1730 return f"{property_name}={self.sql(expression, 'this')}"
1732 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1733 if self.SUPPORTS_CREATE_TABLE_LIKE: 1734 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1735 options = f" {options}" if options else "" 1736 1737 like = f"LIKE {self.sql(expression, 'this')}{options}" 1738 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1739 like = f"({like})" 1740 1741 return like 1742 1743 if expression.expressions: 1744 self.unsupported("Transpilation of LIKE property options is unsupported") 1745 1746 select = exp.select("*").from_(expression.this).limit(0) 1747 return f"AS {self.sql(select)}"
1754 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1755 no = "NO " if expression.args.get("no") else "" 1756 local = expression.args.get("local") 1757 local = f"{local} " if local else "" 1758 dual = "DUAL " if expression.args.get("dual") else "" 1759 before = "BEFORE " if expression.args.get("before") else "" 1760 after = "AFTER " if expression.args.get("after") else "" 1761 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1777 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1778 if expression.args.get("no"): 1779 return "NO MERGEBLOCKRATIO" 1780 if expression.args.get("default"): 1781 return "DEFAULT MERGEBLOCKRATIO" 1782 1783 percent = " PERCENT" if expression.args.get("percent") else "" 1784 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1786 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1787 default = expression.args.get("default") 1788 minimum = expression.args.get("minimum") 1789 maximum = expression.args.get("maximum") 1790 if default or minimum or maximum: 1791 if default: 1792 prop = "DEFAULT" 1793 elif minimum: 1794 prop = "MINIMUM" 1795 else: 1796 prop = "MAXIMUM" 1797 return f"{prop} DATABLOCKSIZE" 1798 units = expression.args.get("units") 1799 units = f" {units}" if units else "" 1800 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1802 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1803 autotemp = expression.args.get("autotemp") 1804 always = expression.args.get("always") 1805 default = expression.args.get("default") 1806 manual = expression.args.get("manual") 1807 never = expression.args.get("never") 1808 1809 if autotemp is not None: 1810 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1811 elif always: 1812 prop = "ALWAYS" 1813 elif default: 1814 prop = "DEFAULT" 1815 elif manual: 1816 prop = "MANUAL" 1817 elif never: 1818 prop = "NEVER" 1819 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1821 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1822 no = expression.args.get("no") 1823 no = " NO" if no else "" 1824 concurrent = expression.args.get("concurrent") 1825 concurrent = " CONCURRENT" if concurrent else "" 1826 target = self.sql(expression, "target") 1827 target = f" {target}" if target else "" 1828 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1830 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1831 if isinstance(expression.this, list): 1832 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1833 if expression.this: 1834 modulus = self.sql(expression, "this") 1835 remainder = self.sql(expression, "expression") 1836 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1837 1838 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1839 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1840 return f"FROM ({from_expressions}) TO ({to_expressions})"
1842 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1843 this = self.sql(expression, "this") 1844 1845 for_values_or_default = expression.expression 1846 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1847 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1848 else: 1849 for_values_or_default = " DEFAULT" 1850 1851 return f"PARTITION OF {this}{for_values_or_default}"
1853 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1854 kind = expression.args.get("kind") 1855 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1856 for_or_in = expression.args.get("for_or_in") 1857 for_or_in = f" {for_or_in}" if for_or_in else "" 1858 lock_type = expression.args.get("lock_type") 1859 override = " OVERRIDE" if expression.args.get("override") else "" 1860 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1862 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1863 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1864 statistics = expression.args.get("statistics") 1865 statistics_sql = "" 1866 if statistics is not None: 1867 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1868 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1870 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1871 this = self.sql(expression, "this") 1872 this = f"HISTORY_TABLE={this}" if this else "" 1873 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1874 data_consistency = ( 1875 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1876 ) 1877 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1878 retention_period = ( 1879 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1880 ) 1881 1882 if this: 1883 on_sql = self.func("ON", this, data_consistency, retention_period) 1884 else: 1885 on_sql = "ON" if expression.args.get("on") else "OFF" 1886 1887 sql = f"SYSTEM_VERSIONING={on_sql}" 1888 1889 return f"WITH({sql})" if expression.args.get("with") else sql
1891 def insert_sql(self, expression: exp.Insert) -> str: 1892 hint = self.sql(expression, "hint") 1893 overwrite = expression.args.get("overwrite") 1894 1895 if isinstance(expression.this, exp.Directory): 1896 this = " OVERWRITE" if overwrite else " INTO" 1897 else: 1898 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1899 1900 stored = self.sql(expression, "stored") 1901 stored = f" {stored}" if stored else "" 1902 alternative = expression.args.get("alternative") 1903 alternative = f" OR {alternative}" if alternative else "" 1904 ignore = " IGNORE" if expression.args.get("ignore") else "" 1905 is_function = expression.args.get("is_function") 1906 if is_function: 1907 this = f"{this} FUNCTION" 1908 this = f"{this} {self.sql(expression, 'this')}" 1909 1910 exists = " IF EXISTS" if expression.args.get("exists") else "" 1911 where = self.sql(expression, "where") 1912 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1913 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1914 on_conflict = self.sql(expression, "conflict") 1915 on_conflict = f" {on_conflict}" if on_conflict else "" 1916 by_name = " BY NAME" if expression.args.get("by_name") else "" 1917 returning = self.sql(expression, "returning") 1918 1919 if self.RETURNING_END: 1920 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1921 else: 1922 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1923 1924 partition_by = self.sql(expression, "partition") 1925 partition_by = f" {partition_by}" if partition_by else "" 1926 settings = self.sql(expression, "settings") 1927 settings = f" {settings}" if settings else "" 1928 1929 source = self.sql(expression, "source") 1930 source = f"TABLE {source}" if source else "" 1931 1932 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1933 return self.prepend_ctes(expression, sql)
1951 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1952 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1953 1954 constraint = self.sql(expression, "constraint") 1955 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1956 1957 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1958 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1959 action = self.sql(expression, "action") 1960 1961 expressions = self.expressions(expression, flat=True) 1962 if expressions: 1963 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1964 expressions = f" {set_keyword}{expressions}" 1965 1966 where = self.sql(expression, "where") 1967 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1972 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1973 fields = self.sql(expression, "fields") 1974 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1975 escaped = self.sql(expression, "escaped") 1976 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1977 items = self.sql(expression, "collection_items") 1978 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1979 keys = self.sql(expression, "map_keys") 1980 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1981 lines = self.sql(expression, "lines") 1982 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1983 null = self.sql(expression, "null") 1984 null = f" NULL DEFINED AS {null}" if null else "" 1985 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2013 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2014 table = self.table_parts(expression) 2015 only = "ONLY " if expression.args.get("only") else "" 2016 partition = self.sql(expression, "partition") 2017 partition = f" {partition}" if partition else "" 2018 version = self.sql(expression, "version") 2019 version = f" {version}" if version else "" 2020 alias = self.sql(expression, "alias") 2021 alias = f"{sep}{alias}" if alias else "" 2022 2023 sample = self.sql(expression, "sample") 2024 if self.dialect.ALIAS_POST_TABLESAMPLE: 2025 sample_pre_alias = sample 2026 sample_post_alias = "" 2027 else: 2028 sample_pre_alias = "" 2029 sample_post_alias = sample 2030 2031 hints = self.expressions(expression, key="hints", sep=" ") 2032 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2033 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2034 joins = self.indent( 2035 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2036 ) 2037 laterals = self.expressions(expression, key="laterals", sep="") 2038 2039 file_format = self.sql(expression, "format") 2040 if file_format: 2041 pattern = self.sql(expression, "pattern") 2042 pattern = f", PATTERN => {pattern}" if pattern else "" 2043 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2044 2045 ordinality = expression.args.get("ordinality") or "" 2046 if ordinality: 2047 ordinality = f" WITH ORDINALITY{alias}" 2048 alias = "" 2049 2050 when = self.sql(expression, "when") 2051 if when: 2052 table = f"{table} {when}" 2053 2054 changes = self.sql(expression, "changes") 2055 changes = f" {changes}" if changes else "" 2056 2057 rows_from = self.expressions(expression, key="rows_from") 2058 if rows_from: 2059 table = f"ROWS FROM {self.wrap(rows_from)}" 2060 2061 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2063 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2064 table = self.func("TABLE", expression.this) 2065 alias = self.sql(expression, "alias") 2066 alias = f" AS {alias}" if alias else "" 2067 sample = self.sql(expression, "sample") 2068 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2069 joins = self.indent( 2070 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2071 ) 2072 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2074 def tablesample_sql( 2075 self, 2076 expression: exp.TableSample, 2077 tablesample_keyword: t.Optional[str] = None, 2078 ) -> str: 2079 method = self.sql(expression, "method") 2080 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2081 numerator = self.sql(expression, "bucket_numerator") 2082 denominator = self.sql(expression, "bucket_denominator") 2083 field = self.sql(expression, "bucket_field") 2084 field = f" ON {field}" if field else "" 2085 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2086 seed = self.sql(expression, "seed") 2087 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2088 2089 size = self.sql(expression, "size") 2090 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2091 size = f"{size} ROWS" 2092 2093 percent = self.sql(expression, "percent") 2094 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2095 percent = f"{percent} PERCENT" 2096 2097 expr = f"{bucket}{percent}{size}" 2098 if self.TABLESAMPLE_REQUIRES_PARENS: 2099 expr = f"({expr})" 2100 2101 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2103 def pivot_sql(self, expression: exp.Pivot) -> str: 2104 expressions = self.expressions(expression, flat=True) 2105 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2106 2107 group = self.sql(expression, "group") 2108 2109 if expression.this: 2110 this = self.sql(expression, "this") 2111 if not expressions: 2112 return f"UNPIVOT {this}" 2113 2114 on = f"{self.seg('ON')} {expressions}" 2115 into = self.sql(expression, "into") 2116 into = f"{self.seg('INTO')} {into}" if into else "" 2117 using = self.expressions(expression, key="using", flat=True) 2118 using = f"{self.seg('USING')} {using}" if using else "" 2119 return f"{direction} {this}{on}{into}{using}{group}" 2120 2121 alias = self.sql(expression, "alias") 2122 alias = f" AS {alias}" if alias else "" 2123 2124 fields = self.expressions( 2125 expression, 2126 "fields", 2127 sep=" ", 2128 dynamic=True, 2129 new_line=True, 2130 skip_first=True, 2131 skip_last=True, 2132 ) 2133 2134 include_nulls = expression.args.get("include_nulls") 2135 if include_nulls is not None: 2136 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2137 else: 2138 nulls = "" 2139 2140 default_on_null = self.sql(expression, "default_on_null") 2141 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2142 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2153 def update_sql(self, expression: exp.Update) -> str: 2154 this = self.sql(expression, "this") 2155 set_sql = self.expressions(expression, flat=True) 2156 from_sql = self.sql(expression, "from") 2157 where_sql = self.sql(expression, "where") 2158 returning = self.sql(expression, "returning") 2159 order = self.sql(expression, "order") 2160 limit = self.sql(expression, "limit") 2161 if self.RETURNING_END: 2162 expression_sql = f"{from_sql}{where_sql}{returning}" 2163 else: 2164 expression_sql = f"{returning}{from_sql}{where_sql}" 2165 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2166 return self.prepend_ctes(expression, sql)
2168 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2169 values_as_table = values_as_table and self.VALUES_AS_TABLE 2170 2171 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2172 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2173 args = self.expressions(expression) 2174 alias = self.sql(expression, "alias") 2175 values = f"VALUES{self.seg('')}{args}" 2176 values = ( 2177 f"({values})" 2178 if self.WRAP_DERIVED_VALUES 2179 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2180 else values 2181 ) 2182 return f"{values} AS {alias}" if alias else values 2183 2184 # Converts `VALUES...` expression into a series of select unions. 2185 alias_node = expression.args.get("alias") 2186 column_names = alias_node and alias_node.columns 2187 2188 selects: t.List[exp.Query] = [] 2189 2190 for i, tup in enumerate(expression.expressions): 2191 row = tup.expressions 2192 2193 if i == 0 and column_names: 2194 row = [ 2195 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2196 ] 2197 2198 selects.append(exp.Select(expressions=row)) 2199 2200 if self.pretty: 2201 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2202 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2203 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2204 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2205 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2206 2207 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2208 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2209 return f"({unions}){alias}"
2214 @unsupported_args("expressions") 2215 def into_sql(self, expression: exp.Into) -> str: 2216 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2217 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2218 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2235 def group_sql(self, expression: exp.Group) -> str: 2236 group_by_all = expression.args.get("all") 2237 if group_by_all is True: 2238 modifier = " ALL" 2239 elif group_by_all is False: 2240 modifier = " DISTINCT" 2241 else: 2242 modifier = "" 2243 2244 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2245 2246 grouping_sets = self.expressions(expression, key="grouping_sets") 2247 cube = self.expressions(expression, key="cube") 2248 rollup = self.expressions(expression, key="rollup") 2249 2250 groupings = csv( 2251 self.seg(grouping_sets) if grouping_sets else "", 2252 self.seg(cube) if cube else "", 2253 self.seg(rollup) if rollup else "", 2254 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2255 sep=self.GROUPINGS_SEP, 2256 ) 2257 2258 if ( 2259 expression.expressions 2260 and groupings 2261 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2262 ): 2263 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2264 2265 return f"{group_by}{groupings}"
2271 def connect_sql(self, expression: exp.Connect) -> str: 2272 start = self.sql(expression, "start") 2273 start = self.seg(f"START WITH {start}") if start else "" 2274 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2275 connect = self.sql(expression, "connect") 2276 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2277 return start + connect
2282 def join_sql(self, expression: exp.Join) -> str: 2283 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2284 side = None 2285 else: 2286 side = expression.side 2287 2288 op_sql = " ".join( 2289 op 2290 for op in ( 2291 expression.method, 2292 "GLOBAL" if expression.args.get("global") else None, 2293 side, 2294 expression.kind, 2295 expression.hint if self.JOIN_HINTS else None, 2296 ) 2297 if op 2298 ) 2299 match_cond = self.sql(expression, "match_condition") 2300 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2301 on_sql = self.sql(expression, "on") 2302 using = expression.args.get("using") 2303 2304 if not on_sql and using: 2305 on_sql = csv(*(self.sql(column) for column in using)) 2306 2307 this = expression.this 2308 this_sql = self.sql(this) 2309 2310 exprs = self.expressions(expression) 2311 if exprs: 2312 this_sql = f"{this_sql},{self.seg(exprs)}" 2313 2314 if on_sql: 2315 on_sql = self.indent(on_sql, skip_first=True) 2316 space = self.seg(" " * self.pad) if self.pretty else " " 2317 if using: 2318 on_sql = f"{space}USING ({on_sql})" 2319 else: 2320 on_sql = f"{space}ON {on_sql}" 2321 elif not op_sql: 2322 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2323 return f" {this_sql}" 2324 2325 return f", {this_sql}" 2326 2327 if op_sql != "STRAIGHT_JOIN": 2328 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2329 2330 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2331 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:
2338 def lateral_op(self, expression: exp.Lateral) -> str: 2339 cross_apply = expression.args.get("cross_apply") 2340 2341 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2342 if cross_apply is True: 2343 op = "INNER JOIN " 2344 elif cross_apply is False: 2345 op = "LEFT JOIN " 2346 else: 2347 op = "" 2348 2349 return f"{op}LATERAL"
2351 def lateral_sql(self, expression: exp.Lateral) -> str: 2352 this = self.sql(expression, "this") 2353 2354 if expression.args.get("view"): 2355 alias = expression.args["alias"] 2356 columns = self.expressions(alias, key="columns", flat=True) 2357 table = f" {alias.name}" if alias.name else "" 2358 columns = f" AS {columns}" if columns else "" 2359 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2360 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2361 2362 alias = self.sql(expression, "alias") 2363 alias = f" AS {alias}" if alias else "" 2364 2365 ordinality = expression.args.get("ordinality") or "" 2366 if ordinality: 2367 ordinality = f" WITH ORDINALITY{alias}" 2368 alias = "" 2369 2370 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2372 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2373 this = self.sql(expression, "this") 2374 2375 args = [ 2376 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2377 for e in (expression.args.get(k) for k in ("offset", "expression")) 2378 if e 2379 ] 2380 2381 args_sql = ", ".join(self.sql(e) for e in args) 2382 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2383 expressions = self.expressions(expression, flat=True) 2384 limit_options = self.sql(expression, "limit_options") 2385 expressions = f" BY {expressions}" if expressions else "" 2386 2387 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2389 def offset_sql(self, expression: exp.Offset) -> str: 2390 this = self.sql(expression, "this") 2391 value = expression.expression 2392 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2393 expressions = self.expressions(expression, flat=True) 2394 expressions = f" BY {expressions}" if expressions else "" 2395 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2397 def setitem_sql(self, expression: exp.SetItem) -> str: 2398 kind = self.sql(expression, "kind") 2399 kind = f"{kind} " if kind else "" 2400 this = self.sql(expression, "this") 2401 expressions = self.expressions(expression) 2402 collate = self.sql(expression, "collate") 2403 collate = f" COLLATE {collate}" if collate else "" 2404 global_ = "GLOBAL " if expression.args.get("global") else "" 2405 return f"{global_}{kind}{this}{expressions}{collate}"
2412 def queryband_sql(self, expression: exp.QueryBand) -> str: 2413 this = self.sql(expression, "this") 2414 update = " UPDATE" if expression.args.get("update") else "" 2415 scope = self.sql(expression, "scope") 2416 scope = f" FOR {scope}" if scope else "" 2417 2418 return f"QUERY_BAND = {this}{update}{scope}"
2423 def lock_sql(self, expression: exp.Lock) -> str: 2424 if not self.LOCKING_READS_SUPPORTED: 2425 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2426 return "" 2427 2428 update = expression.args["update"] 2429 key = expression.args.get("key") 2430 if update: 2431 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2432 else: 2433 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2434 expressions = self.expressions(expression, flat=True) 2435 expressions = f" OF {expressions}" if expressions else "" 2436 wait = expression.args.get("wait") 2437 2438 if wait is not None: 2439 if isinstance(wait, exp.Literal): 2440 wait = f" WAIT {self.sql(wait)}" 2441 else: 2442 wait = " NOWAIT" if wait else " SKIP LOCKED" 2443 2444 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2452 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2453 if self.dialect.ESCAPED_SEQUENCES: 2454 to_escaped = self.dialect.ESCAPED_SEQUENCES 2455 text = "".join( 2456 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2457 ) 2458 2459 return self._replace_line_breaks(text).replace( 2460 self.dialect.QUOTE_END, self._escaped_quote_end 2461 )
2463 def loaddata_sql(self, expression: exp.LoadData) -> str: 2464 local = " LOCAL" if expression.args.get("local") else "" 2465 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2466 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2467 this = f" INTO TABLE {self.sql(expression, 'this')}" 2468 partition = self.sql(expression, "partition") 2469 partition = f" {partition}" if partition else "" 2470 input_format = self.sql(expression, "input_format") 2471 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2472 serde = self.sql(expression, "serde") 2473 serde = f" SERDE {serde}" if serde else "" 2474 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2482 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2483 this = self.sql(expression, "this") 2484 this = f"{this} " if this else this 2485 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2486 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2488 def withfill_sql(self, expression: exp.WithFill) -> str: 2489 from_sql = self.sql(expression, "from") 2490 from_sql = f" FROM {from_sql}" if from_sql else "" 2491 to_sql = self.sql(expression, "to") 2492 to_sql = f" TO {to_sql}" if to_sql else "" 2493 step_sql = self.sql(expression, "step") 2494 step_sql = f" STEP {step_sql}" if step_sql else "" 2495 interpolated_values = [ 2496 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2497 if isinstance(e, exp.Alias) 2498 else self.sql(e, "this") 2499 for e in expression.args.get("interpolate") or [] 2500 ] 2501 interpolate = ( 2502 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2503 ) 2504 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2515 def ordered_sql(self, expression: exp.Ordered) -> str: 2516 desc = expression.args.get("desc") 2517 asc = not desc 2518 2519 nulls_first = expression.args.get("nulls_first") 2520 nulls_last = not nulls_first 2521 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2522 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2523 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2524 2525 this = self.sql(expression, "this") 2526 2527 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2528 nulls_sort_change = "" 2529 if nulls_first and ( 2530 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2531 ): 2532 nulls_sort_change = " NULLS FIRST" 2533 elif ( 2534 nulls_last 2535 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2536 and not nulls_are_last 2537 ): 2538 nulls_sort_change = " NULLS LAST" 2539 2540 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2541 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2542 window = expression.find_ancestor(exp.Window, exp.Select) 2543 if isinstance(window, exp.Window) and window.args.get("spec"): 2544 self.unsupported( 2545 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2546 ) 2547 nulls_sort_change = "" 2548 elif self.NULL_ORDERING_SUPPORTED is False and ( 2549 (asc and nulls_sort_change == " NULLS LAST") 2550 or (desc and nulls_sort_change == " NULLS FIRST") 2551 ): 2552 # BigQuery does not allow these ordering/nulls combinations when used under 2553 # an aggregation func or under a window containing one 2554 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2555 2556 if isinstance(ancestor, exp.Window): 2557 ancestor = ancestor.this 2558 if isinstance(ancestor, exp.AggFunc): 2559 self.unsupported( 2560 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2561 ) 2562 nulls_sort_change = "" 2563 elif self.NULL_ORDERING_SUPPORTED is None: 2564 if expression.this.is_int: 2565 self.unsupported( 2566 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2567 ) 2568 elif not isinstance(expression.this, exp.Rand): 2569 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2570 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2571 nulls_sort_change = "" 2572 2573 with_fill = self.sql(expression, "with_fill") 2574 with_fill = f" {with_fill}" if with_fill else "" 2575 2576 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2586 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2587 partition = self.partition_by_sql(expression) 2588 order = self.sql(expression, "order") 2589 measures = self.expressions(expression, key="measures") 2590 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2591 rows = self.sql(expression, "rows") 2592 rows = self.seg(rows) if rows else "" 2593 after = self.sql(expression, "after") 2594 after = self.seg(after) if after else "" 2595 pattern = self.sql(expression, "pattern") 2596 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2597 definition_sqls = [ 2598 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2599 for definition in expression.args.get("define", []) 2600 ] 2601 definitions = self.expressions(sqls=definition_sqls) 2602 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2603 body = "".join( 2604 ( 2605 partition, 2606 order, 2607 measures, 2608 rows, 2609 after, 2610 pattern, 2611 define, 2612 ) 2613 ) 2614 alias = self.sql(expression, "alias") 2615 alias = f" {alias}" if alias else "" 2616 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2618 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2619 limit = expression.args.get("limit") 2620 2621 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2622 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2623 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2624 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2625 2626 return csv( 2627 *sqls, 2628 *[self.sql(join) for join in expression.args.get("joins") or []], 2629 self.sql(expression, "match"), 2630 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2631 self.sql(expression, "prewhere"), 2632 self.sql(expression, "where"), 2633 self.sql(expression, "connect"), 2634 self.sql(expression, "group"), 2635 self.sql(expression, "having"), 2636 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2637 self.sql(expression, "order"), 2638 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2639 *self.after_limit_modifiers(expression), 2640 self.options_modifier(expression), 2641 self.for_modifiers(expression), 2642 sep="", 2643 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2657 def offset_limit_modifiers( 2658 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2659 ) -> t.List[str]: 2660 return [ 2661 self.sql(expression, "offset") if fetch else self.sql(limit), 2662 self.sql(limit) if fetch else self.sql(expression, "offset"), 2663 ]
2670 def select_sql(self, expression: exp.Select) -> str: 2671 into = expression.args.get("into") 2672 if not self.SUPPORTS_SELECT_INTO and into: 2673 into.pop() 2674 2675 hint = self.sql(expression, "hint") 2676 distinct = self.sql(expression, "distinct") 2677 distinct = f" {distinct}" if distinct else "" 2678 kind = self.sql(expression, "kind") 2679 2680 limit = expression.args.get("limit") 2681 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2682 top = self.limit_sql(limit, top=True) 2683 limit.pop() 2684 else: 2685 top = "" 2686 2687 expressions = self.expressions(expression) 2688 2689 if kind: 2690 if kind in self.SELECT_KINDS: 2691 kind = f" AS {kind}" 2692 else: 2693 if kind == "STRUCT": 2694 expressions = self.expressions( 2695 sqls=[ 2696 self.sql( 2697 exp.Struct( 2698 expressions=[ 2699 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2700 if isinstance(e, exp.Alias) 2701 else e 2702 for e in expression.expressions 2703 ] 2704 ) 2705 ) 2706 ] 2707 ) 2708 kind = "" 2709 2710 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2711 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2712 2713 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2714 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2715 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2716 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2717 sql = self.query_modifiers( 2718 expression, 2719 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2720 self.sql(expression, "into", comment=False), 2721 self.sql(expression, "from", comment=False), 2722 ) 2723 2724 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2725 if expression.args.get("with"): 2726 sql = self.maybe_comment(sql, expression) 2727 expression.pop_comments() 2728 2729 sql = self.prepend_ctes(expression, sql) 2730 2731 if not self.SUPPORTS_SELECT_INTO and into: 2732 if into.args.get("temporary"): 2733 table_kind = " TEMPORARY" 2734 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2735 table_kind = " UNLOGGED" 2736 else: 2737 table_kind = "" 2738 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2739 2740 return sql
2752 def star_sql(self, expression: exp.Star) -> str: 2753 except_ = self.expressions(expression, key="except", flat=True) 2754 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2755 replace = self.expressions(expression, key="replace", flat=True) 2756 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2757 rename = self.expressions(expression, key="rename", flat=True) 2758 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2759 return f"*{except_}{replace}{rename}"
2775 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2776 alias = self.sql(expression, "alias") 2777 alias = f"{sep}{alias}" if alias else "" 2778 sample = self.sql(expression, "sample") 2779 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2780 alias = f"{sample}{alias}" 2781 2782 # Set to None so it's not generated again by self.query_modifiers() 2783 expression.set("sample", None) 2784 2785 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2786 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2787 return self.prepend_ctes(expression, sql)
2793 def unnest_sql(self, expression: exp.Unnest) -> str: 2794 args = self.expressions(expression, flat=True) 2795 2796 alias = expression.args.get("alias") 2797 offset = expression.args.get("offset") 2798 2799 if self.UNNEST_WITH_ORDINALITY: 2800 if alias and isinstance(offset, exp.Expression): 2801 alias.append("columns", offset) 2802 2803 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2804 columns = alias.columns 2805 alias = self.sql(columns[0]) if columns else "" 2806 else: 2807 alias = self.sql(alias) 2808 2809 alias = f" AS {alias}" if alias else alias 2810 if self.UNNEST_WITH_ORDINALITY: 2811 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2812 else: 2813 if isinstance(offset, exp.Expression): 2814 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2815 elif offset: 2816 suffix = f"{alias} WITH OFFSET" 2817 else: 2818 suffix = alias 2819 2820 return f"UNNEST({args}){suffix}"
2829 def window_sql(self, expression: exp.Window) -> str: 2830 this = self.sql(expression, "this") 2831 partition = self.partition_by_sql(expression) 2832 order = expression.args.get("order") 2833 order = self.order_sql(order, flat=True) if order else "" 2834 spec = self.sql(expression, "spec") 2835 alias = self.sql(expression, "alias") 2836 over = self.sql(expression, "over") or "OVER" 2837 2838 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2839 2840 first = expression.args.get("first") 2841 if first is None: 2842 first = "" 2843 else: 2844 first = "FIRST" if first else "LAST" 2845 2846 if not partition and not order and not spec and alias: 2847 return f"{this} {alias}" 2848 2849 args = self.format_args( 2850 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2851 ) 2852 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2858 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2859 kind = self.sql(expression, "kind") 2860 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2861 end = ( 2862 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2863 or "CURRENT ROW" 2864 ) 2865 2866 window_spec = f"{kind} BETWEEN {start} AND {end}" 2867 2868 exclude = self.sql(expression, "exclude") 2869 if exclude: 2870 if self.SUPPORTS_WINDOW_EXCLUDE: 2871 window_spec += f" EXCLUDE {exclude}" 2872 else: 2873 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2874 2875 return window_spec
2882 def between_sql(self, expression: exp.Between) -> str: 2883 this = self.sql(expression, "this") 2884 low = self.sql(expression, "low") 2885 high = self.sql(expression, "high") 2886 symmetric = expression.args.get("symmetric") 2887 2888 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2889 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2890 2891 flag = ( 2892 " SYMMETRIC" 2893 if symmetric 2894 else " ASYMMETRIC" 2895 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2896 else "" # silently drop ASYMMETRIC – semantics identical 2897 ) 2898 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]:
2900 def bracket_offset_expressions( 2901 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2902 ) -> t.List[exp.Expression]: 2903 return apply_index_offset( 2904 expression.this, 2905 expression.expressions, 2906 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2907 dialect=self.dialect, 2908 )
2921 def any_sql(self, expression: exp.Any) -> str: 2922 this = self.sql(expression, "this") 2923 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2924 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2925 this = self.wrap(this) 2926 return f"ANY{this}" 2927 return f"ANY {this}"
2932 def case_sql(self, expression: exp.Case) -> str: 2933 this = self.sql(expression, "this") 2934 statements = [f"CASE {this}" if this else "CASE"] 2935 2936 for e in expression.args["ifs"]: 2937 statements.append(f"WHEN {self.sql(e, 'this')}") 2938 statements.append(f"THEN {self.sql(e, 'true')}") 2939 2940 default = self.sql(expression, "default") 2941 2942 if default: 2943 statements.append(f"ELSE {default}") 2944 2945 statements.append("END") 2946 2947 if self.pretty and self.too_wide(statements): 2948 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2949 2950 return " ".join(statements)
2962 def extract_sql(self, expression: exp.Extract) -> str: 2963 from sqlglot.dialects.dialect import map_date_part 2964 2965 this = ( 2966 map_date_part(expression.this, self.dialect) 2967 if self.NORMALIZE_EXTRACT_DATE_PARTS 2968 else expression.this 2969 ) 2970 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2971 expression_sql = self.sql(expression, "expression") 2972 2973 return f"EXTRACT({this_sql} FROM {expression_sql})"
2975 def trim_sql(self, expression: exp.Trim) -> str: 2976 trim_type = self.sql(expression, "position") 2977 2978 if trim_type == "LEADING": 2979 func_name = "LTRIM" 2980 elif trim_type == "TRAILING": 2981 func_name = "RTRIM" 2982 else: 2983 func_name = "TRIM" 2984 2985 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]:
2987 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2988 args = expression.expressions 2989 if isinstance(expression, exp.ConcatWs): 2990 args = args[1:] # Skip the delimiter 2991 2992 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2993 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2994 2995 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2996 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2997 2998 return args
3000 def concat_sql(self, expression: exp.Concat) -> str: 3001 expressions = self.convert_concat_args(expression) 3002 3003 # Some dialects don't allow a single-argument CONCAT call 3004 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3005 return self.sql(expressions[0]) 3006 3007 return self.func("CONCAT", *expressions)
3018 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3019 expressions = self.expressions(expression, flat=True) 3020 expressions = f" ({expressions})" if expressions else "" 3021 reference = self.sql(expression, "reference") 3022 reference = f" {reference}" if reference else "" 3023 delete = self.sql(expression, "delete") 3024 delete = f" ON DELETE {delete}" if delete else "" 3025 update = self.sql(expression, "update") 3026 update = f" ON UPDATE {update}" if update else "" 3027 options = self.expressions(expression, key="options", flat=True, sep=" ") 3028 options = f" {options}" if options else "" 3029 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3031 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3032 expressions = self.expressions(expression, flat=True) 3033 include = self.sql(expression, "include") 3034 options = self.expressions(expression, key="options", flat=True, sep=" ") 3035 options = f" {options}" if options else "" 3036 return f"PRIMARY KEY ({expressions}){include}{options}"
3049 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3050 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3051 3052 if expression.args.get("escape"): 3053 path = self.escape_str(path) 3054 3055 if self.QUOTE_JSON_PATH: 3056 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3057 3058 return path
3060 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3061 if isinstance(expression, exp.JSONPathPart): 3062 transform = self.TRANSFORMS.get(expression.__class__) 3063 if not callable(transform): 3064 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3065 return "" 3066 3067 return transform(self, expression) 3068 3069 if isinstance(expression, int): 3070 return str(expression) 3071 3072 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3073 escaped = expression.replace("'", "\\'") 3074 escaped = f"\\'{expression}\\'" 3075 else: 3076 escaped = expression.replace('"', '\\"') 3077 escaped = f'"{escaped}"' 3078 3079 return escaped
3084 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3085 # Output the Teradata column FORMAT override. 3086 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3087 this = self.sql(expression, "this") 3088 fmt = self.sql(expression, "format") 3089 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3091 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3092 null_handling = expression.args.get("null_handling") 3093 null_handling = f" {null_handling}" if null_handling else "" 3094 3095 unique_keys = expression.args.get("unique_keys") 3096 if unique_keys is not None: 3097 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3098 else: 3099 unique_keys = "" 3100 3101 return_type = self.sql(expression, "return_type") 3102 return_type = f" RETURNING {return_type}" if return_type else "" 3103 encoding = self.sql(expression, "encoding") 3104 encoding = f" ENCODING {encoding}" if encoding else "" 3105 3106 return self.func( 3107 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3108 *expression.expressions, 3109 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3110 )
3115 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3116 null_handling = expression.args.get("null_handling") 3117 null_handling = f" {null_handling}" if null_handling else "" 3118 return_type = self.sql(expression, "return_type") 3119 return_type = f" RETURNING {return_type}" if return_type else "" 3120 strict = " STRICT" if expression.args.get("strict") else "" 3121 return self.func( 3122 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3123 )
3125 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3126 this = self.sql(expression, "this") 3127 order = self.sql(expression, "order") 3128 null_handling = expression.args.get("null_handling") 3129 null_handling = f" {null_handling}" if null_handling else "" 3130 return_type = self.sql(expression, "return_type") 3131 return_type = f" RETURNING {return_type}" if return_type else "" 3132 strict = " STRICT" if expression.args.get("strict") else "" 3133 return self.func( 3134 "JSON_ARRAYAGG", 3135 this, 3136 suffix=f"{order}{null_handling}{return_type}{strict})", 3137 )
3139 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3140 path = self.sql(expression, "path") 3141 path = f" PATH {path}" if path else "" 3142 nested_schema = self.sql(expression, "nested_schema") 3143 3144 if nested_schema: 3145 return f"NESTED{path} {nested_schema}" 3146 3147 this = self.sql(expression, "this") 3148 kind = self.sql(expression, "kind") 3149 kind = f" {kind}" if kind else "" 3150 return f"{this}{kind}{path}"
3155 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3156 this = self.sql(expression, "this") 3157 path = self.sql(expression, "path") 3158 path = f", {path}" if path else "" 3159 error_handling = expression.args.get("error_handling") 3160 error_handling = f" {error_handling}" if error_handling else "" 3161 empty_handling = expression.args.get("empty_handling") 3162 empty_handling = f" {empty_handling}" if empty_handling else "" 3163 schema = self.sql(expression, "schema") 3164 return self.func( 3165 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3166 )
3168 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3169 this = self.sql(expression, "this") 3170 kind = self.sql(expression, "kind") 3171 path = self.sql(expression, "path") 3172 path = f" {path}" if path else "" 3173 as_json = " AS JSON" if expression.args.get("as_json") else "" 3174 return f"{this} {kind}{path}{as_json}"
3176 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3177 this = self.sql(expression, "this") 3178 path = self.sql(expression, "path") 3179 path = f", {path}" if path else "" 3180 expressions = self.expressions(expression) 3181 with_ = ( 3182 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3183 if expressions 3184 else "" 3185 ) 3186 return f"OPENJSON({this}{path}){with_}"
3188 def in_sql(self, expression: exp.In) -> str: 3189 query = expression.args.get("query") 3190 unnest = expression.args.get("unnest") 3191 field = expression.args.get("field") 3192 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3193 3194 if query: 3195 in_sql = self.sql(query) 3196 elif unnest: 3197 in_sql = self.in_unnest_op(unnest) 3198 elif field: 3199 in_sql = self.sql(field) 3200 else: 3201 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3202 3203 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3208 def interval_sql(self, expression: exp.Interval) -> str: 3209 unit = self.sql(expression, "unit") 3210 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3211 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3212 unit = f" {unit}" if unit else "" 3213 3214 if self.SINGLE_STRING_INTERVAL: 3215 this = expression.this.name if expression.this else "" 3216 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3217 3218 this = self.sql(expression, "this") 3219 if this: 3220 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3221 this = f" {this}" if unwrapped else f" ({this})" 3222 3223 return f"INTERVAL{this}{unit}"
3228 def reference_sql(self, expression: exp.Reference) -> str: 3229 this = self.sql(expression, "this") 3230 expressions = self.expressions(expression, flat=True) 3231 expressions = f"({expressions})" if expressions else "" 3232 options = self.expressions(expression, key="options", flat=True, sep=" ") 3233 options = f" {options}" if options else "" 3234 return f"REFERENCES {this}{expressions}{options}"
3236 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3237 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3238 parent = expression.parent 3239 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3240 return self.func( 3241 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3242 )
3262 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3263 alias = expression.args["alias"] 3264 3265 parent = expression.parent 3266 pivot = parent and parent.parent 3267 3268 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3269 identifier_alias = isinstance(alias, exp.Identifier) 3270 literal_alias = isinstance(alias, exp.Literal) 3271 3272 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3273 alias.replace(exp.Literal.string(alias.output_name)) 3274 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3275 alias.replace(exp.to_identifier(alias.output_name)) 3276 3277 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:
3315 def connector_sql( 3316 self, 3317 expression: exp.Connector, 3318 op: str, 3319 stack: t.Optional[t.List[str | exp.Expression]] = None, 3320 ) -> str: 3321 if stack is not None: 3322 if expression.expressions: 3323 stack.append(self.expressions(expression, sep=f" {op} ")) 3324 else: 3325 stack.append(expression.right) 3326 if expression.comments and self.comments: 3327 for comment in expression.comments: 3328 if comment: 3329 op += f" /*{self.sanitize_comment(comment)}*/" 3330 stack.extend((op, expression.left)) 3331 return op 3332 3333 stack = [expression] 3334 sqls: t.List[str] = [] 3335 ops = set() 3336 3337 while stack: 3338 node = stack.pop() 3339 if isinstance(node, exp.Connector): 3340 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3341 else: 3342 sql = self.sql(node) 3343 if sqls and sqls[-1] in ops: 3344 sqls[-1] += f" {sql}" 3345 else: 3346 sqls.append(sql) 3347 3348 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3349 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3369 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3370 format_sql = self.sql(expression, "format") 3371 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3372 to_sql = self.sql(expression, "to") 3373 to_sql = f" {to_sql}" if to_sql else "" 3374 action = self.sql(expression, "action") 3375 action = f" {action}" if action else "" 3376 default = self.sql(expression, "default") 3377 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3378 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3392 def comment_sql(self, expression: exp.Comment) -> str: 3393 this = self.sql(expression, "this") 3394 kind = expression.args["kind"] 3395 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3396 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3397 expression_sql = self.sql(expression, "expression") 3398 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3400 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3401 this = self.sql(expression, "this") 3402 delete = " DELETE" if expression.args.get("delete") else "" 3403 recompress = self.sql(expression, "recompress") 3404 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3405 to_disk = self.sql(expression, "to_disk") 3406 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3407 to_volume = self.sql(expression, "to_volume") 3408 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3409 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3411 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3412 where = self.sql(expression, "where") 3413 group = self.sql(expression, "group") 3414 aggregates = self.expressions(expression, key="aggregates") 3415 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3416 3417 if not (where or group or aggregates) and len(expression.expressions) == 1: 3418 return f"TTL {self.expressions(expression, flat=True)}" 3419 3420 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3437 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3438 this = self.sql(expression, "this") 3439 3440 dtype = self.sql(expression, "dtype") 3441 if dtype: 3442 collate = self.sql(expression, "collate") 3443 collate = f" COLLATE {collate}" if collate else "" 3444 using = self.sql(expression, "using") 3445 using = f" USING {using}" if using else "" 3446 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3447 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3448 3449 default = self.sql(expression, "default") 3450 if default: 3451 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3452 3453 comment = self.sql(expression, "comment") 3454 if comment: 3455 return f"ALTER COLUMN {this} COMMENT {comment}" 3456 3457 visible = expression.args.get("visible") 3458 if visible: 3459 return f"ALTER COLUMN {this} SET {visible}" 3460 3461 allow_null = expression.args.get("allow_null") 3462 drop = expression.args.get("drop") 3463 3464 if not drop and not allow_null: 3465 self.unsupported("Unsupported ALTER COLUMN syntax") 3466 3467 if allow_null is not None: 3468 keyword = "DROP" if drop else "SET" 3469 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3470 3471 return f"ALTER COLUMN {this} DROP DEFAULT"
3487 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3488 compound = " COMPOUND" if expression.args.get("compound") else "" 3489 this = self.sql(expression, "this") 3490 expressions = self.expressions(expression, flat=True) 3491 expressions = f"({expressions})" if expressions else "" 3492 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3494 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3495 if not self.RENAME_TABLE_WITH_DB: 3496 # Remove db from tables 3497 expression = expression.transform( 3498 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3499 ).assert_is(exp.AlterRename) 3500 this = self.sql(expression, "this") 3501 to_kw = " TO" if include_to else "" 3502 return f"RENAME{to_kw} {this}"
3517 def alter_sql(self, expression: exp.Alter) -> str: 3518 actions = expression.args["actions"] 3519 3520 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3521 actions[0], exp.ColumnDef 3522 ): 3523 actions_sql = self.expressions(expression, key="actions", flat=True) 3524 actions_sql = f"ADD {actions_sql}" 3525 else: 3526 actions_list = [] 3527 for action in actions: 3528 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3529 action_sql = self.add_column_sql(action) 3530 else: 3531 action_sql = self.sql(action) 3532 if isinstance(action, exp.Query): 3533 action_sql = f"AS {action_sql}" 3534 3535 actions_list.append(action_sql) 3536 3537 actions_sql = self.format_args(*actions_list).lstrip("\n") 3538 3539 exists = " IF EXISTS" if expression.args.get("exists") else "" 3540 on_cluster = self.sql(expression, "cluster") 3541 on_cluster = f" {on_cluster}" if on_cluster else "" 3542 only = " ONLY" if expression.args.get("only") else "" 3543 options = self.expressions(expression, key="options") 3544 options = f", {options}" if options else "" 3545 kind = self.sql(expression, "kind") 3546 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3547 3548 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3550 def add_column_sql(self, expression: exp.Expression) -> str: 3551 sql = self.sql(expression) 3552 if isinstance(expression, exp.Schema): 3553 column_text = " COLUMNS" 3554 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3555 column_text = " COLUMN" 3556 else: 3557 column_text = "" 3558 3559 return f"ADD{column_text} {sql}"
3569 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3570 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3571 location = self.sql(expression, "location") 3572 location = f" {location}" if location else "" 3573 return f"ADD {exists}{self.sql(expression.this)}{location}"
3575 def distinct_sql(self, expression: exp.Distinct) -> str: 3576 this = self.expressions(expression, flat=True) 3577 3578 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3579 case = exp.case() 3580 for arg in expression.expressions: 3581 case = case.when(arg.is_(exp.null()), exp.null()) 3582 this = self.sql(case.else_(f"({this})")) 3583 3584 this = f" {this}" if this else "" 3585 3586 on = self.sql(expression, "on") 3587 on = f" ON {on}" if on else "" 3588 return f"DISTINCT{this}{on}"
3617 def div_sql(self, expression: exp.Div) -> str: 3618 l, r = expression.left, expression.right 3619 3620 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3621 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3622 3623 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3624 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3625 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3626 3627 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3628 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3629 return self.sql( 3630 exp.cast( 3631 l / r, 3632 to=exp.DataType.Type.BIGINT, 3633 ) 3634 ) 3635 3636 return self.binary(expression, "/")
3753 def log_sql(self, expression: exp.Log) -> str: 3754 this = expression.this 3755 expr = expression.expression 3756 3757 if self.dialect.LOG_BASE_FIRST is False: 3758 this, expr = expr, this 3759 elif self.dialect.LOG_BASE_FIRST is None and expr: 3760 if this.name in ("2", "10"): 3761 return self.func(f"LOG{this.name}", expr) 3762 3763 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3764 3765 return self.func("LOG", this, expr)
3774 def binary(self, expression: exp.Binary, op: str) -> str: 3775 sqls: t.List[str] = [] 3776 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3777 binary_type = type(expression) 3778 3779 while stack: 3780 node = stack.pop() 3781 3782 if type(node) is binary_type: 3783 op_func = node.args.get("operator") 3784 if op_func: 3785 op = f"OPERATOR({self.sql(op_func)})" 3786 3787 stack.append(node.right) 3788 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3789 stack.append(node.left) 3790 else: 3791 sqls.append(self.sql(node)) 3792 3793 return "".join(sqls)
3802 def function_fallback_sql(self, expression: exp.Func) -> str: 3803 args = [] 3804 3805 for key in expression.arg_types: 3806 arg_value = expression.args.get(key) 3807 3808 if isinstance(arg_value, list): 3809 for value in arg_value: 3810 args.append(value) 3811 elif arg_value is not None: 3812 args.append(arg_value) 3813 3814 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3815 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3816 else: 3817 name = expression.sql_name() 3818 3819 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:
3821 def func( 3822 self, 3823 name: str, 3824 *args: t.Optional[exp.Expression | str], 3825 prefix: str = "(", 3826 suffix: str = ")", 3827 normalize: bool = True, 3828 ) -> str: 3829 name = self.normalize_func(name) if normalize else name 3830 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3832 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3833 arg_sqls = tuple( 3834 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3835 ) 3836 if self.pretty and self.too_wide(arg_sqls): 3837 return self.indent( 3838 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3839 ) 3840 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]:
3845 def format_time( 3846 self, 3847 expression: exp.Expression, 3848 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3849 inverse_time_trie: t.Optional[t.Dict] = None, 3850 ) -> t.Optional[str]: 3851 return format_time( 3852 self.sql(expression, "format"), 3853 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3854 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3855 )
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:
3857 def expressions( 3858 self, 3859 expression: t.Optional[exp.Expression] = None, 3860 key: t.Optional[str] = None, 3861 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3862 flat: bool = False, 3863 indent: bool = True, 3864 skip_first: bool = False, 3865 skip_last: bool = False, 3866 sep: str = ", ", 3867 prefix: str = "", 3868 dynamic: bool = False, 3869 new_line: bool = False, 3870 ) -> str: 3871 expressions = expression.args.get(key or "expressions") if expression else sqls 3872 3873 if not expressions: 3874 return "" 3875 3876 if flat: 3877 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3878 3879 num_sqls = len(expressions) 3880 result_sqls = [] 3881 3882 for i, e in enumerate(expressions): 3883 sql = self.sql(e, comment=False) 3884 if not sql: 3885 continue 3886 3887 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3888 3889 if self.pretty: 3890 if self.leading_comma: 3891 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3892 else: 3893 result_sqls.append( 3894 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3895 ) 3896 else: 3897 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3898 3899 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3900 if new_line: 3901 result_sqls.insert(0, "") 3902 result_sqls.append("") 3903 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3904 else: 3905 result_sql = "".join(result_sqls) 3906 3907 return ( 3908 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3909 if indent 3910 else result_sql 3911 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3913 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3914 flat = flat or isinstance(expression.parent, exp.Properties) 3915 expressions_sql = self.expressions(expression, flat=flat) 3916 if flat: 3917 return f"{op} {expressions_sql}" 3918 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3920 def naked_property(self, expression: exp.Property) -> str: 3921 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3922 if not property_name: 3923 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3924 return f"{property_name} {self.sql(expression, 'this')}"
3932 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3933 this = self.sql(expression, "this") 3934 expressions = self.no_identify(self.expressions, expression) 3935 expressions = ( 3936 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3937 ) 3938 return f"{this}{expressions}" if expressions.strip() != "" else this
3948 def when_sql(self, expression: exp.When) -> str: 3949 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3950 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3951 condition = self.sql(expression, "condition") 3952 condition = f" AND {condition}" if condition else "" 3953 3954 then_expression = expression.args.get("then") 3955 if isinstance(then_expression, exp.Insert): 3956 this = self.sql(then_expression, "this") 3957 this = f"INSERT {this}" if this else "INSERT" 3958 then = self.sql(then_expression, "expression") 3959 then = f"{this} VALUES {then}" if then else this 3960 elif isinstance(then_expression, exp.Update): 3961 if isinstance(then_expression.args.get("expressions"), exp.Star): 3962 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3963 else: 3964 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3965 else: 3966 then = self.sql(then_expression) 3967 return f"WHEN {matched}{source}{condition} THEN {then}"
3972 def merge_sql(self, expression: exp.Merge) -> str: 3973 table = expression.this 3974 table_alias = "" 3975 3976 hints = table.args.get("hints") 3977 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3978 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3979 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3980 3981 this = self.sql(table) 3982 using = f"USING {self.sql(expression, 'using')}" 3983 on = f"ON {self.sql(expression, 'on')}" 3984 whens = self.sql(expression, "whens") 3985 3986 returning = self.sql(expression, "returning") 3987 if returning: 3988 whens = f"{whens}{returning}" 3989 3990 sep = self.sep() 3991 3992 return self.prepend_ctes( 3993 expression, 3994 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3995 )
4001 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4002 if not self.SUPPORTS_TO_NUMBER: 4003 self.unsupported("Unsupported TO_NUMBER function") 4004 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4005 4006 fmt = expression.args.get("format") 4007 if not fmt: 4008 self.unsupported("Conversion format is required for TO_NUMBER") 4009 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4010 4011 return self.func("TO_NUMBER", expression.this, fmt)
4013 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4014 this = self.sql(expression, "this") 4015 kind = self.sql(expression, "kind") 4016 settings_sql = self.expressions(expression, key="settings", sep=" ") 4017 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4018 return f"{this}({kind}{args})"
4037 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4038 expressions = self.expressions(expression, flat=True) 4039 expressions = f" {self.wrap(expressions)}" if expressions else "" 4040 buckets = self.sql(expression, "buckets") 4041 kind = self.sql(expression, "kind") 4042 buckets = f" BUCKETS {buckets}" if buckets else "" 4043 order = self.sql(expression, "order") 4044 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4049 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4050 expressions = self.expressions(expression, key="expressions", flat=True) 4051 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4052 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4053 buckets = self.sql(expression, "buckets") 4054 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4056 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4057 this = self.sql(expression, "this") 4058 having = self.sql(expression, "having") 4059 4060 if having: 4061 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4062 4063 return self.func("ANY_VALUE", this)
4065 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4066 transform = self.func("TRANSFORM", *expression.expressions) 4067 row_format_before = self.sql(expression, "row_format_before") 4068 row_format_before = f" {row_format_before}" if row_format_before else "" 4069 record_writer = self.sql(expression, "record_writer") 4070 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4071 using = f" USING {self.sql(expression, 'command_script')}" 4072 schema = self.sql(expression, "schema") 4073 schema = f" AS {schema}" if schema else "" 4074 row_format_after = self.sql(expression, "row_format_after") 4075 row_format_after = f" {row_format_after}" if row_format_after else "" 4076 record_reader = self.sql(expression, "record_reader") 4077 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4078 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4080 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4081 key_block_size = self.sql(expression, "key_block_size") 4082 if key_block_size: 4083 return f"KEY_BLOCK_SIZE = {key_block_size}" 4084 4085 using = self.sql(expression, "using") 4086 if using: 4087 return f"USING {using}" 4088 4089 parser = self.sql(expression, "parser") 4090 if parser: 4091 return f"WITH PARSER {parser}" 4092 4093 comment = self.sql(expression, "comment") 4094 if comment: 4095 return f"COMMENT {comment}" 4096 4097 visible = expression.args.get("visible") 4098 if visible is not None: 4099 return "VISIBLE" if visible else "INVISIBLE" 4100 4101 engine_attr = self.sql(expression, "engine_attr") 4102 if engine_attr: 4103 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4104 4105 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4106 if secondary_engine_attr: 4107 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4108 4109 self.unsupported("Unsupported index constraint option.") 4110 return ""
4116 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4117 kind = self.sql(expression, "kind") 4118 kind = f"{kind} INDEX" if kind else "INDEX" 4119 this = self.sql(expression, "this") 4120 this = f" {this}" if this else "" 4121 index_type = self.sql(expression, "index_type") 4122 index_type = f" USING {index_type}" if index_type else "" 4123 expressions = self.expressions(expression, flat=True) 4124 expressions = f" ({expressions})" if expressions else "" 4125 options = self.expressions(expression, key="options", sep=" ") 4126 options = f" {options}" if options else "" 4127 return f"{kind}{this}{index_type}{expressions}{options}"
4129 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4130 if self.NVL2_SUPPORTED: 4131 return self.function_fallback_sql(expression) 4132 4133 case = exp.Case().when( 4134 expression.this.is_(exp.null()).not_(copy=False), 4135 expression.args["true"], 4136 copy=False, 4137 ) 4138 else_cond = expression.args.get("false") 4139 if else_cond: 4140 case.else_(else_cond, copy=False) 4141 4142 return self.sql(case)
4144 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4145 this = self.sql(expression, "this") 4146 expr = self.sql(expression, "expression") 4147 iterator = self.sql(expression, "iterator") 4148 condition = self.sql(expression, "condition") 4149 condition = f" IF {condition}" if condition else "" 4150 return f"{this} FOR {expr} IN {iterator}{condition}"
4158 def predict_sql(self, expression: exp.Predict) -> str: 4159 model = self.sql(expression, "this") 4160 model = f"MODEL {model}" 4161 table = self.sql(expression, "expression") 4162 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4163 parameters = self.sql(expression, "params_struct") 4164 return self.func("PREDICT", model, table, parameters or None)
4176 def toarray_sql(self, expression: exp.ToArray) -> str: 4177 arg = expression.this 4178 if not arg.type: 4179 from sqlglot.optimizer.annotate_types import annotate_types 4180 4181 arg = annotate_types(arg, dialect=self.dialect) 4182 4183 if arg.is_type(exp.DataType.Type.ARRAY): 4184 return self.sql(arg) 4185 4186 cond_for_null = arg.is_(exp.null()) 4187 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4189 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4190 this = expression.this 4191 time_format = self.format_time(expression) 4192 4193 if time_format: 4194 return self.sql( 4195 exp.cast( 4196 exp.StrToTime(this=this, format=expression.args["format"]), 4197 exp.DataType.Type.TIME, 4198 ) 4199 ) 4200 4201 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4202 return self.sql(this) 4203 4204 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4206 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4207 this = expression.this 4208 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4209 return self.sql(this) 4210 4211 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4213 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4214 this = expression.this 4215 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4216 return self.sql(this) 4217 4218 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4220 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4221 this = expression.this 4222 time_format = self.format_time(expression) 4223 4224 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4225 return self.sql( 4226 exp.cast( 4227 exp.StrToTime(this=this, format=expression.args["format"]), 4228 exp.DataType.Type.DATE, 4229 ) 4230 ) 4231 4232 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4233 return self.sql(this) 4234 4235 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4247 def lastday_sql(self, expression: exp.LastDay) -> str: 4248 if self.LAST_DAY_SUPPORTS_DATE_PART: 4249 return self.function_fallback_sql(expression) 4250 4251 unit = expression.text("unit") 4252 if unit and unit != "MONTH": 4253 self.unsupported("Date parts are not supported in LAST_DAY.") 4254 4255 return self.func("LAST_DAY", expression.this)
4264 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4265 if self.CAN_IMPLEMENT_ARRAY_ANY: 4266 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4267 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4268 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4269 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4270 4271 from sqlglot.dialects import Dialect 4272 4273 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4274 if self.dialect.__class__ != Dialect: 4275 self.unsupported("ARRAY_ANY is unsupported") 4276 4277 return self.function_fallback_sql(expression)
4279 def struct_sql(self, expression: exp.Struct) -> str: 4280 expression.set( 4281 "expressions", 4282 [ 4283 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4284 if isinstance(e, exp.PropertyEQ) 4285 else e 4286 for e in expression.expressions 4287 ], 4288 ) 4289 4290 return self.function_fallback_sql(expression)
4298 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4299 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4300 tables = f" {self.expressions(expression)}" 4301 4302 exists = " IF EXISTS" if expression.args.get("exists") else "" 4303 4304 on_cluster = self.sql(expression, "cluster") 4305 on_cluster = f" {on_cluster}" if on_cluster else "" 4306 4307 identity = self.sql(expression, "identity") 4308 identity = f" {identity} IDENTITY" if identity else "" 4309 4310 option = self.sql(expression, "option") 4311 option = f" {option}" if option else "" 4312 4313 partition = self.sql(expression, "partition") 4314 partition = f" {partition}" if partition else "" 4315 4316 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4320 def convert_sql(self, expression: exp.Convert) -> str: 4321 to = expression.this 4322 value = expression.expression 4323 style = expression.args.get("style") 4324 safe = expression.args.get("safe") 4325 strict = expression.args.get("strict") 4326 4327 if not to or not value: 4328 return "" 4329 4330 # Retrieve length of datatype and override to default if not specified 4331 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4332 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4333 4334 transformed: t.Optional[exp.Expression] = None 4335 cast = exp.Cast if strict else exp.TryCast 4336 4337 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4338 if isinstance(style, exp.Literal) and style.is_int: 4339 from sqlglot.dialects.tsql import TSQL 4340 4341 style_value = style.name 4342 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4343 if not converted_style: 4344 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4345 4346 fmt = exp.Literal.string(converted_style) 4347 4348 if to.this == exp.DataType.Type.DATE: 4349 transformed = exp.StrToDate(this=value, format=fmt) 4350 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4351 transformed = exp.StrToTime(this=value, format=fmt) 4352 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4353 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4354 elif to.this == exp.DataType.Type.TEXT: 4355 transformed = exp.TimeToStr(this=value, format=fmt) 4356 4357 if not transformed: 4358 transformed = cast(this=value, to=to, safe=safe) 4359 4360 return self.sql(transformed)
4428 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4429 option = self.sql(expression, "this") 4430 4431 if expression.expressions: 4432 upper = option.upper() 4433 4434 # Snowflake FILE_FORMAT options are separated by whitespace 4435 sep = " " if upper == "FILE_FORMAT" else ", " 4436 4437 # Databricks copy/format options do not set their list of values with EQ 4438 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4439 values = self.expressions(expression, flat=True, sep=sep) 4440 return f"{option}{op}({values})" 4441 4442 value = self.sql(expression, "expression") 4443 4444 if not value: 4445 return option 4446 4447 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4448 4449 return f"{option}{op}{value}"
4451 def credentials_sql(self, expression: exp.Credentials) -> str: 4452 cred_expr = expression.args.get("credentials") 4453 if isinstance(cred_expr, exp.Literal): 4454 # Redshift case: CREDENTIALS <string> 4455 credentials = self.sql(expression, "credentials") 4456 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4457 else: 4458 # Snowflake case: CREDENTIALS = (...) 4459 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4460 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4461 4462 storage = self.sql(expression, "storage") 4463 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4464 4465 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4466 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4467 4468 iam_role = self.sql(expression, "iam_role") 4469 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4470 4471 region = self.sql(expression, "region") 4472 region = f" REGION {region}" if region else "" 4473 4474 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4476 def copy_sql(self, expression: exp.Copy) -> str: 4477 this = self.sql(expression, "this") 4478 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4479 4480 credentials = self.sql(expression, "credentials") 4481 credentials = self.seg(credentials) if credentials else "" 4482 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4483 files = self.expressions(expression, key="files", flat=True) 4484 4485 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4486 params = self.expressions( 4487 expression, 4488 key="params", 4489 sep=sep, 4490 new_line=True, 4491 skip_last=True, 4492 skip_first=True, 4493 indent=self.COPY_PARAMS_ARE_WRAPPED, 4494 ) 4495 4496 if params: 4497 if self.COPY_PARAMS_ARE_WRAPPED: 4498 params = f" WITH ({params})" 4499 elif not self.pretty: 4500 params = f" {params}" 4501 4502 return f"COPY{this}{kind} {files}{credentials}{params}"
4507 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4508 on_sql = "ON" if expression.args.get("on") else "OFF" 4509 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4510 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4511 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4512 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4513 4514 if filter_col or retention_period: 4515 on_sql = self.func("ON", filter_col, retention_period) 4516 4517 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4519 def maskingpolicycolumnconstraint_sql( 4520 self, expression: exp.MaskingPolicyColumnConstraint 4521 ) -> str: 4522 this = self.sql(expression, "this") 4523 expressions = self.expressions(expression, flat=True) 4524 expressions = f" USING ({expressions})" if expressions else "" 4525 return f"MASKING POLICY {this}{expressions}"
4535 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4536 this = self.sql(expression, "this") 4537 expr = expression.expression 4538 4539 if isinstance(expr, exp.Func): 4540 # T-SQL's CLR functions are case sensitive 4541 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4542 else: 4543 expr = self.sql(expression, "expression") 4544 4545 return self.scope_resolution(expr, this)
4553 def rand_sql(self, expression: exp.Rand) -> str: 4554 lower = self.sql(expression, "lower") 4555 upper = self.sql(expression, "upper") 4556 4557 if lower and upper: 4558 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4559 return self.func("RAND", expression.this)
4561 def changes_sql(self, expression: exp.Changes) -> str: 4562 information = self.sql(expression, "information") 4563 information = f"INFORMATION => {information}" 4564 at_before = self.sql(expression, "at_before") 4565 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4566 end = self.sql(expression, "end") 4567 end = f"{self.seg('')}{end}" if end else "" 4568 4569 return f"CHANGES ({information}){at_before}{end}"
4571 def pad_sql(self, expression: exp.Pad) -> str: 4572 prefix = "L" if expression.args.get("is_left") else "R" 4573 4574 fill_pattern = self.sql(expression, "fill_pattern") or None 4575 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4576 fill_pattern = "' '" 4577 4578 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4584 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4585 generate_series = exp.GenerateSeries(**expression.args) 4586 4587 parent = expression.parent 4588 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4589 parent = parent.parent 4590 4591 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4592 return self.sql(exp.Unnest(expressions=[generate_series])) 4593 4594 if isinstance(parent, exp.Select): 4595 self.unsupported("GenerateSeries projection unnesting is not supported.") 4596 4597 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4599 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4600 exprs = expression.expressions 4601 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4602 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4603 else: 4604 rhs = self.expressions(expression) 4605 4606 return self.func(name, expression.this, rhs or None)
4608 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4609 if self.SUPPORTS_CONVERT_TIMEZONE: 4610 return self.function_fallback_sql(expression) 4611 4612 source_tz = expression.args.get("source_tz") 4613 target_tz = expression.args.get("target_tz") 4614 timestamp = expression.args.get("timestamp") 4615 4616 if source_tz and timestamp: 4617 timestamp = exp.AtTimeZone( 4618 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4619 ) 4620 4621 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4622 4623 return self.sql(expr)
4625 def json_sql(self, expression: exp.JSON) -> str: 4626 this = self.sql(expression, "this") 4627 this = f" {this}" if this else "" 4628 4629 _with = expression.args.get("with") 4630 4631 if _with is None: 4632 with_sql = "" 4633 elif not _with: 4634 with_sql = " WITHOUT" 4635 else: 4636 with_sql = " WITH" 4637 4638 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4639 4640 return f"JSON{this}{with_sql}{unique_sql}"
4642 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4643 def _generate_on_options(arg: t.Any) -> str: 4644 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4645 4646 path = self.sql(expression, "path") 4647 returning = self.sql(expression, "returning") 4648 returning = f" RETURNING {returning}" if returning else "" 4649 4650 on_condition = self.sql(expression, "on_condition") 4651 on_condition = f" {on_condition}" if on_condition else "" 4652 4653 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4655 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4656 else_ = "ELSE " if expression.args.get("else_") else "" 4657 condition = self.sql(expression, "expression") 4658 condition = f"WHEN {condition} THEN " if condition else else_ 4659 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4660 return f"{condition}{insert}"
4668 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4669 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4670 empty = expression.args.get("empty") 4671 empty = ( 4672 f"DEFAULT {empty} ON EMPTY" 4673 if isinstance(empty, exp.Expression) 4674 else self.sql(expression, "empty") 4675 ) 4676 4677 error = expression.args.get("error") 4678 error = ( 4679 f"DEFAULT {error} ON ERROR" 4680 if isinstance(error, exp.Expression) 4681 else self.sql(expression, "error") 4682 ) 4683 4684 if error and empty: 4685 error = ( 4686 f"{empty} {error}" 4687 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4688 else f"{error} {empty}" 4689 ) 4690 empty = "" 4691 4692 null = self.sql(expression, "null") 4693 4694 return f"{empty}{error}{null}"
4700 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4701 this = self.sql(expression, "this") 4702 path = self.sql(expression, "path") 4703 4704 passing = self.expressions(expression, "passing") 4705 passing = f" PASSING {passing}" if passing else "" 4706 4707 on_condition = self.sql(expression, "on_condition") 4708 on_condition = f" {on_condition}" if on_condition else "" 4709 4710 path = f"{path}{passing}{on_condition}" 4711 4712 return self.func("JSON_EXISTS", this, path)
4714 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4715 array_agg = self.function_fallback_sql(expression) 4716 4717 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4718 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4719 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4720 parent = expression.parent 4721 if isinstance(parent, exp.Filter): 4722 parent_cond = parent.expression.this 4723 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4724 else: 4725 this = expression.this 4726 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4727 if this.find(exp.Column): 4728 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4729 this_sql = ( 4730 self.expressions(this) 4731 if isinstance(this, exp.Distinct) 4732 else self.sql(expression, "this") 4733 ) 4734 4735 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4736 4737 return array_agg
4745 def grant_sql(self, expression: exp.Grant) -> str: 4746 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4747 4748 kind = self.sql(expression, "kind") 4749 kind = f" {kind}" if kind else "" 4750 4751 securable = self.sql(expression, "securable") 4752 securable = f" {securable}" if securable else "" 4753 4754 principals = self.expressions(expression, key="principals", flat=True) 4755 4756 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4757 4758 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4782 def overlay_sql(self, expression: exp.Overlay): 4783 this = self.sql(expression, "this") 4784 expr = self.sql(expression, "expression") 4785 from_sql = self.sql(expression, "from") 4786 for_sql = self.sql(expression, "for") 4787 for_sql = f" FOR {for_sql}" if for_sql else "" 4788 4789 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4795 def string_sql(self, expression: exp.String) -> str: 4796 this = expression.this 4797 zone = expression.args.get("zone") 4798 4799 if zone: 4800 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4801 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4802 # set for source_tz to transpile the time conversion before the STRING cast 4803 this = exp.ConvertTimezone( 4804 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4805 ) 4806 4807 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4817 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4818 filler = self.sql(expression, "this") 4819 filler = f" {filler}" if filler else "" 4820 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4821 return f"TRUNCATE{filler} {with_count}"
4823 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4824 if self.SUPPORTS_UNIX_SECONDS: 4825 return self.function_fallback_sql(expression) 4826 4827 start_ts = exp.cast( 4828 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4829 ) 4830 4831 return self.sql( 4832 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4833 )
4835 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4836 dim = expression.expression 4837 4838 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4839 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4840 if not (dim.is_int and dim.name == "1"): 4841 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4842 dim = None 4843 4844 # If dimension is required but not specified, default initialize it 4845 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4846 dim = exp.Literal.number(1) 4847 4848 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4850 def attach_sql(self, expression: exp.Attach) -> str: 4851 this = self.sql(expression, "this") 4852 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4853 expressions = self.expressions(expression) 4854 expressions = f" ({expressions})" if expressions else "" 4855 4856 return f"ATTACH{exists_sql} {this}{expressions}"
4858 def detach_sql(self, expression: exp.Detach) -> str: 4859 this = self.sql(expression, "this") 4860 # the DATABASE keyword is required if IF EXISTS is set 4861 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4862 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4863 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4864 4865 return f"DETACH{exists_sql} {this}"
4873 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4874 this_sql = self.sql(expression, "this") 4875 if isinstance(expression.this, exp.Table): 4876 this_sql = f"TABLE {this_sql}" 4877 4878 return self.func( 4879 "FEATURES_AT_TIME", 4880 this_sql, 4881 expression.args.get("time"), 4882 expression.args.get("num_rows"), 4883 expression.args.get("ignore_feature_nulls"), 4884 )
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4891 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4892 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4893 encode = f"{encode} {self.sql(expression, 'this')}" 4894 4895 properties = expression.args.get("properties") 4896 if properties: 4897 encode = f"{encode} {self.properties(properties)}" 4898 4899 return encode
4901 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4902 this = self.sql(expression, "this") 4903 include = f"INCLUDE {this}" 4904 4905 column_def = self.sql(expression, "column_def") 4906 if column_def: 4907 include = f"{include} {column_def}" 4908 4909 alias = self.sql(expression, "alias") 4910 if alias: 4911 include = f"{include} AS {alias}" 4912 4913 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
4925 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4926 partitions = self.expressions(expression, "partition_expressions") 4927 create = self.expressions(expression, "create_expressions") 4928 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
4930 def partitionbyrangepropertydynamic_sql( 4931 self, expression: exp.PartitionByRangePropertyDynamic 4932 ) -> str: 4933 start = self.sql(expression, "start") 4934 end = self.sql(expression, "end") 4935 4936 every = expression.args["every"] 4937 if isinstance(every, exp.Interval) and every.this.is_string: 4938 every.this.replace(exp.Literal.number(every.name)) 4939 4940 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4953 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4954 kind = self.sql(expression, "kind") 4955 option = self.sql(expression, "option") 4956 option = f" {option}" if option else "" 4957 this = self.sql(expression, "this") 4958 this = f" {this}" if this else "" 4959 columns = self.expressions(expression) 4960 columns = f" {columns}" if columns else "" 4961 return f"{kind}{option} STATISTICS{this}{columns}"
4963 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4964 this = self.sql(expression, "this") 4965 columns = self.expressions(expression) 4966 inner_expression = self.sql(expression, "expression") 4967 inner_expression = f" {inner_expression}" if inner_expression else "" 4968 update_options = self.sql(expression, "update_options") 4969 update_options = f" {update_options} UPDATE" if update_options else "" 4970 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
4981 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4982 kind = self.sql(expression, "kind") 4983 this = self.sql(expression, "this") 4984 this = f" {this}" if this else "" 4985 inner_expression = self.sql(expression, "expression") 4986 return f"VALIDATE {kind}{this}{inner_expression}"
4988 def analyze_sql(self, expression: exp.Analyze) -> str: 4989 options = self.expressions(expression, key="options", sep=" ") 4990 options = f" {options}" if options else "" 4991 kind = self.sql(expression, "kind") 4992 kind = f" {kind}" if kind else "" 4993 this = self.sql(expression, "this") 4994 this = f" {this}" if this else "" 4995 mode = self.sql(expression, "mode") 4996 mode = f" {mode}" if mode else "" 4997 properties = self.sql(expression, "properties") 4998 properties = f" {properties}" if properties else "" 4999 partition = self.sql(expression, "partition") 5000 partition = f" {partition}" if partition else "" 5001 inner_expression = self.sql(expression, "expression") 5002 inner_expression = f" {inner_expression}" if inner_expression else "" 5003 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5005 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5006 this = self.sql(expression, "this") 5007 namespaces = self.expressions(expression, key="namespaces") 5008 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5009 passing = self.expressions(expression, key="passing") 5010 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5011 columns = self.expressions(expression, key="columns") 5012 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5013 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5014 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5020 def export_sql(self, expression: exp.Export) -> str: 5021 this = self.sql(expression, "this") 5022 connection = self.sql(expression, "connection") 5023 connection = f"WITH CONNECTION {connection} " if connection else "" 5024 options = self.sql(expression, "options") 5025 return f"EXPORT DATA {connection}{options} AS {this}"
5030 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5031 variable = self.sql(expression, "this") 5032 default = self.sql(expression, "default") 5033 default = f" = {default}" if default else "" 5034 5035 kind = self.sql(expression, "kind") 5036 if isinstance(expression.args.get("kind"), exp.Schema): 5037 kind = f"TABLE {kind}" 5038 5039 return f"{variable} AS {kind}{default}"
5041 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5042 kind = self.sql(expression, "kind") 5043 this = self.sql(expression, "this") 5044 set = self.sql(expression, "expression") 5045 using = self.sql(expression, "using") 5046 using = f" USING {using}" if using else "" 5047 5048 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5049 5050 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5069 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5070 # Snowflake GET/PUT statements: 5071 # PUT <file> <internalStage> <properties> 5072 # GET <internalStage> <file> <properties> 5073 props = expression.args.get("properties") 5074 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5075 this = self.sql(expression, "this") 5076 target = self.sql(expression, "target") 5077 5078 if isinstance(expression, exp.Put): 5079 return f"PUT {this} {target}{props_sql}" 5080 else: 5081 return f"GET {target} {this}{props_sql}"
5089 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5090 if self.SUPPORTS_DECODE_CASE: 5091 return self.func("DECODE", *expression.expressions) 5092 5093 expression, *expressions = expression.expressions 5094 5095 ifs = [] 5096 for search, result in zip(expressions[::2], expressions[1::2]): 5097 if isinstance(search, exp.Literal): 5098 ifs.append(exp.If(this=expression.eq(search), true=result)) 5099 elif isinstance(search, exp.Null): 5100 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5101 else: 5102 if isinstance(search, exp.Binary): 5103 search = exp.paren(search) 5104 5105 cond = exp.or_( 5106 expression.eq(search), 5107 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5108 copy=False, 5109 ) 5110 ifs.append(exp.If(this=cond, true=result)) 5111 5112 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5113 return self.sql(case)
5115 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5116 this = self.sql(expression, "this") 5117 this = self.seg(this, sep="") 5118 dimensions = self.expressions( 5119 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5120 ) 5121 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5122 metrics = self.expressions( 5123 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5124 ) 5125 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5126 where = self.sql(expression, "where") 5127 where = self.seg(f"WHERE {where}") if where else "" 5128 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"
5130 def getextract_sql(self, expression: exp.GetExtract) -> str: 5131 this = expression.this 5132 expr = expression.expression 5133 5134 if not this.type or not expression.type: 5135 from sqlglot.optimizer.annotate_types import annotate_types 5136 5137 this = annotate_types(this, dialect=self.dialect) 5138 5139 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5140 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5141 5142 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))