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 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 515 516 TIME_PART_SINGULARS = { 517 "MICROSECONDS": "MICROSECOND", 518 "SECONDS": "SECOND", 519 "MINUTES": "MINUTE", 520 "HOURS": "HOUR", 521 "DAYS": "DAY", 522 "WEEKS": "WEEK", 523 "MONTHS": "MONTH", 524 "QUARTERS": "QUARTER", 525 "YEARS": "YEAR", 526 } 527 528 AFTER_HAVING_MODIFIER_TRANSFORMS = { 529 "cluster": lambda self, e: self.sql(e, "cluster"), 530 "distribute": lambda self, e: self.sql(e, "distribute"), 531 "sort": lambda self, e: self.sql(e, "sort"), 532 "windows": lambda self, e: ( 533 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 534 if e.args.get("windows") 535 else "" 536 ), 537 "qualify": lambda self, e: self.sql(e, "qualify"), 538 } 539 540 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 541 542 STRUCT_DELIMITER = ("<", ">") 543 544 PARAMETER_TOKEN = "@" 545 NAMED_PLACEHOLDER_TOKEN = ":" 546 547 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 548 549 PROPERTIES_LOCATION = { 550 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 551 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 552 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 556 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 558 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 559 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 560 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 561 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 562 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 565 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 567 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 568 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 570 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 573 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 574 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 578 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 579 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 580 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 581 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 582 exp.HeapProperty: exp.Properties.Location.POST_WITH, 583 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 585 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 588 exp.JournalProperty: exp.Properties.Location.POST_NAME, 589 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 593 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 594 exp.LogProperty: exp.Properties.Location.POST_NAME, 595 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 596 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 597 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 598 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 600 exp.Order: exp.Properties.Location.POST_SCHEMA, 601 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 603 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 605 exp.Property: exp.Properties.Location.POST_WITH, 606 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 611 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 614 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 616 exp.Set: exp.Properties.Location.POST_SCHEMA, 617 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.SetProperty: exp.Properties.Location.POST_CREATE, 619 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 620 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 621 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 622 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 625 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 628 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.Tags: exp.Properties.Location.POST_WITH, 630 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 631 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 633 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 635 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 636 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 639 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 640 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 641 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 642 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 643 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 644 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 645 } 646 647 # Keywords that can't be used as unquoted identifier names 648 RESERVED_KEYWORDS: t.Set[str] = set() 649 650 # Expressions whose comments are separated from them for better formatting 651 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 652 exp.Command, 653 exp.Create, 654 exp.Describe, 655 exp.Delete, 656 exp.Drop, 657 exp.From, 658 exp.Insert, 659 exp.Join, 660 exp.MultitableInserts, 661 exp.Order, 662 exp.Group, 663 exp.Having, 664 exp.Select, 665 exp.SetOperation, 666 exp.Update, 667 exp.Where, 668 exp.With, 669 ) 670 671 # Expressions that should not have their comments generated in maybe_comment 672 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 673 exp.Binary, 674 exp.SetOperation, 675 ) 676 677 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 678 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 679 exp.Column, 680 exp.Literal, 681 exp.Neg, 682 exp.Paren, 683 ) 684 685 PARAMETERIZABLE_TEXT_TYPES = { 686 exp.DataType.Type.NVARCHAR, 687 exp.DataType.Type.VARCHAR, 688 exp.DataType.Type.CHAR, 689 exp.DataType.Type.NCHAR, 690 } 691 692 # Expressions that need to have all CTEs under them bubbled up to them 693 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 694 695 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 696 697 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 698 699 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 700 701 __slots__ = ( 702 "pretty", 703 "identify", 704 "normalize", 705 "pad", 706 "_indent", 707 "normalize_functions", 708 "unsupported_level", 709 "max_unsupported", 710 "leading_comma", 711 "max_text_width", 712 "comments", 713 "dialect", 714 "unsupported_messages", 715 "_escaped_quote_end", 716 "_escaped_identifier_end", 717 "_next_name", 718 "_identifier_start", 719 "_identifier_end", 720 "_quote_json_path_key_using_brackets", 721 ) 722 723 def __init__( 724 self, 725 pretty: t.Optional[bool] = None, 726 identify: str | bool = False, 727 normalize: bool = False, 728 pad: int = 2, 729 indent: int = 2, 730 normalize_functions: t.Optional[str | bool] = None, 731 unsupported_level: ErrorLevel = ErrorLevel.WARN, 732 max_unsupported: int = 3, 733 leading_comma: bool = False, 734 max_text_width: int = 80, 735 comments: bool = True, 736 dialect: DialectType = None, 737 ): 738 import sqlglot 739 from sqlglot.dialects import Dialect 740 741 self.pretty = pretty if pretty is not None else sqlglot.pretty 742 self.identify = identify 743 self.normalize = normalize 744 self.pad = pad 745 self._indent = indent 746 self.unsupported_level = unsupported_level 747 self.max_unsupported = max_unsupported 748 self.leading_comma = leading_comma 749 self.max_text_width = max_text_width 750 self.comments = comments 751 self.dialect = Dialect.get_or_raise(dialect) 752 753 # This is both a Dialect property and a Generator argument, so we prioritize the latter 754 self.normalize_functions = ( 755 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 756 ) 757 758 self.unsupported_messages: t.List[str] = [] 759 self._escaped_quote_end: str = ( 760 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 761 ) 762 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 763 764 self._next_name = name_sequence("_t") 765 766 self._identifier_start = self.dialect.IDENTIFIER_START 767 self._identifier_end = self.dialect.IDENTIFIER_END 768 769 self._quote_json_path_key_using_brackets = True 770 771 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 772 """ 773 Generates the SQL string corresponding to the given syntax tree. 774 775 Args: 776 expression: The syntax tree. 777 copy: Whether to copy the expression. The generator performs mutations so 778 it is safer to copy. 779 780 Returns: 781 The SQL string corresponding to `expression`. 782 """ 783 if copy: 784 expression = expression.copy() 785 786 expression = self.preprocess(expression) 787 788 self.unsupported_messages = [] 789 sql = self.sql(expression).strip() 790 791 if self.pretty: 792 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 793 794 if self.unsupported_level == ErrorLevel.IGNORE: 795 return sql 796 797 if self.unsupported_level == ErrorLevel.WARN: 798 for msg in self.unsupported_messages: 799 logger.warning(msg) 800 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 801 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 802 803 return sql 804 805 def preprocess(self, expression: exp.Expression) -> exp.Expression: 806 """Apply generic preprocessing transformations to a given expression.""" 807 expression = self._move_ctes_to_top_level(expression) 808 809 if self.ENSURE_BOOLS: 810 from sqlglot.transforms import ensure_bools 811 812 expression = ensure_bools(expression) 813 814 return expression 815 816 def _move_ctes_to_top_level(self, expression: E) -> E: 817 if ( 818 not expression.parent 819 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 820 and any(node.parent is not expression for node in expression.find_all(exp.With)) 821 ): 822 from sqlglot.transforms import move_ctes_to_top_level 823 824 expression = move_ctes_to_top_level(expression) 825 return expression 826 827 def unsupported(self, message: str) -> None: 828 if self.unsupported_level == ErrorLevel.IMMEDIATE: 829 raise UnsupportedError(message) 830 self.unsupported_messages.append(message) 831 832 def sep(self, sep: str = " ") -> str: 833 return f"{sep.strip()}\n" if self.pretty else sep 834 835 def seg(self, sql: str, sep: str = " ") -> str: 836 return f"{self.sep(sep)}{sql}" 837 838 def sanitize_comment(self, comment: str) -> str: 839 comment = " " + comment if comment[0].strip() else comment 840 comment = comment + " " if comment[-1].strip() else comment 841 842 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 843 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 844 comment = comment.replace("*/", "* /") 845 846 return comment 847 848 def maybe_comment( 849 self, 850 sql: str, 851 expression: t.Optional[exp.Expression] = None, 852 comments: t.Optional[t.List[str]] = None, 853 separated: bool = False, 854 ) -> str: 855 comments = ( 856 ((expression and expression.comments) if comments is None else comments) # type: ignore 857 if self.comments 858 else None 859 ) 860 861 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 862 return sql 863 864 comments_sql = " ".join( 865 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 866 ) 867 868 if not comments_sql: 869 return sql 870 871 comments_sql = self._replace_line_breaks(comments_sql) 872 873 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 874 return ( 875 f"{self.sep()}{comments_sql}{sql}" 876 if not sql or sql[0].isspace() 877 else f"{comments_sql}{self.sep()}{sql}" 878 ) 879 880 return f"{sql} {comments_sql}" 881 882 def wrap(self, expression: exp.Expression | str) -> str: 883 this_sql = ( 884 self.sql(expression) 885 if isinstance(expression, exp.UNWRAPPED_QUERIES) 886 else self.sql(expression, "this") 887 ) 888 if not this_sql: 889 return "()" 890 891 this_sql = self.indent(this_sql, level=1, pad=0) 892 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 893 894 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 895 original = self.identify 896 self.identify = False 897 result = func(*args, **kwargs) 898 self.identify = original 899 return result 900 901 def normalize_func(self, name: str) -> str: 902 if self.normalize_functions == "upper" or self.normalize_functions is True: 903 return name.upper() 904 if self.normalize_functions == "lower": 905 return name.lower() 906 return name 907 908 def indent( 909 self, 910 sql: str, 911 level: int = 0, 912 pad: t.Optional[int] = None, 913 skip_first: bool = False, 914 skip_last: bool = False, 915 ) -> str: 916 if not self.pretty or not sql: 917 return sql 918 919 pad = self.pad if pad is None else pad 920 lines = sql.split("\n") 921 922 return "\n".join( 923 ( 924 line 925 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 926 else f"{' ' * (level * self._indent + pad)}{line}" 927 ) 928 for i, line in enumerate(lines) 929 ) 930 931 def sql( 932 self, 933 expression: t.Optional[str | exp.Expression], 934 key: t.Optional[str] = None, 935 comment: bool = True, 936 ) -> str: 937 if not expression: 938 return "" 939 940 if isinstance(expression, str): 941 return expression 942 943 if key: 944 value = expression.args.get(key) 945 if value: 946 return self.sql(value) 947 return "" 948 949 transform = self.TRANSFORMS.get(expression.__class__) 950 951 if callable(transform): 952 sql = transform(self, expression) 953 elif isinstance(expression, exp.Expression): 954 exp_handler_name = f"{expression.key}_sql" 955 956 if hasattr(self, exp_handler_name): 957 sql = getattr(self, exp_handler_name)(expression) 958 elif isinstance(expression, exp.Func): 959 sql = self.function_fallback_sql(expression) 960 elif isinstance(expression, exp.Property): 961 sql = self.property_sql(expression) 962 else: 963 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 964 else: 965 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 966 967 return self.maybe_comment(sql, expression) if self.comments and comment else sql 968 969 def uncache_sql(self, expression: exp.Uncache) -> str: 970 table = self.sql(expression, "this") 971 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 972 return f"UNCACHE TABLE{exists_sql} {table}" 973 974 def cache_sql(self, expression: exp.Cache) -> str: 975 lazy = " LAZY" if expression.args.get("lazy") else "" 976 table = self.sql(expression, "this") 977 options = expression.args.get("options") 978 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 979 sql = self.sql(expression, "expression") 980 sql = f" AS{self.sep()}{sql}" if sql else "" 981 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 982 return self.prepend_ctes(expression, sql) 983 984 def characterset_sql(self, expression: exp.CharacterSet) -> str: 985 if isinstance(expression.parent, exp.Cast): 986 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 987 default = "DEFAULT " if expression.args.get("default") else "" 988 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 989 990 def column_parts(self, expression: exp.Column) -> str: 991 return ".".join( 992 self.sql(part) 993 for part in ( 994 expression.args.get("catalog"), 995 expression.args.get("db"), 996 expression.args.get("table"), 997 expression.args.get("this"), 998 ) 999 if part 1000 ) 1001 1002 def column_sql(self, expression: exp.Column) -> str: 1003 join_mark = " (+)" if expression.args.get("join_mark") else "" 1004 1005 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1006 join_mark = "" 1007 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1008 1009 return f"{self.column_parts(expression)}{join_mark}" 1010 1011 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1012 this = self.sql(expression, "this") 1013 this = f" {this}" if this else "" 1014 position = self.sql(expression, "position") 1015 return f"{position}{this}" 1016 1017 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1018 column = self.sql(expression, "this") 1019 kind = self.sql(expression, "kind") 1020 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1021 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1022 kind = f"{sep}{kind}" if kind else "" 1023 constraints = f" {constraints}" if constraints else "" 1024 position = self.sql(expression, "position") 1025 position = f" {position}" if position else "" 1026 1027 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1028 kind = "" 1029 1030 return f"{exists}{column}{kind}{constraints}{position}" 1031 1032 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1033 this = self.sql(expression, "this") 1034 kind_sql = self.sql(expression, "kind").strip() 1035 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1036 1037 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1038 this = self.sql(expression, "this") 1039 if expression.args.get("not_null"): 1040 persisted = " PERSISTED NOT NULL" 1041 elif expression.args.get("persisted"): 1042 persisted = " PERSISTED" 1043 else: 1044 persisted = "" 1045 1046 return f"AS {this}{persisted}" 1047 1048 def autoincrementcolumnconstraint_sql(self, _) -> str: 1049 return self.token_sql(TokenType.AUTO_INCREMENT) 1050 1051 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1052 if isinstance(expression.this, list): 1053 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1054 else: 1055 this = self.sql(expression, "this") 1056 1057 return f"COMPRESS {this}" 1058 1059 def generatedasidentitycolumnconstraint_sql( 1060 self, expression: exp.GeneratedAsIdentityColumnConstraint 1061 ) -> str: 1062 this = "" 1063 if expression.this is not None: 1064 on_null = " ON NULL" if expression.args.get("on_null") else "" 1065 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1066 1067 start = expression.args.get("start") 1068 start = f"START WITH {start}" if start else "" 1069 increment = expression.args.get("increment") 1070 increment = f" INCREMENT BY {increment}" if increment else "" 1071 minvalue = expression.args.get("minvalue") 1072 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1073 maxvalue = expression.args.get("maxvalue") 1074 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1075 cycle = expression.args.get("cycle") 1076 cycle_sql = "" 1077 1078 if cycle is not None: 1079 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1080 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1081 1082 sequence_opts = "" 1083 if start or increment or cycle_sql: 1084 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1085 sequence_opts = f" ({sequence_opts.strip()})" 1086 1087 expr = self.sql(expression, "expression") 1088 expr = f"({expr})" if expr else "IDENTITY" 1089 1090 return f"GENERATED{this} AS {expr}{sequence_opts}" 1091 1092 def generatedasrowcolumnconstraint_sql( 1093 self, expression: exp.GeneratedAsRowColumnConstraint 1094 ) -> str: 1095 start = "START" if expression.args.get("start") else "END" 1096 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1097 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1098 1099 def periodforsystemtimeconstraint_sql( 1100 self, expression: exp.PeriodForSystemTimeConstraint 1101 ) -> str: 1102 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1103 1104 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1105 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1106 1107 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1108 desc = expression.args.get("desc") 1109 if desc is not None: 1110 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1111 options = self.expressions(expression, key="options", flat=True, sep=" ") 1112 options = f" {options}" if options else "" 1113 return f"PRIMARY KEY{options}" 1114 1115 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1116 this = self.sql(expression, "this") 1117 this = f" {this}" if this else "" 1118 index_type = expression.args.get("index_type") 1119 index_type = f" USING {index_type}" if index_type else "" 1120 on_conflict = self.sql(expression, "on_conflict") 1121 on_conflict = f" {on_conflict}" if on_conflict else "" 1122 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1123 options = self.expressions(expression, key="options", flat=True, sep=" ") 1124 options = f" {options}" if options else "" 1125 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1126 1127 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1128 return self.sql(expression, "this") 1129 1130 def create_sql(self, expression: exp.Create) -> str: 1131 kind = self.sql(expression, "kind") 1132 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1133 properties = expression.args.get("properties") 1134 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1135 1136 this = self.createable_sql(expression, properties_locs) 1137 1138 properties_sql = "" 1139 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1140 exp.Properties.Location.POST_WITH 1141 ): 1142 props_ast = exp.Properties( 1143 expressions=[ 1144 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1145 *properties_locs[exp.Properties.Location.POST_WITH], 1146 ] 1147 ) 1148 props_ast.parent = expression 1149 properties_sql = self.sql(props_ast) 1150 1151 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1152 properties_sql = self.sep() + properties_sql 1153 elif not self.pretty: 1154 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1155 properties_sql = f" {properties_sql}" 1156 1157 begin = " BEGIN" if expression.args.get("begin") else "" 1158 end = " END" if expression.args.get("end") else "" 1159 1160 expression_sql = self.sql(expression, "expression") 1161 if expression_sql: 1162 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1163 1164 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1165 postalias_props_sql = "" 1166 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1167 postalias_props_sql = self.properties( 1168 exp.Properties( 1169 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1170 ), 1171 wrapped=False, 1172 ) 1173 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1174 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1175 1176 postindex_props_sql = "" 1177 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1178 postindex_props_sql = self.properties( 1179 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1180 wrapped=False, 1181 prefix=" ", 1182 ) 1183 1184 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1185 indexes = f" {indexes}" if indexes else "" 1186 index_sql = indexes + postindex_props_sql 1187 1188 replace = " OR REPLACE" if expression.args.get("replace") else "" 1189 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1190 unique = " UNIQUE" if expression.args.get("unique") else "" 1191 1192 clustered = expression.args.get("clustered") 1193 if clustered is None: 1194 clustered_sql = "" 1195 elif clustered: 1196 clustered_sql = " CLUSTERED COLUMNSTORE" 1197 else: 1198 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1199 1200 postcreate_props_sql = "" 1201 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1202 postcreate_props_sql = self.properties( 1203 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1204 sep=" ", 1205 prefix=" ", 1206 wrapped=False, 1207 ) 1208 1209 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1210 1211 postexpression_props_sql = "" 1212 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1213 postexpression_props_sql = self.properties( 1214 exp.Properties( 1215 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1216 ), 1217 sep=" ", 1218 prefix=" ", 1219 wrapped=False, 1220 ) 1221 1222 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1223 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1224 no_schema_binding = ( 1225 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1226 ) 1227 1228 clone = self.sql(expression, "clone") 1229 clone = f" {clone}" if clone else "" 1230 1231 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1232 properties_expression = f"{expression_sql}{properties_sql}" 1233 else: 1234 properties_expression = f"{properties_sql}{expression_sql}" 1235 1236 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1237 return self.prepend_ctes(expression, expression_sql) 1238 1239 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1240 start = self.sql(expression, "start") 1241 start = f"START WITH {start}" if start else "" 1242 increment = self.sql(expression, "increment") 1243 increment = f" INCREMENT BY {increment}" if increment else "" 1244 minvalue = self.sql(expression, "minvalue") 1245 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1246 maxvalue = self.sql(expression, "maxvalue") 1247 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1248 owned = self.sql(expression, "owned") 1249 owned = f" OWNED BY {owned}" if owned else "" 1250 1251 cache = expression.args.get("cache") 1252 if cache is None: 1253 cache_str = "" 1254 elif cache is True: 1255 cache_str = " CACHE" 1256 else: 1257 cache_str = f" CACHE {cache}" 1258 1259 options = self.expressions(expression, key="options", flat=True, sep=" ") 1260 options = f" {options}" if options else "" 1261 1262 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1263 1264 def clone_sql(self, expression: exp.Clone) -> str: 1265 this = self.sql(expression, "this") 1266 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1267 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1268 return f"{shallow}{keyword} {this}" 1269 1270 def describe_sql(self, expression: exp.Describe) -> str: 1271 style = expression.args.get("style") 1272 style = f" {style}" if style else "" 1273 partition = self.sql(expression, "partition") 1274 partition = f" {partition}" if partition else "" 1275 format = self.sql(expression, "format") 1276 format = f" {format}" if format else "" 1277 1278 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1279 1280 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1281 tag = self.sql(expression, "tag") 1282 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1283 1284 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1285 with_ = self.sql(expression, "with") 1286 if with_: 1287 sql = f"{with_}{self.sep()}{sql}" 1288 return sql 1289 1290 def with_sql(self, expression: exp.With) -> str: 1291 sql = self.expressions(expression, flat=True) 1292 recursive = ( 1293 "RECURSIVE " 1294 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1295 else "" 1296 ) 1297 search = self.sql(expression, "search") 1298 search = f" {search}" if search else "" 1299 1300 return f"WITH {recursive}{sql}{search}" 1301 1302 def cte_sql(self, expression: exp.CTE) -> str: 1303 alias = expression.args.get("alias") 1304 if alias: 1305 alias.add_comments(expression.pop_comments()) 1306 1307 alias_sql = self.sql(expression, "alias") 1308 1309 materialized = expression.args.get("materialized") 1310 if materialized is False: 1311 materialized = "NOT MATERIALIZED " 1312 elif materialized: 1313 materialized = "MATERIALIZED " 1314 1315 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1316 1317 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1318 alias = self.sql(expression, "this") 1319 columns = self.expressions(expression, key="columns", flat=True) 1320 columns = f"({columns})" if columns else "" 1321 1322 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1323 columns = "" 1324 self.unsupported("Named columns are not supported in table alias.") 1325 1326 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1327 alias = self._next_name() 1328 1329 return f"{alias}{columns}" 1330 1331 def bitstring_sql(self, expression: exp.BitString) -> str: 1332 this = self.sql(expression, "this") 1333 if self.dialect.BIT_START: 1334 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1335 return f"{int(this, 2)}" 1336 1337 def hexstring_sql( 1338 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1339 ) -> str: 1340 this = self.sql(expression, "this") 1341 is_integer_type = expression.args.get("is_integer") 1342 1343 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1344 not self.dialect.HEX_START and not binary_function_repr 1345 ): 1346 # Integer representation will be returned if: 1347 # - The read dialect treats the hex value as integer literal but not the write 1348 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1349 return f"{int(this, 16)}" 1350 1351 if not is_integer_type: 1352 # Read dialect treats the hex value as BINARY/BLOB 1353 if binary_function_repr: 1354 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1355 return self.func(binary_function_repr, exp.Literal.string(this)) 1356 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1357 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1358 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1359 1360 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1361 1362 def bytestring_sql(self, expression: exp.ByteString) -> str: 1363 this = self.sql(expression, "this") 1364 if self.dialect.BYTE_START: 1365 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1366 return this 1367 1368 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1369 this = self.sql(expression, "this") 1370 escape = expression.args.get("escape") 1371 1372 if self.dialect.UNICODE_START: 1373 escape_substitute = r"\\\1" 1374 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1375 else: 1376 escape_substitute = r"\\u\1" 1377 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1378 1379 if escape: 1380 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1381 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1382 else: 1383 escape_pattern = ESCAPED_UNICODE_RE 1384 escape_sql = "" 1385 1386 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1387 this = escape_pattern.sub(escape_substitute, this) 1388 1389 return f"{left_quote}{this}{right_quote}{escape_sql}" 1390 1391 def rawstring_sql(self, expression: exp.RawString) -> str: 1392 string = expression.this 1393 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1394 string = string.replace("\\", "\\\\") 1395 1396 string = self.escape_str(string, escape_backslash=False) 1397 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1398 1399 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1400 this = self.sql(expression, "this") 1401 specifier = self.sql(expression, "expression") 1402 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1403 return f"{this}{specifier}" 1404 1405 def datatype_sql(self, expression: exp.DataType) -> str: 1406 nested = "" 1407 values = "" 1408 interior = self.expressions(expression, flat=True) 1409 1410 type_value = expression.this 1411 if type_value in self.UNSUPPORTED_TYPES: 1412 self.unsupported( 1413 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1414 ) 1415 1416 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1417 type_sql = self.sql(expression, "kind") 1418 else: 1419 type_sql = ( 1420 self.TYPE_MAPPING.get(type_value, type_value.value) 1421 if isinstance(type_value, exp.DataType.Type) 1422 else type_value 1423 ) 1424 1425 if interior: 1426 if expression.args.get("nested"): 1427 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1428 if expression.args.get("values") is not None: 1429 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1430 values = self.expressions(expression, key="values", flat=True) 1431 values = f"{delimiters[0]}{values}{delimiters[1]}" 1432 elif type_value == exp.DataType.Type.INTERVAL: 1433 nested = f" {interior}" 1434 else: 1435 nested = f"({interior})" 1436 1437 type_sql = f"{type_sql}{nested}{values}" 1438 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1439 exp.DataType.Type.TIMETZ, 1440 exp.DataType.Type.TIMESTAMPTZ, 1441 ): 1442 type_sql = f"{type_sql} WITH TIME ZONE" 1443 1444 return type_sql 1445 1446 def directory_sql(self, expression: exp.Directory) -> str: 1447 local = "LOCAL " if expression.args.get("local") else "" 1448 row_format = self.sql(expression, "row_format") 1449 row_format = f" {row_format}" if row_format else "" 1450 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1451 1452 def delete_sql(self, expression: exp.Delete) -> str: 1453 this = self.sql(expression, "this") 1454 this = f" FROM {this}" if this else "" 1455 using = self.sql(expression, "using") 1456 using = f" USING {using}" if using else "" 1457 cluster = self.sql(expression, "cluster") 1458 cluster = f" {cluster}" if cluster else "" 1459 where = self.sql(expression, "where") 1460 returning = self.sql(expression, "returning") 1461 limit = self.sql(expression, "limit") 1462 tables = self.expressions(expression, key="tables") 1463 tables = f" {tables}" if tables else "" 1464 if self.RETURNING_END: 1465 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1466 else: 1467 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1468 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1469 1470 def drop_sql(self, expression: exp.Drop) -> str: 1471 this = self.sql(expression, "this") 1472 expressions = self.expressions(expression, flat=True) 1473 expressions = f" ({expressions})" if expressions else "" 1474 kind = expression.args["kind"] 1475 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1476 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1477 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1478 on_cluster = self.sql(expression, "cluster") 1479 on_cluster = f" {on_cluster}" if on_cluster else "" 1480 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1481 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1482 cascade = " CASCADE" if expression.args.get("cascade") else "" 1483 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1484 purge = " PURGE" if expression.args.get("purge") else "" 1485 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1486 1487 def set_operation(self, expression: exp.SetOperation) -> str: 1488 op_type = type(expression) 1489 op_name = op_type.key.upper() 1490 1491 distinct = expression.args.get("distinct") 1492 if ( 1493 distinct is False 1494 and op_type in (exp.Except, exp.Intersect) 1495 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1496 ): 1497 self.unsupported(f"{op_name} ALL is not supported") 1498 1499 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1500 1501 if distinct is None: 1502 distinct = default_distinct 1503 if distinct is None: 1504 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1505 1506 if distinct is default_distinct: 1507 distinct_or_all = "" 1508 else: 1509 distinct_or_all = " DISTINCT" if distinct else " ALL" 1510 1511 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1512 side_kind = f"{side_kind} " if side_kind else "" 1513 1514 by_name = " BY NAME" if expression.args.get("by_name") else "" 1515 on = self.expressions(expression, key="on", flat=True) 1516 on = f" ON ({on})" if on else "" 1517 1518 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1519 1520 def set_operations(self, expression: exp.SetOperation) -> str: 1521 if not self.SET_OP_MODIFIERS: 1522 limit = expression.args.get("limit") 1523 order = expression.args.get("order") 1524 1525 if limit or order: 1526 select = self._move_ctes_to_top_level( 1527 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1528 ) 1529 1530 if limit: 1531 select = select.limit(limit.pop(), copy=False) 1532 if order: 1533 select = select.order_by(order.pop(), copy=False) 1534 return self.sql(select) 1535 1536 sqls: t.List[str] = [] 1537 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1538 1539 while stack: 1540 node = stack.pop() 1541 1542 if isinstance(node, exp.SetOperation): 1543 stack.append(node.expression) 1544 stack.append( 1545 self.maybe_comment( 1546 self.set_operation(node), comments=node.comments, separated=True 1547 ) 1548 ) 1549 stack.append(node.this) 1550 else: 1551 sqls.append(self.sql(node)) 1552 1553 this = self.sep().join(sqls) 1554 this = self.query_modifiers(expression, this) 1555 return self.prepend_ctes(expression, this) 1556 1557 def fetch_sql(self, expression: exp.Fetch) -> str: 1558 direction = expression.args.get("direction") 1559 direction = f" {direction}" if direction else "" 1560 count = self.sql(expression, "count") 1561 count = f" {count}" if count else "" 1562 limit_options = self.sql(expression, "limit_options") 1563 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1564 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1565 1566 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1567 percent = " PERCENT" if expression.args.get("percent") else "" 1568 rows = " ROWS" if expression.args.get("rows") else "" 1569 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1570 if not with_ties and rows: 1571 with_ties = " ONLY" 1572 return f"{percent}{rows}{with_ties}" 1573 1574 def filter_sql(self, expression: exp.Filter) -> str: 1575 if self.AGGREGATE_FILTER_SUPPORTED: 1576 this = self.sql(expression, "this") 1577 where = self.sql(expression, "expression").strip() 1578 return f"{this} FILTER({where})" 1579 1580 agg = expression.this 1581 agg_arg = agg.this 1582 cond = expression.expression.this 1583 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1584 return self.sql(agg) 1585 1586 def hint_sql(self, expression: exp.Hint) -> str: 1587 if not self.QUERY_HINTS: 1588 self.unsupported("Hints are not supported") 1589 return "" 1590 1591 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1592 1593 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1594 using = self.sql(expression, "using") 1595 using = f" USING {using}" if using else "" 1596 columns = self.expressions(expression, key="columns", flat=True) 1597 columns = f"({columns})" if columns else "" 1598 partition_by = self.expressions(expression, key="partition_by", flat=True) 1599 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1600 where = self.sql(expression, "where") 1601 include = self.expressions(expression, key="include", flat=True) 1602 if include: 1603 include = f" INCLUDE ({include})" 1604 with_storage = self.expressions(expression, key="with_storage", flat=True) 1605 with_storage = f" WITH ({with_storage})" if with_storage else "" 1606 tablespace = self.sql(expression, "tablespace") 1607 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1608 on = self.sql(expression, "on") 1609 on = f" ON {on}" if on else "" 1610 1611 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1612 1613 def index_sql(self, expression: exp.Index) -> str: 1614 unique = "UNIQUE " if expression.args.get("unique") else "" 1615 primary = "PRIMARY " if expression.args.get("primary") else "" 1616 amp = "AMP " if expression.args.get("amp") else "" 1617 name = self.sql(expression, "this") 1618 name = f"{name} " if name else "" 1619 table = self.sql(expression, "table") 1620 table = f"{self.INDEX_ON} {table}" if table else "" 1621 1622 index = "INDEX " if not table else "" 1623 1624 params = self.sql(expression, "params") 1625 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1626 1627 def identifier_sql(self, expression: exp.Identifier) -> str: 1628 text = expression.name 1629 lower = text.lower() 1630 text = lower if self.normalize and not expression.quoted else text 1631 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1632 if ( 1633 expression.quoted 1634 or self.dialect.can_identify(text, self.identify) 1635 or lower in self.RESERVED_KEYWORDS 1636 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1637 ): 1638 text = f"{self._identifier_start}{text}{self._identifier_end}" 1639 return text 1640 1641 def hex_sql(self, expression: exp.Hex) -> str: 1642 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1643 if self.dialect.HEX_LOWERCASE: 1644 text = self.func("LOWER", text) 1645 1646 return text 1647 1648 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1649 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1650 if not self.dialect.HEX_LOWERCASE: 1651 text = self.func("LOWER", text) 1652 return text 1653 1654 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1655 input_format = self.sql(expression, "input_format") 1656 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1657 output_format = self.sql(expression, "output_format") 1658 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1659 return self.sep().join((input_format, output_format)) 1660 1661 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1662 string = self.sql(exp.Literal.string(expression.name)) 1663 return f"{prefix}{string}" 1664 1665 def partition_sql(self, expression: exp.Partition) -> str: 1666 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1667 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1668 1669 def properties_sql(self, expression: exp.Properties) -> str: 1670 root_properties = [] 1671 with_properties = [] 1672 1673 for p in expression.expressions: 1674 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1675 if p_loc == exp.Properties.Location.POST_WITH: 1676 with_properties.append(p) 1677 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1678 root_properties.append(p) 1679 1680 root_props_ast = exp.Properties(expressions=root_properties) 1681 root_props_ast.parent = expression.parent 1682 1683 with_props_ast = exp.Properties(expressions=with_properties) 1684 with_props_ast.parent = expression.parent 1685 1686 root_props = self.root_properties(root_props_ast) 1687 with_props = self.with_properties(with_props_ast) 1688 1689 if root_props and with_props and not self.pretty: 1690 with_props = " " + with_props 1691 1692 return root_props + with_props 1693 1694 def root_properties(self, properties: exp.Properties) -> str: 1695 if properties.expressions: 1696 return self.expressions(properties, indent=False, sep=" ") 1697 return "" 1698 1699 def properties( 1700 self, 1701 properties: exp.Properties, 1702 prefix: str = "", 1703 sep: str = ", ", 1704 suffix: str = "", 1705 wrapped: bool = True, 1706 ) -> str: 1707 if properties.expressions: 1708 expressions = self.expressions(properties, sep=sep, indent=False) 1709 if expressions: 1710 expressions = self.wrap(expressions) if wrapped else expressions 1711 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1712 return "" 1713 1714 def with_properties(self, properties: exp.Properties) -> str: 1715 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1716 1717 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1718 properties_locs = defaultdict(list) 1719 for p in properties.expressions: 1720 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1721 if p_loc != exp.Properties.Location.UNSUPPORTED: 1722 properties_locs[p_loc].append(p) 1723 else: 1724 self.unsupported(f"Unsupported property {p.key}") 1725 1726 return properties_locs 1727 1728 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1729 if isinstance(expression.this, exp.Dot): 1730 return self.sql(expression, "this") 1731 return f"'{expression.name}'" if string_key else expression.name 1732 1733 def property_sql(self, expression: exp.Property) -> str: 1734 property_cls = expression.__class__ 1735 if property_cls == exp.Property: 1736 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1737 1738 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1739 if not property_name: 1740 self.unsupported(f"Unsupported property {expression.key}") 1741 1742 return f"{property_name}={self.sql(expression, 'this')}" 1743 1744 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1745 if self.SUPPORTS_CREATE_TABLE_LIKE: 1746 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1747 options = f" {options}" if options else "" 1748 1749 like = f"LIKE {self.sql(expression, 'this')}{options}" 1750 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1751 like = f"({like})" 1752 1753 return like 1754 1755 if expression.expressions: 1756 self.unsupported("Transpilation of LIKE property options is unsupported") 1757 1758 select = exp.select("*").from_(expression.this).limit(0) 1759 return f"AS {self.sql(select)}" 1760 1761 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1762 no = "NO " if expression.args.get("no") else "" 1763 protection = " PROTECTION" if expression.args.get("protection") else "" 1764 return f"{no}FALLBACK{protection}" 1765 1766 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1767 no = "NO " if expression.args.get("no") else "" 1768 local = expression.args.get("local") 1769 local = f"{local} " if local else "" 1770 dual = "DUAL " if expression.args.get("dual") else "" 1771 before = "BEFORE " if expression.args.get("before") else "" 1772 after = "AFTER " if expression.args.get("after") else "" 1773 return f"{no}{local}{dual}{before}{after}JOURNAL" 1774 1775 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1776 freespace = self.sql(expression, "this") 1777 percent = " PERCENT" if expression.args.get("percent") else "" 1778 return f"FREESPACE={freespace}{percent}" 1779 1780 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1781 if expression.args.get("default"): 1782 property = "DEFAULT" 1783 elif expression.args.get("on"): 1784 property = "ON" 1785 else: 1786 property = "OFF" 1787 return f"CHECKSUM={property}" 1788 1789 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1790 if expression.args.get("no"): 1791 return "NO MERGEBLOCKRATIO" 1792 if expression.args.get("default"): 1793 return "DEFAULT MERGEBLOCKRATIO" 1794 1795 percent = " PERCENT" if expression.args.get("percent") else "" 1796 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1797 1798 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1799 default = expression.args.get("default") 1800 minimum = expression.args.get("minimum") 1801 maximum = expression.args.get("maximum") 1802 if default or minimum or maximum: 1803 if default: 1804 prop = "DEFAULT" 1805 elif minimum: 1806 prop = "MINIMUM" 1807 else: 1808 prop = "MAXIMUM" 1809 return f"{prop} DATABLOCKSIZE" 1810 units = expression.args.get("units") 1811 units = f" {units}" if units else "" 1812 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1813 1814 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1815 autotemp = expression.args.get("autotemp") 1816 always = expression.args.get("always") 1817 default = expression.args.get("default") 1818 manual = expression.args.get("manual") 1819 never = expression.args.get("never") 1820 1821 if autotemp is not None: 1822 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1823 elif always: 1824 prop = "ALWAYS" 1825 elif default: 1826 prop = "DEFAULT" 1827 elif manual: 1828 prop = "MANUAL" 1829 elif never: 1830 prop = "NEVER" 1831 return f"BLOCKCOMPRESSION={prop}" 1832 1833 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1834 no = expression.args.get("no") 1835 no = " NO" if no else "" 1836 concurrent = expression.args.get("concurrent") 1837 concurrent = " CONCURRENT" if concurrent else "" 1838 target = self.sql(expression, "target") 1839 target = f" {target}" if target else "" 1840 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1841 1842 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1843 if isinstance(expression.this, list): 1844 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1845 if expression.this: 1846 modulus = self.sql(expression, "this") 1847 remainder = self.sql(expression, "expression") 1848 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1849 1850 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1851 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1852 return f"FROM ({from_expressions}) TO ({to_expressions})" 1853 1854 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1855 this = self.sql(expression, "this") 1856 1857 for_values_or_default = expression.expression 1858 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1859 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1860 else: 1861 for_values_or_default = " DEFAULT" 1862 1863 return f"PARTITION OF {this}{for_values_or_default}" 1864 1865 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1866 kind = expression.args.get("kind") 1867 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1868 for_or_in = expression.args.get("for_or_in") 1869 for_or_in = f" {for_or_in}" if for_or_in else "" 1870 lock_type = expression.args.get("lock_type") 1871 override = " OVERRIDE" if expression.args.get("override") else "" 1872 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1873 1874 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1875 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1876 statistics = expression.args.get("statistics") 1877 statistics_sql = "" 1878 if statistics is not None: 1879 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1880 return f"{data_sql}{statistics_sql}" 1881 1882 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1883 this = self.sql(expression, "this") 1884 this = f"HISTORY_TABLE={this}" if this else "" 1885 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1886 data_consistency = ( 1887 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1888 ) 1889 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1890 retention_period = ( 1891 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1892 ) 1893 1894 if this: 1895 on_sql = self.func("ON", this, data_consistency, retention_period) 1896 else: 1897 on_sql = "ON" if expression.args.get("on") else "OFF" 1898 1899 sql = f"SYSTEM_VERSIONING={on_sql}" 1900 1901 return f"WITH({sql})" if expression.args.get("with") else sql 1902 1903 def insert_sql(self, expression: exp.Insert) -> str: 1904 hint = self.sql(expression, "hint") 1905 overwrite = expression.args.get("overwrite") 1906 1907 if isinstance(expression.this, exp.Directory): 1908 this = " OVERWRITE" if overwrite else " INTO" 1909 else: 1910 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1911 1912 stored = self.sql(expression, "stored") 1913 stored = f" {stored}" if stored else "" 1914 alternative = expression.args.get("alternative") 1915 alternative = f" OR {alternative}" if alternative else "" 1916 ignore = " IGNORE" if expression.args.get("ignore") else "" 1917 is_function = expression.args.get("is_function") 1918 if is_function: 1919 this = f"{this} FUNCTION" 1920 this = f"{this} {self.sql(expression, 'this')}" 1921 1922 exists = " IF EXISTS" if expression.args.get("exists") else "" 1923 where = self.sql(expression, "where") 1924 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1925 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1926 on_conflict = self.sql(expression, "conflict") 1927 on_conflict = f" {on_conflict}" if on_conflict else "" 1928 by_name = " BY NAME" if expression.args.get("by_name") else "" 1929 returning = self.sql(expression, "returning") 1930 1931 if self.RETURNING_END: 1932 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1933 else: 1934 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1935 1936 partition_by = self.sql(expression, "partition") 1937 partition_by = f" {partition_by}" if partition_by else "" 1938 settings = self.sql(expression, "settings") 1939 settings = f" {settings}" if settings else "" 1940 1941 source = self.sql(expression, "source") 1942 source = f"TABLE {source}" if source else "" 1943 1944 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1945 return self.prepend_ctes(expression, sql) 1946 1947 def introducer_sql(self, expression: exp.Introducer) -> str: 1948 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1949 1950 def kill_sql(self, expression: exp.Kill) -> str: 1951 kind = self.sql(expression, "kind") 1952 kind = f" {kind}" if kind else "" 1953 this = self.sql(expression, "this") 1954 this = f" {this}" if this else "" 1955 return f"KILL{kind}{this}" 1956 1957 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1958 return expression.name 1959 1960 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1961 return expression.name 1962 1963 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1964 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1965 1966 constraint = self.sql(expression, "constraint") 1967 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1968 1969 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1970 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1971 action = self.sql(expression, "action") 1972 1973 expressions = self.expressions(expression, flat=True) 1974 if expressions: 1975 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1976 expressions = f" {set_keyword}{expressions}" 1977 1978 where = self.sql(expression, "where") 1979 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1980 1981 def returning_sql(self, expression: exp.Returning) -> str: 1982 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1983 1984 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1985 fields = self.sql(expression, "fields") 1986 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1987 escaped = self.sql(expression, "escaped") 1988 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1989 items = self.sql(expression, "collection_items") 1990 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1991 keys = self.sql(expression, "map_keys") 1992 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1993 lines = self.sql(expression, "lines") 1994 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1995 null = self.sql(expression, "null") 1996 null = f" NULL DEFINED AS {null}" if null else "" 1997 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1998 1999 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2000 return f"WITH ({self.expressions(expression, flat=True)})" 2001 2002 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2003 this = f"{self.sql(expression, 'this')} INDEX" 2004 target = self.sql(expression, "target") 2005 target = f" FOR {target}" if target else "" 2006 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2007 2008 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2009 this = self.sql(expression, "this") 2010 kind = self.sql(expression, "kind") 2011 expr = self.sql(expression, "expression") 2012 return f"{this} ({kind} => {expr})" 2013 2014 def table_parts(self, expression: exp.Table) -> str: 2015 return ".".join( 2016 self.sql(part) 2017 for part in ( 2018 expression.args.get("catalog"), 2019 expression.args.get("db"), 2020 expression.args.get("this"), 2021 ) 2022 if part is not None 2023 ) 2024 2025 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2026 table = self.table_parts(expression) 2027 only = "ONLY " if expression.args.get("only") else "" 2028 partition = self.sql(expression, "partition") 2029 partition = f" {partition}" if partition else "" 2030 version = self.sql(expression, "version") 2031 version = f" {version}" if version else "" 2032 alias = self.sql(expression, "alias") 2033 alias = f"{sep}{alias}" if alias else "" 2034 2035 sample = self.sql(expression, "sample") 2036 if self.dialect.ALIAS_POST_TABLESAMPLE: 2037 sample_pre_alias = sample 2038 sample_post_alias = "" 2039 else: 2040 sample_pre_alias = "" 2041 sample_post_alias = sample 2042 2043 hints = self.expressions(expression, key="hints", sep=" ") 2044 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2045 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2046 joins = self.indent( 2047 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2048 ) 2049 laterals = self.expressions(expression, key="laterals", sep="") 2050 2051 file_format = self.sql(expression, "format") 2052 if file_format: 2053 pattern = self.sql(expression, "pattern") 2054 pattern = f", PATTERN => {pattern}" if pattern else "" 2055 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2056 2057 ordinality = expression.args.get("ordinality") or "" 2058 if ordinality: 2059 ordinality = f" WITH ORDINALITY{alias}" 2060 alias = "" 2061 2062 when = self.sql(expression, "when") 2063 if when: 2064 table = f"{table} {when}" 2065 2066 changes = self.sql(expression, "changes") 2067 changes = f" {changes}" if changes else "" 2068 2069 rows_from = self.expressions(expression, key="rows_from") 2070 if rows_from: 2071 table = f"ROWS FROM {self.wrap(rows_from)}" 2072 2073 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2074 2075 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2076 table = self.func("TABLE", expression.this) 2077 alias = self.sql(expression, "alias") 2078 alias = f" AS {alias}" if alias else "" 2079 sample = self.sql(expression, "sample") 2080 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2081 joins = self.indent( 2082 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2083 ) 2084 return f"{table}{alias}{pivots}{sample}{joins}" 2085 2086 def tablesample_sql( 2087 self, 2088 expression: exp.TableSample, 2089 tablesample_keyword: t.Optional[str] = None, 2090 ) -> str: 2091 method = self.sql(expression, "method") 2092 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2093 numerator = self.sql(expression, "bucket_numerator") 2094 denominator = self.sql(expression, "bucket_denominator") 2095 field = self.sql(expression, "bucket_field") 2096 field = f" ON {field}" if field else "" 2097 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2098 seed = self.sql(expression, "seed") 2099 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2100 2101 size = self.sql(expression, "size") 2102 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2103 size = f"{size} ROWS" 2104 2105 percent = self.sql(expression, "percent") 2106 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2107 percent = f"{percent} PERCENT" 2108 2109 expr = f"{bucket}{percent}{size}" 2110 if self.TABLESAMPLE_REQUIRES_PARENS: 2111 expr = f"({expr})" 2112 2113 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2114 2115 def pivot_sql(self, expression: exp.Pivot) -> str: 2116 expressions = self.expressions(expression, flat=True) 2117 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2118 2119 group = self.sql(expression, "group") 2120 2121 if expression.this: 2122 this = self.sql(expression, "this") 2123 if not expressions: 2124 return f"UNPIVOT {this}" 2125 2126 on = f"{self.seg('ON')} {expressions}" 2127 into = self.sql(expression, "into") 2128 into = f"{self.seg('INTO')} {into}" if into else "" 2129 using = self.expressions(expression, key="using", flat=True) 2130 using = f"{self.seg('USING')} {using}" if using else "" 2131 return f"{direction} {this}{on}{into}{using}{group}" 2132 2133 alias = self.sql(expression, "alias") 2134 alias = f" AS {alias}" if alias else "" 2135 2136 fields = self.expressions( 2137 expression, 2138 "fields", 2139 sep=" ", 2140 dynamic=True, 2141 new_line=True, 2142 skip_first=True, 2143 skip_last=True, 2144 ) 2145 2146 include_nulls = expression.args.get("include_nulls") 2147 if include_nulls is not None: 2148 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2149 else: 2150 nulls = "" 2151 2152 default_on_null = self.sql(expression, "default_on_null") 2153 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2154 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2155 2156 def version_sql(self, expression: exp.Version) -> str: 2157 this = f"FOR {expression.name}" 2158 kind = expression.text("kind") 2159 expr = self.sql(expression, "expression") 2160 return f"{this} {kind} {expr}" 2161 2162 def tuple_sql(self, expression: exp.Tuple) -> str: 2163 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2164 2165 def update_sql(self, expression: exp.Update) -> str: 2166 this = self.sql(expression, "this") 2167 set_sql = self.expressions(expression, flat=True) 2168 from_sql = self.sql(expression, "from") 2169 where_sql = self.sql(expression, "where") 2170 returning = self.sql(expression, "returning") 2171 order = self.sql(expression, "order") 2172 limit = self.sql(expression, "limit") 2173 if self.RETURNING_END: 2174 expression_sql = f"{from_sql}{where_sql}{returning}" 2175 else: 2176 expression_sql = f"{returning}{from_sql}{where_sql}" 2177 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2178 return self.prepend_ctes(expression, sql) 2179 2180 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2181 values_as_table = values_as_table and self.VALUES_AS_TABLE 2182 2183 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2184 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2185 args = self.expressions(expression) 2186 alias = self.sql(expression, "alias") 2187 values = f"VALUES{self.seg('')}{args}" 2188 values = ( 2189 f"({values})" 2190 if self.WRAP_DERIVED_VALUES 2191 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2192 else values 2193 ) 2194 return f"{values} AS {alias}" if alias else values 2195 2196 # Converts `VALUES...` expression into a series of select unions. 2197 alias_node = expression.args.get("alias") 2198 column_names = alias_node and alias_node.columns 2199 2200 selects: t.List[exp.Query] = [] 2201 2202 for i, tup in enumerate(expression.expressions): 2203 row = tup.expressions 2204 2205 if i == 0 and column_names: 2206 row = [ 2207 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2208 ] 2209 2210 selects.append(exp.Select(expressions=row)) 2211 2212 if self.pretty: 2213 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2214 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2215 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2216 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2217 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2218 2219 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2220 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2221 return f"({unions}){alias}" 2222 2223 def var_sql(self, expression: exp.Var) -> str: 2224 return self.sql(expression, "this") 2225 2226 @unsupported_args("expressions") 2227 def into_sql(self, expression: exp.Into) -> str: 2228 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2229 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2230 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2231 2232 def from_sql(self, expression: exp.From) -> str: 2233 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2234 2235 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2236 grouping_sets = self.expressions(expression, indent=False) 2237 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2238 2239 def rollup_sql(self, expression: exp.Rollup) -> str: 2240 expressions = self.expressions(expression, indent=False) 2241 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2242 2243 def cube_sql(self, expression: exp.Cube) -> str: 2244 expressions = self.expressions(expression, indent=False) 2245 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2246 2247 def group_sql(self, expression: exp.Group) -> str: 2248 group_by_all = expression.args.get("all") 2249 if group_by_all is True: 2250 modifier = " ALL" 2251 elif group_by_all is False: 2252 modifier = " DISTINCT" 2253 else: 2254 modifier = "" 2255 2256 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2257 2258 grouping_sets = self.expressions(expression, key="grouping_sets") 2259 cube = self.expressions(expression, key="cube") 2260 rollup = self.expressions(expression, key="rollup") 2261 2262 groupings = csv( 2263 self.seg(grouping_sets) if grouping_sets else "", 2264 self.seg(cube) if cube else "", 2265 self.seg(rollup) if rollup else "", 2266 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2267 sep=self.GROUPINGS_SEP, 2268 ) 2269 2270 if ( 2271 expression.expressions 2272 and groupings 2273 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2274 ): 2275 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2276 2277 return f"{group_by}{groupings}" 2278 2279 def having_sql(self, expression: exp.Having) -> str: 2280 this = self.indent(self.sql(expression, "this")) 2281 return f"{self.seg('HAVING')}{self.sep()}{this}" 2282 2283 def connect_sql(self, expression: exp.Connect) -> str: 2284 start = self.sql(expression, "start") 2285 start = self.seg(f"START WITH {start}") if start else "" 2286 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2287 connect = self.sql(expression, "connect") 2288 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2289 return start + connect 2290 2291 def prior_sql(self, expression: exp.Prior) -> str: 2292 return f"PRIOR {self.sql(expression, 'this')}" 2293 2294 def join_sql(self, expression: exp.Join) -> str: 2295 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2296 side = None 2297 else: 2298 side = expression.side 2299 2300 op_sql = " ".join( 2301 op 2302 for op in ( 2303 expression.method, 2304 "GLOBAL" if expression.args.get("global") else None, 2305 side, 2306 expression.kind, 2307 expression.hint if self.JOIN_HINTS else None, 2308 ) 2309 if op 2310 ) 2311 match_cond = self.sql(expression, "match_condition") 2312 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2313 on_sql = self.sql(expression, "on") 2314 using = expression.args.get("using") 2315 2316 if not on_sql and using: 2317 on_sql = csv(*(self.sql(column) for column in using)) 2318 2319 this = expression.this 2320 this_sql = self.sql(this) 2321 2322 exprs = self.expressions(expression) 2323 if exprs: 2324 this_sql = f"{this_sql},{self.seg(exprs)}" 2325 2326 if on_sql: 2327 on_sql = self.indent(on_sql, skip_first=True) 2328 space = self.seg(" " * self.pad) if self.pretty else " " 2329 if using: 2330 on_sql = f"{space}USING ({on_sql})" 2331 else: 2332 on_sql = f"{space}ON {on_sql}" 2333 elif not op_sql: 2334 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2335 return f" {this_sql}" 2336 2337 return f", {this_sql}" 2338 2339 if op_sql != "STRAIGHT_JOIN": 2340 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2341 2342 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2343 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2344 2345 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2346 args = self.expressions(expression, flat=True) 2347 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2348 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2349 2350 def lateral_op(self, expression: exp.Lateral) -> str: 2351 cross_apply = expression.args.get("cross_apply") 2352 2353 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2354 if cross_apply is True: 2355 op = "INNER JOIN " 2356 elif cross_apply is False: 2357 op = "LEFT JOIN " 2358 else: 2359 op = "" 2360 2361 return f"{op}LATERAL" 2362 2363 def lateral_sql(self, expression: exp.Lateral) -> str: 2364 this = self.sql(expression, "this") 2365 2366 if expression.args.get("view"): 2367 alias = expression.args["alias"] 2368 columns = self.expressions(alias, key="columns", flat=True) 2369 table = f" {alias.name}" if alias.name else "" 2370 columns = f" AS {columns}" if columns else "" 2371 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2372 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2373 2374 alias = self.sql(expression, "alias") 2375 alias = f" AS {alias}" if alias else "" 2376 2377 ordinality = expression.args.get("ordinality") or "" 2378 if ordinality: 2379 ordinality = f" WITH ORDINALITY{alias}" 2380 alias = "" 2381 2382 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2383 2384 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2385 this = self.sql(expression, "this") 2386 2387 args = [ 2388 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2389 for e in (expression.args.get(k) for k in ("offset", "expression")) 2390 if e 2391 ] 2392 2393 args_sql = ", ".join(self.sql(e) for e in args) 2394 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2395 expressions = self.expressions(expression, flat=True) 2396 limit_options = self.sql(expression, "limit_options") 2397 expressions = f" BY {expressions}" if expressions else "" 2398 2399 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2400 2401 def offset_sql(self, expression: exp.Offset) -> str: 2402 this = self.sql(expression, "this") 2403 value = expression.expression 2404 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2405 expressions = self.expressions(expression, flat=True) 2406 expressions = f" BY {expressions}" if expressions else "" 2407 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2408 2409 def setitem_sql(self, expression: exp.SetItem) -> str: 2410 kind = self.sql(expression, "kind") 2411 kind = f"{kind} " if kind else "" 2412 this = self.sql(expression, "this") 2413 expressions = self.expressions(expression) 2414 collate = self.sql(expression, "collate") 2415 collate = f" COLLATE {collate}" if collate else "" 2416 global_ = "GLOBAL " if expression.args.get("global") else "" 2417 return f"{global_}{kind}{this}{expressions}{collate}" 2418 2419 def set_sql(self, expression: exp.Set) -> str: 2420 expressions = f" {self.expressions(expression, flat=True)}" 2421 tag = " TAG" if expression.args.get("tag") else "" 2422 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2423 2424 def queryband_sql(self, expression: exp.QueryBand) -> str: 2425 this = self.sql(expression, "this") 2426 update = " UPDATE" if expression.args.get("update") else "" 2427 scope = self.sql(expression, "scope") 2428 scope = f" FOR {scope}" if scope else "" 2429 2430 return f"QUERY_BAND = {this}{update}{scope}" 2431 2432 def pragma_sql(self, expression: exp.Pragma) -> str: 2433 return f"PRAGMA {self.sql(expression, 'this')}" 2434 2435 def lock_sql(self, expression: exp.Lock) -> str: 2436 if not self.LOCKING_READS_SUPPORTED: 2437 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2438 return "" 2439 2440 update = expression.args["update"] 2441 key = expression.args.get("key") 2442 if update: 2443 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2444 else: 2445 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2446 expressions = self.expressions(expression, flat=True) 2447 expressions = f" OF {expressions}" if expressions else "" 2448 wait = expression.args.get("wait") 2449 2450 if wait is not None: 2451 if isinstance(wait, exp.Literal): 2452 wait = f" WAIT {self.sql(wait)}" 2453 else: 2454 wait = " NOWAIT" if wait else " SKIP LOCKED" 2455 2456 return f"{lock_type}{expressions}{wait or ''}" 2457 2458 def literal_sql(self, expression: exp.Literal) -> str: 2459 text = expression.this or "" 2460 if expression.is_string: 2461 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2462 return text 2463 2464 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2465 if self.dialect.ESCAPED_SEQUENCES: 2466 to_escaped = self.dialect.ESCAPED_SEQUENCES 2467 text = "".join( 2468 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2469 ) 2470 2471 return self._replace_line_breaks(text).replace( 2472 self.dialect.QUOTE_END, self._escaped_quote_end 2473 ) 2474 2475 def loaddata_sql(self, expression: exp.LoadData) -> str: 2476 local = " LOCAL" if expression.args.get("local") else "" 2477 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2478 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2479 this = f" INTO TABLE {self.sql(expression, 'this')}" 2480 partition = self.sql(expression, "partition") 2481 partition = f" {partition}" if partition else "" 2482 input_format = self.sql(expression, "input_format") 2483 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2484 serde = self.sql(expression, "serde") 2485 serde = f" SERDE {serde}" if serde else "" 2486 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2487 2488 def null_sql(self, *_) -> str: 2489 return "NULL" 2490 2491 def boolean_sql(self, expression: exp.Boolean) -> str: 2492 return "TRUE" if expression.this else "FALSE" 2493 2494 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2495 this = self.sql(expression, "this") 2496 this = f"{this} " if this else this 2497 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2498 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2499 2500 def withfill_sql(self, expression: exp.WithFill) -> str: 2501 from_sql = self.sql(expression, "from") 2502 from_sql = f" FROM {from_sql}" if from_sql else "" 2503 to_sql = self.sql(expression, "to") 2504 to_sql = f" TO {to_sql}" if to_sql else "" 2505 step_sql = self.sql(expression, "step") 2506 step_sql = f" STEP {step_sql}" if step_sql else "" 2507 interpolated_values = [ 2508 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2509 if isinstance(e, exp.Alias) 2510 else self.sql(e, "this") 2511 for e in expression.args.get("interpolate") or [] 2512 ] 2513 interpolate = ( 2514 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2515 ) 2516 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2517 2518 def cluster_sql(self, expression: exp.Cluster) -> str: 2519 return self.op_expressions("CLUSTER BY", expression) 2520 2521 def distribute_sql(self, expression: exp.Distribute) -> str: 2522 return self.op_expressions("DISTRIBUTE BY", expression) 2523 2524 def sort_sql(self, expression: exp.Sort) -> str: 2525 return self.op_expressions("SORT BY", expression) 2526 2527 def ordered_sql(self, expression: exp.Ordered) -> str: 2528 desc = expression.args.get("desc") 2529 asc = not desc 2530 2531 nulls_first = expression.args.get("nulls_first") 2532 nulls_last = not nulls_first 2533 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2534 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2535 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2536 2537 this = self.sql(expression, "this") 2538 2539 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2540 nulls_sort_change = "" 2541 if nulls_first and ( 2542 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2543 ): 2544 nulls_sort_change = " NULLS FIRST" 2545 elif ( 2546 nulls_last 2547 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2548 and not nulls_are_last 2549 ): 2550 nulls_sort_change = " NULLS LAST" 2551 2552 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2553 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2554 window = expression.find_ancestor(exp.Window, exp.Select) 2555 if isinstance(window, exp.Window) and window.args.get("spec"): 2556 self.unsupported( 2557 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2558 ) 2559 nulls_sort_change = "" 2560 elif self.NULL_ORDERING_SUPPORTED is False and ( 2561 (asc and nulls_sort_change == " NULLS LAST") 2562 or (desc and nulls_sort_change == " NULLS FIRST") 2563 ): 2564 # BigQuery does not allow these ordering/nulls combinations when used under 2565 # an aggregation func or under a window containing one 2566 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2567 2568 if isinstance(ancestor, exp.Window): 2569 ancestor = ancestor.this 2570 if isinstance(ancestor, exp.AggFunc): 2571 self.unsupported( 2572 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2573 ) 2574 nulls_sort_change = "" 2575 elif self.NULL_ORDERING_SUPPORTED is None: 2576 if expression.this.is_int: 2577 self.unsupported( 2578 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2579 ) 2580 elif not isinstance(expression.this, exp.Rand): 2581 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2582 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2583 nulls_sort_change = "" 2584 2585 with_fill = self.sql(expression, "with_fill") 2586 with_fill = f" {with_fill}" if with_fill else "" 2587 2588 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2589 2590 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2591 window_frame = self.sql(expression, "window_frame") 2592 window_frame = f"{window_frame} " if window_frame else "" 2593 2594 this = self.sql(expression, "this") 2595 2596 return f"{window_frame}{this}" 2597 2598 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2599 partition = self.partition_by_sql(expression) 2600 order = self.sql(expression, "order") 2601 measures = self.expressions(expression, key="measures") 2602 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2603 rows = self.sql(expression, "rows") 2604 rows = self.seg(rows) if rows else "" 2605 after = self.sql(expression, "after") 2606 after = self.seg(after) if after else "" 2607 pattern = self.sql(expression, "pattern") 2608 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2609 definition_sqls = [ 2610 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2611 for definition in expression.args.get("define", []) 2612 ] 2613 definitions = self.expressions(sqls=definition_sqls) 2614 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2615 body = "".join( 2616 ( 2617 partition, 2618 order, 2619 measures, 2620 rows, 2621 after, 2622 pattern, 2623 define, 2624 ) 2625 ) 2626 alias = self.sql(expression, "alias") 2627 alias = f" {alias}" if alias else "" 2628 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2629 2630 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2631 limit = expression.args.get("limit") 2632 2633 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2634 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2635 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2636 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2637 2638 return csv( 2639 *sqls, 2640 *[self.sql(join) for join in expression.args.get("joins") or []], 2641 self.sql(expression, "match"), 2642 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2643 self.sql(expression, "prewhere"), 2644 self.sql(expression, "where"), 2645 self.sql(expression, "connect"), 2646 self.sql(expression, "group"), 2647 self.sql(expression, "having"), 2648 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2649 self.sql(expression, "order"), 2650 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2651 *self.after_limit_modifiers(expression), 2652 self.options_modifier(expression), 2653 self.for_modifiers(expression), 2654 sep="", 2655 ) 2656 2657 def options_modifier(self, expression: exp.Expression) -> str: 2658 options = self.expressions(expression, key="options") 2659 return f" {options}" if options else "" 2660 2661 def for_modifiers(self, expression: exp.Expression) -> str: 2662 for_modifiers = self.expressions(expression, key="for") 2663 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2664 2665 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2666 self.unsupported("Unsupported query option.") 2667 return "" 2668 2669 def offset_limit_modifiers( 2670 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2671 ) -> t.List[str]: 2672 return [ 2673 self.sql(expression, "offset") if fetch else self.sql(limit), 2674 self.sql(limit) if fetch else self.sql(expression, "offset"), 2675 ] 2676 2677 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2678 locks = self.expressions(expression, key="locks", sep=" ") 2679 locks = f" {locks}" if locks else "" 2680 return [locks, self.sql(expression, "sample")] 2681 2682 def select_sql(self, expression: exp.Select) -> str: 2683 into = expression.args.get("into") 2684 if not self.SUPPORTS_SELECT_INTO and into: 2685 into.pop() 2686 2687 hint = self.sql(expression, "hint") 2688 distinct = self.sql(expression, "distinct") 2689 distinct = f" {distinct}" if distinct else "" 2690 kind = self.sql(expression, "kind") 2691 2692 limit = expression.args.get("limit") 2693 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2694 top = self.limit_sql(limit, top=True) 2695 limit.pop() 2696 else: 2697 top = "" 2698 2699 expressions = self.expressions(expression) 2700 2701 if kind: 2702 if kind in self.SELECT_KINDS: 2703 kind = f" AS {kind}" 2704 else: 2705 if kind == "STRUCT": 2706 expressions = self.expressions( 2707 sqls=[ 2708 self.sql( 2709 exp.Struct( 2710 expressions=[ 2711 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2712 if isinstance(e, exp.Alias) 2713 else e 2714 for e in expression.expressions 2715 ] 2716 ) 2717 ) 2718 ] 2719 ) 2720 kind = "" 2721 2722 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2723 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2724 2725 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2726 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2727 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2728 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2729 sql = self.query_modifiers( 2730 expression, 2731 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2732 self.sql(expression, "into", comment=False), 2733 self.sql(expression, "from", comment=False), 2734 ) 2735 2736 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2737 if expression.args.get("with"): 2738 sql = self.maybe_comment(sql, expression) 2739 expression.pop_comments() 2740 2741 sql = self.prepend_ctes(expression, sql) 2742 2743 if not self.SUPPORTS_SELECT_INTO and into: 2744 if into.args.get("temporary"): 2745 table_kind = " TEMPORARY" 2746 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2747 table_kind = " UNLOGGED" 2748 else: 2749 table_kind = "" 2750 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2751 2752 return sql 2753 2754 def schema_sql(self, expression: exp.Schema) -> str: 2755 this = self.sql(expression, "this") 2756 sql = self.schema_columns_sql(expression) 2757 return f"{this} {sql}" if this and sql else this or sql 2758 2759 def schema_columns_sql(self, expression: exp.Schema) -> str: 2760 if expression.expressions: 2761 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2762 return "" 2763 2764 def star_sql(self, expression: exp.Star) -> str: 2765 except_ = self.expressions(expression, key="except", flat=True) 2766 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2767 replace = self.expressions(expression, key="replace", flat=True) 2768 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2769 rename = self.expressions(expression, key="rename", flat=True) 2770 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2771 return f"*{except_}{replace}{rename}" 2772 2773 def parameter_sql(self, expression: exp.Parameter) -> str: 2774 this = self.sql(expression, "this") 2775 return f"{self.PARAMETER_TOKEN}{this}" 2776 2777 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2778 this = self.sql(expression, "this") 2779 kind = expression.text("kind") 2780 if kind: 2781 kind = f"{kind}." 2782 return f"@@{kind}{this}" 2783 2784 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2785 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2786 2787 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2788 alias = self.sql(expression, "alias") 2789 alias = f"{sep}{alias}" if alias else "" 2790 sample = self.sql(expression, "sample") 2791 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2792 alias = f"{sample}{alias}" 2793 2794 # Set to None so it's not generated again by self.query_modifiers() 2795 expression.set("sample", None) 2796 2797 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2798 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2799 return self.prepend_ctes(expression, sql) 2800 2801 def qualify_sql(self, expression: exp.Qualify) -> str: 2802 this = self.indent(self.sql(expression, "this")) 2803 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2804 2805 def unnest_sql(self, expression: exp.Unnest) -> str: 2806 args = self.expressions(expression, flat=True) 2807 2808 alias = expression.args.get("alias") 2809 offset = expression.args.get("offset") 2810 2811 if self.UNNEST_WITH_ORDINALITY: 2812 if alias and isinstance(offset, exp.Expression): 2813 alias.append("columns", offset) 2814 2815 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2816 columns = alias.columns 2817 alias = self.sql(columns[0]) if columns else "" 2818 else: 2819 alias = self.sql(alias) 2820 2821 alias = f" AS {alias}" if alias else alias 2822 if self.UNNEST_WITH_ORDINALITY: 2823 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2824 else: 2825 if isinstance(offset, exp.Expression): 2826 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2827 elif offset: 2828 suffix = f"{alias} WITH OFFSET" 2829 else: 2830 suffix = alias 2831 2832 return f"UNNEST({args}){suffix}" 2833 2834 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2835 return "" 2836 2837 def where_sql(self, expression: exp.Where) -> str: 2838 this = self.indent(self.sql(expression, "this")) 2839 return f"{self.seg('WHERE')}{self.sep()}{this}" 2840 2841 def window_sql(self, expression: exp.Window) -> str: 2842 this = self.sql(expression, "this") 2843 partition = self.partition_by_sql(expression) 2844 order = expression.args.get("order") 2845 order = self.order_sql(order, flat=True) if order else "" 2846 spec = self.sql(expression, "spec") 2847 alias = self.sql(expression, "alias") 2848 over = self.sql(expression, "over") or "OVER" 2849 2850 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2851 2852 first = expression.args.get("first") 2853 if first is None: 2854 first = "" 2855 else: 2856 first = "FIRST" if first else "LAST" 2857 2858 if not partition and not order and not spec and alias: 2859 return f"{this} {alias}" 2860 2861 args = self.format_args( 2862 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2863 ) 2864 return f"{this} ({args})" 2865 2866 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2867 partition = self.expressions(expression, key="partition_by", flat=True) 2868 return f"PARTITION BY {partition}" if partition else "" 2869 2870 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2871 kind = self.sql(expression, "kind") 2872 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2873 end = ( 2874 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2875 or "CURRENT ROW" 2876 ) 2877 2878 window_spec = f"{kind} BETWEEN {start} AND {end}" 2879 2880 exclude = self.sql(expression, "exclude") 2881 if exclude: 2882 if self.SUPPORTS_WINDOW_EXCLUDE: 2883 window_spec += f" EXCLUDE {exclude}" 2884 else: 2885 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2886 2887 return window_spec 2888 2889 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2890 this = self.sql(expression, "this") 2891 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2892 return f"{this} WITHIN GROUP ({expression_sql})" 2893 2894 def between_sql(self, expression: exp.Between) -> str: 2895 this = self.sql(expression, "this") 2896 low = self.sql(expression, "low") 2897 high = self.sql(expression, "high") 2898 symmetric = expression.args.get("symmetric") 2899 2900 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2901 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2902 2903 flag = ( 2904 " SYMMETRIC" 2905 if symmetric 2906 else " ASYMMETRIC" 2907 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2908 else "" # silently drop ASYMMETRIC – semantics identical 2909 ) 2910 return f"{this} BETWEEN{flag} {low} AND {high}" 2911 2912 def bracket_offset_expressions( 2913 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2914 ) -> t.List[exp.Expression]: 2915 return apply_index_offset( 2916 expression.this, 2917 expression.expressions, 2918 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2919 dialect=self.dialect, 2920 ) 2921 2922 def bracket_sql(self, expression: exp.Bracket) -> str: 2923 expressions = self.bracket_offset_expressions(expression) 2924 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2925 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2926 2927 def all_sql(self, expression: exp.All) -> str: 2928 this = self.sql(expression, "this") 2929 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2930 this = self.wrap(this) 2931 return f"ALL {this}" 2932 2933 def any_sql(self, expression: exp.Any) -> str: 2934 this = self.sql(expression, "this") 2935 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2936 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2937 this = self.wrap(this) 2938 return f"ANY{this}" 2939 return f"ANY {this}" 2940 2941 def exists_sql(self, expression: exp.Exists) -> str: 2942 return f"EXISTS{self.wrap(expression)}" 2943 2944 def case_sql(self, expression: exp.Case) -> str: 2945 this = self.sql(expression, "this") 2946 statements = [f"CASE {this}" if this else "CASE"] 2947 2948 for e in expression.args["ifs"]: 2949 statements.append(f"WHEN {self.sql(e, 'this')}") 2950 statements.append(f"THEN {self.sql(e, 'true')}") 2951 2952 default = self.sql(expression, "default") 2953 2954 if default: 2955 statements.append(f"ELSE {default}") 2956 2957 statements.append("END") 2958 2959 if self.pretty and self.too_wide(statements): 2960 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2961 2962 return " ".join(statements) 2963 2964 def constraint_sql(self, expression: exp.Constraint) -> str: 2965 this = self.sql(expression, "this") 2966 expressions = self.expressions(expression, flat=True) 2967 return f"CONSTRAINT {this} {expressions}" 2968 2969 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2970 order = expression.args.get("order") 2971 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2972 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2973 2974 def extract_sql(self, expression: exp.Extract) -> str: 2975 from sqlglot.dialects.dialect import map_date_part 2976 2977 this = ( 2978 map_date_part(expression.this, self.dialect) 2979 if self.NORMALIZE_EXTRACT_DATE_PARTS 2980 else expression.this 2981 ) 2982 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2983 expression_sql = self.sql(expression, "expression") 2984 2985 return f"EXTRACT({this_sql} FROM {expression_sql})" 2986 2987 def trim_sql(self, expression: exp.Trim) -> str: 2988 trim_type = self.sql(expression, "position") 2989 2990 if trim_type == "LEADING": 2991 func_name = "LTRIM" 2992 elif trim_type == "TRAILING": 2993 func_name = "RTRIM" 2994 else: 2995 func_name = "TRIM" 2996 2997 return self.func(func_name, expression.this, expression.expression) 2998 2999 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3000 args = expression.expressions 3001 if isinstance(expression, exp.ConcatWs): 3002 args = args[1:] # Skip the delimiter 3003 3004 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3005 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3006 3007 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3008 3009 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3010 if not e.type: 3011 from sqlglot.optimizer.annotate_types import annotate_types 3012 3013 e = annotate_types(e, dialect=self.dialect) 3014 3015 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3016 return e 3017 3018 return exp.func("coalesce", e, exp.Literal.string("")) 3019 3020 args = [_wrap_with_coalesce(e) for e in args] 3021 3022 return args 3023 3024 def concat_sql(self, expression: exp.Concat) -> str: 3025 expressions = self.convert_concat_args(expression) 3026 3027 # Some dialects don't allow a single-argument CONCAT call 3028 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3029 return self.sql(expressions[0]) 3030 3031 return self.func("CONCAT", *expressions) 3032 3033 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3034 return self.func( 3035 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3036 ) 3037 3038 def check_sql(self, expression: exp.Check) -> str: 3039 this = self.sql(expression, key="this") 3040 return f"CHECK ({this})" 3041 3042 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3043 expressions = self.expressions(expression, flat=True) 3044 expressions = f" ({expressions})" if expressions else "" 3045 reference = self.sql(expression, "reference") 3046 reference = f" {reference}" if reference else "" 3047 delete = self.sql(expression, "delete") 3048 delete = f" ON DELETE {delete}" if delete else "" 3049 update = self.sql(expression, "update") 3050 update = f" ON UPDATE {update}" if update else "" 3051 options = self.expressions(expression, key="options", flat=True, sep=" ") 3052 options = f" {options}" if options else "" 3053 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3054 3055 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3056 expressions = self.expressions(expression, flat=True) 3057 include = self.sql(expression, "include") 3058 options = self.expressions(expression, key="options", flat=True, sep=" ") 3059 options = f" {options}" if options else "" 3060 return f"PRIMARY KEY ({expressions}){include}{options}" 3061 3062 def if_sql(self, expression: exp.If) -> str: 3063 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3064 3065 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3066 modifier = expression.args.get("modifier") 3067 modifier = f" {modifier}" if modifier else "" 3068 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3069 3070 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3071 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3072 3073 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3074 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3075 3076 if expression.args.get("escape"): 3077 path = self.escape_str(path) 3078 3079 if self.QUOTE_JSON_PATH: 3080 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3081 3082 return path 3083 3084 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3085 if isinstance(expression, exp.JSONPathPart): 3086 transform = self.TRANSFORMS.get(expression.__class__) 3087 if not callable(transform): 3088 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3089 return "" 3090 3091 return transform(self, expression) 3092 3093 if isinstance(expression, int): 3094 return str(expression) 3095 3096 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3097 escaped = expression.replace("'", "\\'") 3098 escaped = f"\\'{expression}\\'" 3099 else: 3100 escaped = expression.replace('"', '\\"') 3101 escaped = f'"{escaped}"' 3102 3103 return escaped 3104 3105 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3106 return f"{self.sql(expression, 'this')} FORMAT JSON" 3107 3108 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3109 # Output the Teradata column FORMAT override. 3110 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3111 this = self.sql(expression, "this") 3112 fmt = self.sql(expression, "format") 3113 return f"{this} (FORMAT {fmt})" 3114 3115 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3116 null_handling = expression.args.get("null_handling") 3117 null_handling = f" {null_handling}" if null_handling else "" 3118 3119 unique_keys = expression.args.get("unique_keys") 3120 if unique_keys is not None: 3121 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3122 else: 3123 unique_keys = "" 3124 3125 return_type = self.sql(expression, "return_type") 3126 return_type = f" RETURNING {return_type}" if return_type else "" 3127 encoding = self.sql(expression, "encoding") 3128 encoding = f" ENCODING {encoding}" if encoding else "" 3129 3130 return self.func( 3131 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3132 *expression.expressions, 3133 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3134 ) 3135 3136 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3137 return self.jsonobject_sql(expression) 3138 3139 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3140 null_handling = expression.args.get("null_handling") 3141 null_handling = f" {null_handling}" if null_handling else "" 3142 return_type = self.sql(expression, "return_type") 3143 return_type = f" RETURNING {return_type}" if return_type else "" 3144 strict = " STRICT" if expression.args.get("strict") else "" 3145 return self.func( 3146 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3147 ) 3148 3149 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3150 this = self.sql(expression, "this") 3151 order = self.sql(expression, "order") 3152 null_handling = expression.args.get("null_handling") 3153 null_handling = f" {null_handling}" if null_handling else "" 3154 return_type = self.sql(expression, "return_type") 3155 return_type = f" RETURNING {return_type}" if return_type else "" 3156 strict = " STRICT" if expression.args.get("strict") else "" 3157 return self.func( 3158 "JSON_ARRAYAGG", 3159 this, 3160 suffix=f"{order}{null_handling}{return_type}{strict})", 3161 ) 3162 3163 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3164 path = self.sql(expression, "path") 3165 path = f" PATH {path}" if path else "" 3166 nested_schema = self.sql(expression, "nested_schema") 3167 3168 if nested_schema: 3169 return f"NESTED{path} {nested_schema}" 3170 3171 this = self.sql(expression, "this") 3172 kind = self.sql(expression, "kind") 3173 kind = f" {kind}" if kind else "" 3174 return f"{this}{kind}{path}" 3175 3176 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3177 return self.func("COLUMNS", *expression.expressions) 3178 3179 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3180 this = self.sql(expression, "this") 3181 path = self.sql(expression, "path") 3182 path = f", {path}" if path else "" 3183 error_handling = expression.args.get("error_handling") 3184 error_handling = f" {error_handling}" if error_handling else "" 3185 empty_handling = expression.args.get("empty_handling") 3186 empty_handling = f" {empty_handling}" if empty_handling else "" 3187 schema = self.sql(expression, "schema") 3188 return self.func( 3189 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3190 ) 3191 3192 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3193 this = self.sql(expression, "this") 3194 kind = self.sql(expression, "kind") 3195 path = self.sql(expression, "path") 3196 path = f" {path}" if path else "" 3197 as_json = " AS JSON" if expression.args.get("as_json") else "" 3198 return f"{this} {kind}{path}{as_json}" 3199 3200 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3201 this = self.sql(expression, "this") 3202 path = self.sql(expression, "path") 3203 path = f", {path}" if path else "" 3204 expressions = self.expressions(expression) 3205 with_ = ( 3206 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3207 if expressions 3208 else "" 3209 ) 3210 return f"OPENJSON({this}{path}){with_}" 3211 3212 def in_sql(self, expression: exp.In) -> str: 3213 query = expression.args.get("query") 3214 unnest = expression.args.get("unnest") 3215 field = expression.args.get("field") 3216 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3217 3218 if query: 3219 in_sql = self.sql(query) 3220 elif unnest: 3221 in_sql = self.in_unnest_op(unnest) 3222 elif field: 3223 in_sql = self.sql(field) 3224 else: 3225 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3226 3227 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3228 3229 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3230 return f"(SELECT {self.sql(unnest)})" 3231 3232 def interval_sql(self, expression: exp.Interval) -> str: 3233 unit = self.sql(expression, "unit") 3234 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3235 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3236 unit = f" {unit}" if unit else "" 3237 3238 if self.SINGLE_STRING_INTERVAL: 3239 this = expression.this.name if expression.this else "" 3240 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3241 3242 this = self.sql(expression, "this") 3243 if this: 3244 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3245 this = f" {this}" if unwrapped else f" ({this})" 3246 3247 return f"INTERVAL{this}{unit}" 3248 3249 def return_sql(self, expression: exp.Return) -> str: 3250 return f"RETURN {self.sql(expression, 'this')}" 3251 3252 def reference_sql(self, expression: exp.Reference) -> str: 3253 this = self.sql(expression, "this") 3254 expressions = self.expressions(expression, flat=True) 3255 expressions = f"({expressions})" if expressions else "" 3256 options = self.expressions(expression, key="options", flat=True, sep=" ") 3257 options = f" {options}" if options else "" 3258 return f"REFERENCES {this}{expressions}{options}" 3259 3260 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3261 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3262 parent = expression.parent 3263 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3264 return self.func( 3265 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3266 ) 3267 3268 def paren_sql(self, expression: exp.Paren) -> str: 3269 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3270 return f"({sql}{self.seg(')', sep='')}" 3271 3272 def neg_sql(self, expression: exp.Neg) -> str: 3273 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3274 this_sql = self.sql(expression, "this") 3275 sep = " " if this_sql[0] == "-" else "" 3276 return f"-{sep}{this_sql}" 3277 3278 def not_sql(self, expression: exp.Not) -> str: 3279 return f"NOT {self.sql(expression, 'this')}" 3280 3281 def alias_sql(self, expression: exp.Alias) -> str: 3282 alias = self.sql(expression, "alias") 3283 alias = f" AS {alias}" if alias else "" 3284 return f"{self.sql(expression, 'this')}{alias}" 3285 3286 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3287 alias = expression.args["alias"] 3288 3289 parent = expression.parent 3290 pivot = parent and parent.parent 3291 3292 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3293 identifier_alias = isinstance(alias, exp.Identifier) 3294 literal_alias = isinstance(alias, exp.Literal) 3295 3296 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3297 alias.replace(exp.Literal.string(alias.output_name)) 3298 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3299 alias.replace(exp.to_identifier(alias.output_name)) 3300 3301 return self.alias_sql(expression) 3302 3303 def aliases_sql(self, expression: exp.Aliases) -> str: 3304 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3305 3306 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3307 this = self.sql(expression, "this") 3308 index = self.sql(expression, "expression") 3309 return f"{this} AT {index}" 3310 3311 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3312 this = self.sql(expression, "this") 3313 zone = self.sql(expression, "zone") 3314 return f"{this} AT TIME ZONE {zone}" 3315 3316 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3317 this = self.sql(expression, "this") 3318 zone = self.sql(expression, "zone") 3319 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3320 3321 def add_sql(self, expression: exp.Add) -> str: 3322 return self.binary(expression, "+") 3323 3324 def and_sql( 3325 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3326 ) -> str: 3327 return self.connector_sql(expression, "AND", stack) 3328 3329 def or_sql( 3330 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3331 ) -> str: 3332 return self.connector_sql(expression, "OR", stack) 3333 3334 def xor_sql( 3335 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3336 ) -> str: 3337 return self.connector_sql(expression, "XOR", stack) 3338 3339 def connector_sql( 3340 self, 3341 expression: exp.Connector, 3342 op: str, 3343 stack: t.Optional[t.List[str | exp.Expression]] = None, 3344 ) -> str: 3345 if stack is not None: 3346 if expression.expressions: 3347 stack.append(self.expressions(expression, sep=f" {op} ")) 3348 else: 3349 stack.append(expression.right) 3350 if expression.comments and self.comments: 3351 for comment in expression.comments: 3352 if comment: 3353 op += f" /*{self.sanitize_comment(comment)}*/" 3354 stack.extend((op, expression.left)) 3355 return op 3356 3357 stack = [expression] 3358 sqls: t.List[str] = [] 3359 ops = set() 3360 3361 while stack: 3362 node = stack.pop() 3363 if isinstance(node, exp.Connector): 3364 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3365 else: 3366 sql = self.sql(node) 3367 if sqls and sqls[-1] in ops: 3368 sqls[-1] += f" {sql}" 3369 else: 3370 sqls.append(sql) 3371 3372 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3373 return sep.join(sqls) 3374 3375 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3376 return self.binary(expression, "&") 3377 3378 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3379 return self.binary(expression, "<<") 3380 3381 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3382 return f"~{self.sql(expression, 'this')}" 3383 3384 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3385 return self.binary(expression, "|") 3386 3387 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3388 return self.binary(expression, ">>") 3389 3390 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3391 return self.binary(expression, "^") 3392 3393 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3394 format_sql = self.sql(expression, "format") 3395 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3396 to_sql = self.sql(expression, "to") 3397 to_sql = f" {to_sql}" if to_sql else "" 3398 action = self.sql(expression, "action") 3399 action = f" {action}" if action else "" 3400 default = self.sql(expression, "default") 3401 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3402 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3403 3404 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3405 zone = self.sql(expression, "this") 3406 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3407 3408 def collate_sql(self, expression: exp.Collate) -> str: 3409 if self.COLLATE_IS_FUNC: 3410 return self.function_fallback_sql(expression) 3411 return self.binary(expression, "COLLATE") 3412 3413 def command_sql(self, expression: exp.Command) -> str: 3414 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3415 3416 def comment_sql(self, expression: exp.Comment) -> str: 3417 this = self.sql(expression, "this") 3418 kind = expression.args["kind"] 3419 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3420 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3421 expression_sql = self.sql(expression, "expression") 3422 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3423 3424 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3425 this = self.sql(expression, "this") 3426 delete = " DELETE" if expression.args.get("delete") else "" 3427 recompress = self.sql(expression, "recompress") 3428 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3429 to_disk = self.sql(expression, "to_disk") 3430 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3431 to_volume = self.sql(expression, "to_volume") 3432 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3433 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3434 3435 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3436 where = self.sql(expression, "where") 3437 group = self.sql(expression, "group") 3438 aggregates = self.expressions(expression, key="aggregates") 3439 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3440 3441 if not (where or group or aggregates) and len(expression.expressions) == 1: 3442 return f"TTL {self.expressions(expression, flat=True)}" 3443 3444 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3445 3446 def transaction_sql(self, expression: exp.Transaction) -> str: 3447 return "BEGIN" 3448 3449 def commit_sql(self, expression: exp.Commit) -> str: 3450 chain = expression.args.get("chain") 3451 if chain is not None: 3452 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3453 3454 return f"COMMIT{chain or ''}" 3455 3456 def rollback_sql(self, expression: exp.Rollback) -> str: 3457 savepoint = expression.args.get("savepoint") 3458 savepoint = f" TO {savepoint}" if savepoint else "" 3459 return f"ROLLBACK{savepoint}" 3460 3461 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3462 this = self.sql(expression, "this") 3463 3464 dtype = self.sql(expression, "dtype") 3465 if dtype: 3466 collate = self.sql(expression, "collate") 3467 collate = f" COLLATE {collate}" if collate else "" 3468 using = self.sql(expression, "using") 3469 using = f" USING {using}" if using else "" 3470 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3471 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3472 3473 default = self.sql(expression, "default") 3474 if default: 3475 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3476 3477 comment = self.sql(expression, "comment") 3478 if comment: 3479 return f"ALTER COLUMN {this} COMMENT {comment}" 3480 3481 visible = expression.args.get("visible") 3482 if visible: 3483 return f"ALTER COLUMN {this} SET {visible}" 3484 3485 allow_null = expression.args.get("allow_null") 3486 drop = expression.args.get("drop") 3487 3488 if not drop and not allow_null: 3489 self.unsupported("Unsupported ALTER COLUMN syntax") 3490 3491 if allow_null is not None: 3492 keyword = "DROP" if drop else "SET" 3493 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3494 3495 return f"ALTER COLUMN {this} DROP DEFAULT" 3496 3497 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3498 this = self.sql(expression, "this") 3499 3500 visible = expression.args.get("visible") 3501 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3502 3503 return f"ALTER INDEX {this} {visible_sql}" 3504 3505 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3506 this = self.sql(expression, "this") 3507 if not isinstance(expression.this, exp.Var): 3508 this = f"KEY DISTKEY {this}" 3509 return f"ALTER DISTSTYLE {this}" 3510 3511 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3512 compound = " COMPOUND" if expression.args.get("compound") else "" 3513 this = self.sql(expression, "this") 3514 expressions = self.expressions(expression, flat=True) 3515 expressions = f"({expressions})" if expressions else "" 3516 return f"ALTER{compound} SORTKEY {this or expressions}" 3517 3518 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3519 if not self.RENAME_TABLE_WITH_DB: 3520 # Remove db from tables 3521 expression = expression.transform( 3522 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3523 ).assert_is(exp.AlterRename) 3524 this = self.sql(expression, "this") 3525 to_kw = " TO" if include_to else "" 3526 return f"RENAME{to_kw} {this}" 3527 3528 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3529 exists = " IF EXISTS" if expression.args.get("exists") else "" 3530 old_column = self.sql(expression, "this") 3531 new_column = self.sql(expression, "to") 3532 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3533 3534 def alterset_sql(self, expression: exp.AlterSet) -> str: 3535 exprs = self.expressions(expression, flat=True) 3536 if self.ALTER_SET_WRAPPED: 3537 exprs = f"({exprs})" 3538 3539 return f"SET {exprs}" 3540 3541 def alter_sql(self, expression: exp.Alter) -> str: 3542 actions = expression.args["actions"] 3543 3544 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3545 actions[0], exp.ColumnDef 3546 ): 3547 actions_sql = self.expressions(expression, key="actions", flat=True) 3548 actions_sql = f"ADD {actions_sql}" 3549 else: 3550 actions_list = [] 3551 for action in actions: 3552 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3553 action_sql = self.add_column_sql(action) 3554 else: 3555 action_sql = self.sql(action) 3556 if isinstance(action, exp.Query): 3557 action_sql = f"AS {action_sql}" 3558 3559 actions_list.append(action_sql) 3560 3561 actions_sql = self.format_args(*actions_list).lstrip("\n") 3562 3563 exists = " IF EXISTS" if expression.args.get("exists") else "" 3564 on_cluster = self.sql(expression, "cluster") 3565 on_cluster = f" {on_cluster}" if on_cluster else "" 3566 only = " ONLY" if expression.args.get("only") else "" 3567 options = self.expressions(expression, key="options") 3568 options = f", {options}" if options else "" 3569 kind = self.sql(expression, "kind") 3570 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3571 check = " WITH CHECK" if expression.args.get("check") else "" 3572 3573 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}" 3574 3575 def add_column_sql(self, expression: exp.Expression) -> str: 3576 sql = self.sql(expression) 3577 if isinstance(expression, exp.Schema): 3578 column_text = " COLUMNS" 3579 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3580 column_text = " COLUMN" 3581 else: 3582 column_text = "" 3583 3584 return f"ADD{column_text} {sql}" 3585 3586 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3587 expressions = self.expressions(expression) 3588 exists = " IF EXISTS " if expression.args.get("exists") else " " 3589 return f"DROP{exists}{expressions}" 3590 3591 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3592 return f"ADD {self.expressions(expression, indent=False)}" 3593 3594 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3595 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3596 location = self.sql(expression, "location") 3597 location = f" {location}" if location else "" 3598 return f"ADD {exists}{self.sql(expression.this)}{location}" 3599 3600 def distinct_sql(self, expression: exp.Distinct) -> str: 3601 this = self.expressions(expression, flat=True) 3602 3603 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3604 case = exp.case() 3605 for arg in expression.expressions: 3606 case = case.when(arg.is_(exp.null()), exp.null()) 3607 this = self.sql(case.else_(f"({this})")) 3608 3609 this = f" {this}" if this else "" 3610 3611 on = self.sql(expression, "on") 3612 on = f" ON {on}" if on else "" 3613 return f"DISTINCT{this}{on}" 3614 3615 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3616 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3617 3618 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3619 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3620 3621 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3622 this_sql = self.sql(expression, "this") 3623 expression_sql = self.sql(expression, "expression") 3624 kind = "MAX" if expression.args.get("max") else "MIN" 3625 return f"{this_sql} HAVING {kind} {expression_sql}" 3626 3627 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3628 return self.sql( 3629 exp.Cast( 3630 this=exp.Div(this=expression.this, expression=expression.expression), 3631 to=exp.DataType(this=exp.DataType.Type.INT), 3632 ) 3633 ) 3634 3635 def dpipe_sql(self, expression: exp.DPipe) -> str: 3636 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3637 return self.func( 3638 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3639 ) 3640 return self.binary(expression, "||") 3641 3642 def div_sql(self, expression: exp.Div) -> str: 3643 l, r = expression.left, expression.right 3644 3645 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3646 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3647 3648 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3649 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3650 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3651 3652 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3653 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3654 return self.sql( 3655 exp.cast( 3656 l / r, 3657 to=exp.DataType.Type.BIGINT, 3658 ) 3659 ) 3660 3661 return self.binary(expression, "/") 3662 3663 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3664 n = exp._wrap(expression.this, exp.Binary) 3665 d = exp._wrap(expression.expression, exp.Binary) 3666 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3667 3668 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3669 return self.binary(expression, "OVERLAPS") 3670 3671 def distance_sql(self, expression: exp.Distance) -> str: 3672 return self.binary(expression, "<->") 3673 3674 def dot_sql(self, expression: exp.Dot) -> str: 3675 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3676 3677 def eq_sql(self, expression: exp.EQ) -> str: 3678 return self.binary(expression, "=") 3679 3680 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3681 return self.binary(expression, ":=") 3682 3683 def escape_sql(self, expression: exp.Escape) -> str: 3684 return self.binary(expression, "ESCAPE") 3685 3686 def glob_sql(self, expression: exp.Glob) -> str: 3687 return self.binary(expression, "GLOB") 3688 3689 def gt_sql(self, expression: exp.GT) -> str: 3690 return self.binary(expression, ">") 3691 3692 def gte_sql(self, expression: exp.GTE) -> str: 3693 return self.binary(expression, ">=") 3694 3695 def is_sql(self, expression: exp.Is) -> str: 3696 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3697 return self.sql( 3698 expression.this if expression.expression.this else exp.not_(expression.this) 3699 ) 3700 return self.binary(expression, "IS") 3701 3702 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3703 this = expression.this 3704 rhs = expression.expression 3705 3706 if isinstance(expression, exp.Like): 3707 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3708 op = "LIKE" 3709 else: 3710 exp_class = exp.ILike 3711 op = "ILIKE" 3712 3713 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3714 exprs = rhs.this.unnest() 3715 3716 if isinstance(exprs, exp.Tuple): 3717 exprs = exprs.expressions 3718 3719 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3720 3721 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3722 for expr in exprs[1:]: 3723 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3724 3725 return self.sql(like_expr) 3726 3727 return self.binary(expression, op) 3728 3729 def like_sql(self, expression: exp.Like) -> str: 3730 return self._like_sql(expression) 3731 3732 def ilike_sql(self, expression: exp.ILike) -> str: 3733 return self._like_sql(expression) 3734 3735 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3736 return self.binary(expression, "SIMILAR TO") 3737 3738 def lt_sql(self, expression: exp.LT) -> str: 3739 return self.binary(expression, "<") 3740 3741 def lte_sql(self, expression: exp.LTE) -> str: 3742 return self.binary(expression, "<=") 3743 3744 def mod_sql(self, expression: exp.Mod) -> str: 3745 return self.binary(expression, "%") 3746 3747 def mul_sql(self, expression: exp.Mul) -> str: 3748 return self.binary(expression, "*") 3749 3750 def neq_sql(self, expression: exp.NEQ) -> str: 3751 return self.binary(expression, "<>") 3752 3753 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3754 return self.binary(expression, "IS NOT DISTINCT FROM") 3755 3756 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3757 return self.binary(expression, "IS DISTINCT FROM") 3758 3759 def slice_sql(self, expression: exp.Slice) -> str: 3760 return self.binary(expression, ":") 3761 3762 def sub_sql(self, expression: exp.Sub) -> str: 3763 return self.binary(expression, "-") 3764 3765 def trycast_sql(self, expression: exp.TryCast) -> str: 3766 return self.cast_sql(expression, safe_prefix="TRY_") 3767 3768 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3769 return self.cast_sql(expression) 3770 3771 def try_sql(self, expression: exp.Try) -> str: 3772 if not self.TRY_SUPPORTED: 3773 self.unsupported("Unsupported TRY function") 3774 return self.sql(expression, "this") 3775 3776 return self.func("TRY", expression.this) 3777 3778 def log_sql(self, expression: exp.Log) -> str: 3779 this = expression.this 3780 expr = expression.expression 3781 3782 if self.dialect.LOG_BASE_FIRST is False: 3783 this, expr = expr, this 3784 elif self.dialect.LOG_BASE_FIRST is None and expr: 3785 if this.name in ("2", "10"): 3786 return self.func(f"LOG{this.name}", expr) 3787 3788 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3789 3790 return self.func("LOG", this, expr) 3791 3792 def use_sql(self, expression: exp.Use) -> str: 3793 kind = self.sql(expression, "kind") 3794 kind = f" {kind}" if kind else "" 3795 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3796 this = f" {this}" if this else "" 3797 return f"USE{kind}{this}" 3798 3799 def binary(self, expression: exp.Binary, op: str) -> str: 3800 sqls: t.List[str] = [] 3801 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3802 binary_type = type(expression) 3803 3804 while stack: 3805 node = stack.pop() 3806 3807 if type(node) is binary_type: 3808 op_func = node.args.get("operator") 3809 if op_func: 3810 op = f"OPERATOR({self.sql(op_func)})" 3811 3812 stack.append(node.right) 3813 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3814 stack.append(node.left) 3815 else: 3816 sqls.append(self.sql(node)) 3817 3818 return "".join(sqls) 3819 3820 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3821 to_clause = self.sql(expression, "to") 3822 if to_clause: 3823 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3824 3825 return self.function_fallback_sql(expression) 3826 3827 def function_fallback_sql(self, expression: exp.Func) -> str: 3828 args = [] 3829 3830 for key in expression.arg_types: 3831 arg_value = expression.args.get(key) 3832 3833 if isinstance(arg_value, list): 3834 for value in arg_value: 3835 args.append(value) 3836 elif arg_value is not None: 3837 args.append(arg_value) 3838 3839 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3840 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3841 else: 3842 name = expression.sql_name() 3843 3844 return self.func(name, *args) 3845 3846 def func( 3847 self, 3848 name: str, 3849 *args: t.Optional[exp.Expression | str], 3850 prefix: str = "(", 3851 suffix: str = ")", 3852 normalize: bool = True, 3853 ) -> str: 3854 name = self.normalize_func(name) if normalize else name 3855 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3856 3857 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3858 arg_sqls = tuple( 3859 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3860 ) 3861 if self.pretty and self.too_wide(arg_sqls): 3862 return self.indent( 3863 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3864 ) 3865 return sep.join(arg_sqls) 3866 3867 def too_wide(self, args: t.Iterable) -> bool: 3868 return sum(len(arg) for arg in args) > self.max_text_width 3869 3870 def format_time( 3871 self, 3872 expression: exp.Expression, 3873 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3874 inverse_time_trie: t.Optional[t.Dict] = None, 3875 ) -> t.Optional[str]: 3876 return format_time( 3877 self.sql(expression, "format"), 3878 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3879 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3880 ) 3881 3882 def expressions( 3883 self, 3884 expression: t.Optional[exp.Expression] = None, 3885 key: t.Optional[str] = None, 3886 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3887 flat: bool = False, 3888 indent: bool = True, 3889 skip_first: bool = False, 3890 skip_last: bool = False, 3891 sep: str = ", ", 3892 prefix: str = "", 3893 dynamic: bool = False, 3894 new_line: bool = False, 3895 ) -> str: 3896 expressions = expression.args.get(key or "expressions") if expression else sqls 3897 3898 if not expressions: 3899 return "" 3900 3901 if flat: 3902 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3903 3904 num_sqls = len(expressions) 3905 result_sqls = [] 3906 3907 for i, e in enumerate(expressions): 3908 sql = self.sql(e, comment=False) 3909 if not sql: 3910 continue 3911 3912 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3913 3914 if self.pretty: 3915 if self.leading_comma: 3916 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3917 else: 3918 result_sqls.append( 3919 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3920 ) 3921 else: 3922 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3923 3924 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3925 if new_line: 3926 result_sqls.insert(0, "") 3927 result_sqls.append("") 3928 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3929 else: 3930 result_sql = "".join(result_sqls) 3931 3932 return ( 3933 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3934 if indent 3935 else result_sql 3936 ) 3937 3938 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3939 flat = flat or isinstance(expression.parent, exp.Properties) 3940 expressions_sql = self.expressions(expression, flat=flat) 3941 if flat: 3942 return f"{op} {expressions_sql}" 3943 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3944 3945 def naked_property(self, expression: exp.Property) -> str: 3946 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3947 if not property_name: 3948 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3949 return f"{property_name} {self.sql(expression, 'this')}" 3950 3951 def tag_sql(self, expression: exp.Tag) -> str: 3952 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3953 3954 def token_sql(self, token_type: TokenType) -> str: 3955 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3956 3957 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3958 this = self.sql(expression, "this") 3959 expressions = self.no_identify(self.expressions, expression) 3960 expressions = ( 3961 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3962 ) 3963 return f"{this}{expressions}" if expressions.strip() != "" else this 3964 3965 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3966 this = self.sql(expression, "this") 3967 expressions = self.expressions(expression, flat=True) 3968 return f"{this}({expressions})" 3969 3970 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3971 return self.binary(expression, "=>") 3972 3973 def when_sql(self, expression: exp.When) -> str: 3974 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3975 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3976 condition = self.sql(expression, "condition") 3977 condition = f" AND {condition}" if condition else "" 3978 3979 then_expression = expression.args.get("then") 3980 if isinstance(then_expression, exp.Insert): 3981 this = self.sql(then_expression, "this") 3982 this = f"INSERT {this}" if this else "INSERT" 3983 then = self.sql(then_expression, "expression") 3984 then = f"{this} VALUES {then}" if then else this 3985 elif isinstance(then_expression, exp.Update): 3986 if isinstance(then_expression.args.get("expressions"), exp.Star): 3987 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3988 else: 3989 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3990 else: 3991 then = self.sql(then_expression) 3992 return f"WHEN {matched}{source}{condition} THEN {then}" 3993 3994 def whens_sql(self, expression: exp.Whens) -> str: 3995 return self.expressions(expression, sep=" ", indent=False) 3996 3997 def merge_sql(self, expression: exp.Merge) -> str: 3998 table = expression.this 3999 table_alias = "" 4000 4001 hints = table.args.get("hints") 4002 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4003 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4004 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4005 4006 this = self.sql(table) 4007 using = f"USING {self.sql(expression, 'using')}" 4008 on = f"ON {self.sql(expression, 'on')}" 4009 whens = self.sql(expression, "whens") 4010 4011 returning = self.sql(expression, "returning") 4012 if returning: 4013 whens = f"{whens}{returning}" 4014 4015 sep = self.sep() 4016 4017 return self.prepend_ctes( 4018 expression, 4019 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4020 ) 4021 4022 @unsupported_args("format") 4023 def tochar_sql(self, expression: exp.ToChar) -> str: 4024 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4025 4026 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4027 if not self.SUPPORTS_TO_NUMBER: 4028 self.unsupported("Unsupported TO_NUMBER function") 4029 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4030 4031 fmt = expression.args.get("format") 4032 if not fmt: 4033 self.unsupported("Conversion format is required for TO_NUMBER") 4034 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4035 4036 return self.func("TO_NUMBER", expression.this, fmt) 4037 4038 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4039 this = self.sql(expression, "this") 4040 kind = self.sql(expression, "kind") 4041 settings_sql = self.expressions(expression, key="settings", sep=" ") 4042 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4043 return f"{this}({kind}{args})" 4044 4045 def dictrange_sql(self, expression: exp.DictRange) -> str: 4046 this = self.sql(expression, "this") 4047 max = self.sql(expression, "max") 4048 min = self.sql(expression, "min") 4049 return f"{this}(MIN {min} MAX {max})" 4050 4051 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4052 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4053 4054 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4055 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4056 4057 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4058 def uniquekeyproperty_sql( 4059 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4060 ) -> str: 4061 return f"{prefix} ({self.expressions(expression, flat=True)})" 4062 4063 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4064 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4065 expressions = self.expressions(expression, flat=True) 4066 expressions = f" {self.wrap(expressions)}" if expressions else "" 4067 buckets = self.sql(expression, "buckets") 4068 kind = self.sql(expression, "kind") 4069 buckets = f" BUCKETS {buckets}" if buckets else "" 4070 order = self.sql(expression, "order") 4071 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4072 4073 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4074 return "" 4075 4076 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4077 expressions = self.expressions(expression, key="expressions", flat=True) 4078 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4079 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4080 buckets = self.sql(expression, "buckets") 4081 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4082 4083 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4084 this = self.sql(expression, "this") 4085 having = self.sql(expression, "having") 4086 4087 if having: 4088 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4089 4090 return self.func("ANY_VALUE", this) 4091 4092 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4093 transform = self.func("TRANSFORM", *expression.expressions) 4094 row_format_before = self.sql(expression, "row_format_before") 4095 row_format_before = f" {row_format_before}" if row_format_before else "" 4096 record_writer = self.sql(expression, "record_writer") 4097 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4098 using = f" USING {self.sql(expression, 'command_script')}" 4099 schema = self.sql(expression, "schema") 4100 schema = f" AS {schema}" if schema else "" 4101 row_format_after = self.sql(expression, "row_format_after") 4102 row_format_after = f" {row_format_after}" if row_format_after else "" 4103 record_reader = self.sql(expression, "record_reader") 4104 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4105 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4106 4107 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4108 key_block_size = self.sql(expression, "key_block_size") 4109 if key_block_size: 4110 return f"KEY_BLOCK_SIZE = {key_block_size}" 4111 4112 using = self.sql(expression, "using") 4113 if using: 4114 return f"USING {using}" 4115 4116 parser = self.sql(expression, "parser") 4117 if parser: 4118 return f"WITH PARSER {parser}" 4119 4120 comment = self.sql(expression, "comment") 4121 if comment: 4122 return f"COMMENT {comment}" 4123 4124 visible = expression.args.get("visible") 4125 if visible is not None: 4126 return "VISIBLE" if visible else "INVISIBLE" 4127 4128 engine_attr = self.sql(expression, "engine_attr") 4129 if engine_attr: 4130 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4131 4132 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4133 if secondary_engine_attr: 4134 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4135 4136 self.unsupported("Unsupported index constraint option.") 4137 return "" 4138 4139 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4140 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4141 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4142 4143 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4144 kind = self.sql(expression, "kind") 4145 kind = f"{kind} INDEX" if kind else "INDEX" 4146 this = self.sql(expression, "this") 4147 this = f" {this}" if this else "" 4148 index_type = self.sql(expression, "index_type") 4149 index_type = f" USING {index_type}" if index_type else "" 4150 expressions = self.expressions(expression, flat=True) 4151 expressions = f" ({expressions})" if expressions else "" 4152 options = self.expressions(expression, key="options", sep=" ") 4153 options = f" {options}" if options else "" 4154 return f"{kind}{this}{index_type}{expressions}{options}" 4155 4156 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4157 if self.NVL2_SUPPORTED: 4158 return self.function_fallback_sql(expression) 4159 4160 case = exp.Case().when( 4161 expression.this.is_(exp.null()).not_(copy=False), 4162 expression.args["true"], 4163 copy=False, 4164 ) 4165 else_cond = expression.args.get("false") 4166 if else_cond: 4167 case.else_(else_cond, copy=False) 4168 4169 return self.sql(case) 4170 4171 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4172 this = self.sql(expression, "this") 4173 expr = self.sql(expression, "expression") 4174 iterator = self.sql(expression, "iterator") 4175 condition = self.sql(expression, "condition") 4176 condition = f" IF {condition}" if condition else "" 4177 return f"{this} FOR {expr} IN {iterator}{condition}" 4178 4179 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4180 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4181 4182 def opclass_sql(self, expression: exp.Opclass) -> str: 4183 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4184 4185 def predict_sql(self, expression: exp.Predict) -> str: 4186 model = self.sql(expression, "this") 4187 model = f"MODEL {model}" 4188 table = self.sql(expression, "expression") 4189 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4190 parameters = self.sql(expression, "params_struct") 4191 return self.func("PREDICT", model, table, parameters or None) 4192 4193 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4194 model = self.sql(expression, "this") 4195 model = f"MODEL {model}" 4196 table = self.sql(expression, "expression") 4197 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4198 parameters = self.sql(expression, "params_struct") 4199 return self.func("GENERATE_EMBEDDING", model, table, parameters or None) 4200 4201 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4202 this_sql = self.sql(expression, "this") 4203 if isinstance(expression.this, exp.Table): 4204 this_sql = f"TABLE {this_sql}" 4205 4206 return self.func( 4207 "FEATURES_AT_TIME", 4208 this_sql, 4209 expression.args.get("time"), 4210 expression.args.get("num_rows"), 4211 expression.args.get("ignore_feature_nulls"), 4212 ) 4213 4214 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4215 this_sql = self.sql(expression, "this") 4216 if isinstance(expression.this, exp.Table): 4217 this_sql = f"TABLE {this_sql}" 4218 4219 query_table = self.sql(expression, "query_table") 4220 if isinstance(expression.args["query_table"], exp.Table): 4221 query_table = f"TABLE {query_table}" 4222 4223 return self.func( 4224 "VECTOR_SEARCH", 4225 this_sql, 4226 expression.args.get("column_to_search"), 4227 query_table, 4228 expression.args.get("query_column_to_search"), 4229 expression.args.get("top_k"), 4230 expression.args.get("distance_type"), 4231 expression.args.get("options"), 4232 ) 4233 4234 def forin_sql(self, expression: exp.ForIn) -> str: 4235 this = self.sql(expression, "this") 4236 expression_sql = self.sql(expression, "expression") 4237 return f"FOR {this} DO {expression_sql}" 4238 4239 def refresh_sql(self, expression: exp.Refresh) -> str: 4240 this = self.sql(expression, "this") 4241 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4242 return f"REFRESH {table}{this}" 4243 4244 def toarray_sql(self, expression: exp.ToArray) -> str: 4245 arg = expression.this 4246 if not arg.type: 4247 from sqlglot.optimizer.annotate_types import annotate_types 4248 4249 arg = annotate_types(arg, dialect=self.dialect) 4250 4251 if arg.is_type(exp.DataType.Type.ARRAY): 4252 return self.sql(arg) 4253 4254 cond_for_null = arg.is_(exp.null()) 4255 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4256 4257 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4258 this = expression.this 4259 time_format = self.format_time(expression) 4260 4261 if time_format: 4262 return self.sql( 4263 exp.cast( 4264 exp.StrToTime(this=this, format=expression.args["format"]), 4265 exp.DataType.Type.TIME, 4266 ) 4267 ) 4268 4269 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4270 return self.sql(this) 4271 4272 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4273 4274 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4275 this = expression.this 4276 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4277 return self.sql(this) 4278 4279 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4280 4281 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4282 this = expression.this 4283 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4284 return self.sql(this) 4285 4286 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4287 4288 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4289 this = expression.this 4290 time_format = self.format_time(expression) 4291 4292 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4293 return self.sql( 4294 exp.cast( 4295 exp.StrToTime(this=this, format=expression.args["format"]), 4296 exp.DataType.Type.DATE, 4297 ) 4298 ) 4299 4300 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4301 return self.sql(this) 4302 4303 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4304 4305 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4306 return self.sql( 4307 exp.func( 4308 "DATEDIFF", 4309 expression.this, 4310 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4311 "day", 4312 ) 4313 ) 4314 4315 def lastday_sql(self, expression: exp.LastDay) -> str: 4316 if self.LAST_DAY_SUPPORTS_DATE_PART: 4317 return self.function_fallback_sql(expression) 4318 4319 unit = expression.text("unit") 4320 if unit and unit != "MONTH": 4321 self.unsupported("Date parts are not supported in LAST_DAY.") 4322 4323 return self.func("LAST_DAY", expression.this) 4324 4325 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4326 from sqlglot.dialects.dialect import unit_to_str 4327 4328 return self.func( 4329 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4330 ) 4331 4332 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4333 if self.CAN_IMPLEMENT_ARRAY_ANY: 4334 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4335 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4336 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4337 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4338 4339 from sqlglot.dialects import Dialect 4340 4341 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4342 if self.dialect.__class__ != Dialect: 4343 self.unsupported("ARRAY_ANY is unsupported") 4344 4345 return self.function_fallback_sql(expression) 4346 4347 def struct_sql(self, expression: exp.Struct) -> str: 4348 expression.set( 4349 "expressions", 4350 [ 4351 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4352 if isinstance(e, exp.PropertyEQ) 4353 else e 4354 for e in expression.expressions 4355 ], 4356 ) 4357 4358 return self.function_fallback_sql(expression) 4359 4360 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4361 low = self.sql(expression, "this") 4362 high = self.sql(expression, "expression") 4363 4364 return f"{low} TO {high}" 4365 4366 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4367 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4368 tables = f" {self.expressions(expression)}" 4369 4370 exists = " IF EXISTS" if expression.args.get("exists") else "" 4371 4372 on_cluster = self.sql(expression, "cluster") 4373 on_cluster = f" {on_cluster}" if on_cluster else "" 4374 4375 identity = self.sql(expression, "identity") 4376 identity = f" {identity} IDENTITY" if identity else "" 4377 4378 option = self.sql(expression, "option") 4379 option = f" {option}" if option else "" 4380 4381 partition = self.sql(expression, "partition") 4382 partition = f" {partition}" if partition else "" 4383 4384 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4385 4386 # This transpiles T-SQL's CONVERT function 4387 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4388 def convert_sql(self, expression: exp.Convert) -> str: 4389 to = expression.this 4390 value = expression.expression 4391 style = expression.args.get("style") 4392 safe = expression.args.get("safe") 4393 strict = expression.args.get("strict") 4394 4395 if not to or not value: 4396 return "" 4397 4398 # Retrieve length of datatype and override to default if not specified 4399 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4400 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4401 4402 transformed: t.Optional[exp.Expression] = None 4403 cast = exp.Cast if strict else exp.TryCast 4404 4405 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4406 if isinstance(style, exp.Literal) and style.is_int: 4407 from sqlglot.dialects.tsql import TSQL 4408 4409 style_value = style.name 4410 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4411 if not converted_style: 4412 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4413 4414 fmt = exp.Literal.string(converted_style) 4415 4416 if to.this == exp.DataType.Type.DATE: 4417 transformed = exp.StrToDate(this=value, format=fmt) 4418 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4419 transformed = exp.StrToTime(this=value, format=fmt) 4420 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4421 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4422 elif to.this == exp.DataType.Type.TEXT: 4423 transformed = exp.TimeToStr(this=value, format=fmt) 4424 4425 if not transformed: 4426 transformed = cast(this=value, to=to, safe=safe) 4427 4428 return self.sql(transformed) 4429 4430 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4431 this = expression.this 4432 if isinstance(this, exp.JSONPathWildcard): 4433 this = self.json_path_part(this) 4434 return f".{this}" if this else "" 4435 4436 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4437 return f".{this}" 4438 4439 this = self.json_path_part(this) 4440 return ( 4441 f"[{this}]" 4442 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4443 else f".{this}" 4444 ) 4445 4446 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4447 this = self.json_path_part(expression.this) 4448 return f"[{this}]" if this else "" 4449 4450 def _simplify_unless_literal(self, expression: E) -> E: 4451 if not isinstance(expression, exp.Literal): 4452 from sqlglot.optimizer.simplify import simplify 4453 4454 expression = simplify(expression, dialect=self.dialect) 4455 4456 return expression 4457 4458 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4459 this = expression.this 4460 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4461 self.unsupported( 4462 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4463 ) 4464 return self.sql(this) 4465 4466 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4467 # The first modifier here will be the one closest to the AggFunc's arg 4468 mods = sorted( 4469 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4470 key=lambda x: 0 4471 if isinstance(x, exp.HavingMax) 4472 else (1 if isinstance(x, exp.Order) else 2), 4473 ) 4474 4475 if mods: 4476 mod = mods[0] 4477 this = expression.__class__(this=mod.this.copy()) 4478 this.meta["inline"] = True 4479 mod.this.replace(this) 4480 return self.sql(expression.this) 4481 4482 agg_func = expression.find(exp.AggFunc) 4483 4484 if agg_func: 4485 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4486 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4487 4488 return f"{self.sql(expression, 'this')} {text}" 4489 4490 def _replace_line_breaks(self, string: str) -> str: 4491 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4492 if self.pretty: 4493 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4494 return string 4495 4496 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4497 option = self.sql(expression, "this") 4498 4499 if expression.expressions: 4500 upper = option.upper() 4501 4502 # Snowflake FILE_FORMAT options are separated by whitespace 4503 sep = " " if upper == "FILE_FORMAT" else ", " 4504 4505 # Databricks copy/format options do not set their list of values with EQ 4506 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4507 values = self.expressions(expression, flat=True, sep=sep) 4508 return f"{option}{op}({values})" 4509 4510 value = self.sql(expression, "expression") 4511 4512 if not value: 4513 return option 4514 4515 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4516 4517 return f"{option}{op}{value}" 4518 4519 def credentials_sql(self, expression: exp.Credentials) -> str: 4520 cred_expr = expression.args.get("credentials") 4521 if isinstance(cred_expr, exp.Literal): 4522 # Redshift case: CREDENTIALS <string> 4523 credentials = self.sql(expression, "credentials") 4524 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4525 else: 4526 # Snowflake case: CREDENTIALS = (...) 4527 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4528 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4529 4530 storage = self.sql(expression, "storage") 4531 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4532 4533 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4534 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4535 4536 iam_role = self.sql(expression, "iam_role") 4537 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4538 4539 region = self.sql(expression, "region") 4540 region = f" REGION {region}" if region else "" 4541 4542 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4543 4544 def copy_sql(self, expression: exp.Copy) -> str: 4545 this = self.sql(expression, "this") 4546 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4547 4548 credentials = self.sql(expression, "credentials") 4549 credentials = self.seg(credentials) if credentials else "" 4550 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4551 files = self.expressions(expression, key="files", flat=True) 4552 4553 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4554 params = self.expressions( 4555 expression, 4556 key="params", 4557 sep=sep, 4558 new_line=True, 4559 skip_last=True, 4560 skip_first=True, 4561 indent=self.COPY_PARAMS_ARE_WRAPPED, 4562 ) 4563 4564 if params: 4565 if self.COPY_PARAMS_ARE_WRAPPED: 4566 params = f" WITH ({params})" 4567 elif not self.pretty: 4568 params = f" {params}" 4569 4570 return f"COPY{this}{kind} {files}{credentials}{params}" 4571 4572 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4573 return "" 4574 4575 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4576 on_sql = "ON" if expression.args.get("on") else "OFF" 4577 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4578 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4579 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4580 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4581 4582 if filter_col or retention_period: 4583 on_sql = self.func("ON", filter_col, retention_period) 4584 4585 return f"DATA_DELETION={on_sql}" 4586 4587 def maskingpolicycolumnconstraint_sql( 4588 self, expression: exp.MaskingPolicyColumnConstraint 4589 ) -> str: 4590 this = self.sql(expression, "this") 4591 expressions = self.expressions(expression, flat=True) 4592 expressions = f" USING ({expressions})" if expressions else "" 4593 return f"MASKING POLICY {this}{expressions}" 4594 4595 def gapfill_sql(self, expression: exp.GapFill) -> str: 4596 this = self.sql(expression, "this") 4597 this = f"TABLE {this}" 4598 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4599 4600 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4601 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4602 4603 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4604 this = self.sql(expression, "this") 4605 expr = expression.expression 4606 4607 if isinstance(expr, exp.Func): 4608 # T-SQL's CLR functions are case sensitive 4609 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4610 else: 4611 expr = self.sql(expression, "expression") 4612 4613 return self.scope_resolution(expr, this) 4614 4615 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4616 if self.PARSE_JSON_NAME is None: 4617 return self.sql(expression.this) 4618 4619 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4620 4621 def rand_sql(self, expression: exp.Rand) -> str: 4622 lower = self.sql(expression, "lower") 4623 upper = self.sql(expression, "upper") 4624 4625 if lower and upper: 4626 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4627 return self.func("RAND", expression.this) 4628 4629 def changes_sql(self, expression: exp.Changes) -> str: 4630 information = self.sql(expression, "information") 4631 information = f"INFORMATION => {information}" 4632 at_before = self.sql(expression, "at_before") 4633 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4634 end = self.sql(expression, "end") 4635 end = f"{self.seg('')}{end}" if end else "" 4636 4637 return f"CHANGES ({information}){at_before}{end}" 4638 4639 def pad_sql(self, expression: exp.Pad) -> str: 4640 prefix = "L" if expression.args.get("is_left") else "R" 4641 4642 fill_pattern = self.sql(expression, "fill_pattern") or None 4643 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4644 fill_pattern = "' '" 4645 4646 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4647 4648 def summarize_sql(self, expression: exp.Summarize) -> str: 4649 table = " TABLE" if expression.args.get("table") else "" 4650 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4651 4652 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4653 generate_series = exp.GenerateSeries(**expression.args) 4654 4655 parent = expression.parent 4656 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4657 parent = parent.parent 4658 4659 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4660 return self.sql(exp.Unnest(expressions=[generate_series])) 4661 4662 if isinstance(parent, exp.Select): 4663 self.unsupported("GenerateSeries projection unnesting is not supported.") 4664 4665 return self.sql(generate_series) 4666 4667 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4668 exprs = expression.expressions 4669 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4670 if len(exprs) == 0: 4671 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4672 else: 4673 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4674 else: 4675 rhs = self.expressions(expression) # type: ignore 4676 4677 return self.func(name, expression.this, rhs or None) 4678 4679 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4680 if self.SUPPORTS_CONVERT_TIMEZONE: 4681 return self.function_fallback_sql(expression) 4682 4683 source_tz = expression.args.get("source_tz") 4684 target_tz = expression.args.get("target_tz") 4685 timestamp = expression.args.get("timestamp") 4686 4687 if source_tz and timestamp: 4688 timestamp = exp.AtTimeZone( 4689 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4690 ) 4691 4692 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4693 4694 return self.sql(expr) 4695 4696 def json_sql(self, expression: exp.JSON) -> str: 4697 this = self.sql(expression, "this") 4698 this = f" {this}" if this else "" 4699 4700 _with = expression.args.get("with") 4701 4702 if _with is None: 4703 with_sql = "" 4704 elif not _with: 4705 with_sql = " WITHOUT" 4706 else: 4707 with_sql = " WITH" 4708 4709 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4710 4711 return f"JSON{this}{with_sql}{unique_sql}" 4712 4713 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4714 def _generate_on_options(arg: t.Any) -> str: 4715 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4716 4717 path = self.sql(expression, "path") 4718 returning = self.sql(expression, "returning") 4719 returning = f" RETURNING {returning}" if returning else "" 4720 4721 on_condition = self.sql(expression, "on_condition") 4722 on_condition = f" {on_condition}" if on_condition else "" 4723 4724 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4725 4726 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4727 else_ = "ELSE " if expression.args.get("else_") else "" 4728 condition = self.sql(expression, "expression") 4729 condition = f"WHEN {condition} THEN " if condition else else_ 4730 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4731 return f"{condition}{insert}" 4732 4733 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4734 kind = self.sql(expression, "kind") 4735 expressions = self.seg(self.expressions(expression, sep=" ")) 4736 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4737 return res 4738 4739 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4740 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4741 empty = expression.args.get("empty") 4742 empty = ( 4743 f"DEFAULT {empty} ON EMPTY" 4744 if isinstance(empty, exp.Expression) 4745 else self.sql(expression, "empty") 4746 ) 4747 4748 error = expression.args.get("error") 4749 error = ( 4750 f"DEFAULT {error} ON ERROR" 4751 if isinstance(error, exp.Expression) 4752 else self.sql(expression, "error") 4753 ) 4754 4755 if error and empty: 4756 error = ( 4757 f"{empty} {error}" 4758 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4759 else f"{error} {empty}" 4760 ) 4761 empty = "" 4762 4763 null = self.sql(expression, "null") 4764 4765 return f"{empty}{error}{null}" 4766 4767 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4768 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4769 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4770 4771 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4772 this = self.sql(expression, "this") 4773 path = self.sql(expression, "path") 4774 4775 passing = self.expressions(expression, "passing") 4776 passing = f" PASSING {passing}" if passing else "" 4777 4778 on_condition = self.sql(expression, "on_condition") 4779 on_condition = f" {on_condition}" if on_condition else "" 4780 4781 path = f"{path}{passing}{on_condition}" 4782 4783 return self.func("JSON_EXISTS", this, path) 4784 4785 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4786 array_agg = self.function_fallback_sql(expression) 4787 4788 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4789 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4790 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4791 parent = expression.parent 4792 if isinstance(parent, exp.Filter): 4793 parent_cond = parent.expression.this 4794 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4795 else: 4796 this = expression.this 4797 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4798 if this.find(exp.Column): 4799 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4800 this_sql = ( 4801 self.expressions(this) 4802 if isinstance(this, exp.Distinct) 4803 else self.sql(expression, "this") 4804 ) 4805 4806 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4807 4808 return array_agg 4809 4810 def apply_sql(self, expression: exp.Apply) -> str: 4811 this = self.sql(expression, "this") 4812 expr = self.sql(expression, "expression") 4813 4814 return f"{this} APPLY({expr})" 4815 4816 def _grant_or_revoke_sql( 4817 self, 4818 expression: exp.Grant | exp.Revoke, 4819 keyword: str, 4820 preposition: str, 4821 grant_option_prefix: str = "", 4822 grant_option_suffix: str = "", 4823 ) -> str: 4824 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4825 4826 kind = self.sql(expression, "kind") 4827 kind = f" {kind}" if kind else "" 4828 4829 securable = self.sql(expression, "securable") 4830 securable = f" {securable}" if securable else "" 4831 4832 principals = self.expressions(expression, key="principals", flat=True) 4833 4834 if not expression.args.get("grant_option"): 4835 grant_option_prefix = grant_option_suffix = "" 4836 4837 # cascade for revoke only 4838 cascade = self.sql(expression, "cascade") 4839 cascade = f" {cascade}" if cascade else "" 4840 4841 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 4842 4843 def grant_sql(self, expression: exp.Grant) -> str: 4844 return self._grant_or_revoke_sql( 4845 expression, 4846 keyword="GRANT", 4847 preposition="TO", 4848 grant_option_suffix=" WITH GRANT OPTION", 4849 ) 4850 4851 def revoke_sql(self, expression: exp.Revoke) -> str: 4852 return self._grant_or_revoke_sql( 4853 expression, 4854 keyword="REVOKE", 4855 preposition="FROM", 4856 grant_option_prefix="GRANT OPTION FOR ", 4857 ) 4858 4859 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4860 this = self.sql(expression, "this") 4861 columns = self.expressions(expression, flat=True) 4862 columns = f"({columns})" if columns else "" 4863 4864 return f"{this}{columns}" 4865 4866 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4867 this = self.sql(expression, "this") 4868 4869 kind = self.sql(expression, "kind") 4870 kind = f"{kind} " if kind else "" 4871 4872 return f"{kind}{this}" 4873 4874 def columns_sql(self, expression: exp.Columns): 4875 func = self.function_fallback_sql(expression) 4876 if expression.args.get("unpack"): 4877 func = f"*{func}" 4878 4879 return func 4880 4881 def overlay_sql(self, expression: exp.Overlay): 4882 this = self.sql(expression, "this") 4883 expr = self.sql(expression, "expression") 4884 from_sql = self.sql(expression, "from") 4885 for_sql = self.sql(expression, "for") 4886 for_sql = f" FOR {for_sql}" if for_sql else "" 4887 4888 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4889 4890 @unsupported_args("format") 4891 def todouble_sql(self, expression: exp.ToDouble) -> str: 4892 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4893 4894 def string_sql(self, expression: exp.String) -> str: 4895 this = expression.this 4896 zone = expression.args.get("zone") 4897 4898 if zone: 4899 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4900 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4901 # set for source_tz to transpile the time conversion before the STRING cast 4902 this = exp.ConvertTimezone( 4903 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4904 ) 4905 4906 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4907 4908 def median_sql(self, expression: exp.Median): 4909 if not self.SUPPORTS_MEDIAN: 4910 return self.sql( 4911 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4912 ) 4913 4914 return self.function_fallback_sql(expression) 4915 4916 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4917 filler = self.sql(expression, "this") 4918 filler = f" {filler}" if filler else "" 4919 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4920 return f"TRUNCATE{filler} {with_count}" 4921 4922 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4923 if self.SUPPORTS_UNIX_SECONDS: 4924 return self.function_fallback_sql(expression) 4925 4926 start_ts = exp.cast( 4927 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4928 ) 4929 4930 return self.sql( 4931 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4932 ) 4933 4934 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4935 dim = expression.expression 4936 4937 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4938 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4939 if not (dim.is_int and dim.name == "1"): 4940 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4941 dim = None 4942 4943 # If dimension is required but not specified, default initialize it 4944 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4945 dim = exp.Literal.number(1) 4946 4947 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4948 4949 def attach_sql(self, expression: exp.Attach) -> str: 4950 this = self.sql(expression, "this") 4951 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4952 expressions = self.expressions(expression) 4953 expressions = f" ({expressions})" if expressions else "" 4954 4955 return f"ATTACH{exists_sql} {this}{expressions}" 4956 4957 def detach_sql(self, expression: exp.Detach) -> str: 4958 this = self.sql(expression, "this") 4959 # the DATABASE keyword is required if IF EXISTS is set 4960 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4961 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4962 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4963 4964 return f"DETACH{exists_sql} {this}" 4965 4966 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4967 this = self.sql(expression, "this") 4968 value = self.sql(expression, "expression") 4969 value = f" {value}" if value else "" 4970 return f"{this}{value}" 4971 4972 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4973 return ( 4974 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4975 ) 4976 4977 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4978 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4979 encode = f"{encode} {self.sql(expression, 'this')}" 4980 4981 properties = expression.args.get("properties") 4982 if properties: 4983 encode = f"{encode} {self.properties(properties)}" 4984 4985 return encode 4986 4987 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4988 this = self.sql(expression, "this") 4989 include = f"INCLUDE {this}" 4990 4991 column_def = self.sql(expression, "column_def") 4992 if column_def: 4993 include = f"{include} {column_def}" 4994 4995 alias = self.sql(expression, "alias") 4996 if alias: 4997 include = f"{include} AS {alias}" 4998 4999 return include 5000 5001 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5002 name = f"NAME {self.sql(expression, 'this')}" 5003 return self.func("XMLELEMENT", name, *expression.expressions) 5004 5005 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5006 this = self.sql(expression, "this") 5007 expr = self.sql(expression, "expression") 5008 expr = f"({expr})" if expr else "" 5009 return f"{this}{expr}" 5010 5011 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5012 partitions = self.expressions(expression, "partition_expressions") 5013 create = self.expressions(expression, "create_expressions") 5014 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5015 5016 def partitionbyrangepropertydynamic_sql( 5017 self, expression: exp.PartitionByRangePropertyDynamic 5018 ) -> str: 5019 start = self.sql(expression, "start") 5020 end = self.sql(expression, "end") 5021 5022 every = expression.args["every"] 5023 if isinstance(every, exp.Interval) and every.this.is_string: 5024 every.this.replace(exp.Literal.number(every.name)) 5025 5026 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5027 5028 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5029 name = self.sql(expression, "this") 5030 values = self.expressions(expression, flat=True) 5031 5032 return f"NAME {name} VALUE {values}" 5033 5034 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5035 kind = self.sql(expression, "kind") 5036 sample = self.sql(expression, "sample") 5037 return f"SAMPLE {sample} {kind}" 5038 5039 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5040 kind = self.sql(expression, "kind") 5041 option = self.sql(expression, "option") 5042 option = f" {option}" if option else "" 5043 this = self.sql(expression, "this") 5044 this = f" {this}" if this else "" 5045 columns = self.expressions(expression) 5046 columns = f" {columns}" if columns else "" 5047 return f"{kind}{option} STATISTICS{this}{columns}" 5048 5049 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5050 this = self.sql(expression, "this") 5051 columns = self.expressions(expression) 5052 inner_expression = self.sql(expression, "expression") 5053 inner_expression = f" {inner_expression}" if inner_expression else "" 5054 update_options = self.sql(expression, "update_options") 5055 update_options = f" {update_options} UPDATE" if update_options else "" 5056 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5057 5058 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5059 kind = self.sql(expression, "kind") 5060 kind = f" {kind}" if kind else "" 5061 return f"DELETE{kind} STATISTICS" 5062 5063 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5064 inner_expression = self.sql(expression, "expression") 5065 return f"LIST CHAINED ROWS{inner_expression}" 5066 5067 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5068 kind = self.sql(expression, "kind") 5069 this = self.sql(expression, "this") 5070 this = f" {this}" if this else "" 5071 inner_expression = self.sql(expression, "expression") 5072 return f"VALIDATE {kind}{this}{inner_expression}" 5073 5074 def analyze_sql(self, expression: exp.Analyze) -> str: 5075 options = self.expressions(expression, key="options", sep=" ") 5076 options = f" {options}" if options else "" 5077 kind = self.sql(expression, "kind") 5078 kind = f" {kind}" if kind else "" 5079 this = self.sql(expression, "this") 5080 this = f" {this}" if this else "" 5081 mode = self.sql(expression, "mode") 5082 mode = f" {mode}" if mode else "" 5083 properties = self.sql(expression, "properties") 5084 properties = f" {properties}" if properties else "" 5085 partition = self.sql(expression, "partition") 5086 partition = f" {partition}" if partition else "" 5087 inner_expression = self.sql(expression, "expression") 5088 inner_expression = f" {inner_expression}" if inner_expression else "" 5089 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5090 5091 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5092 this = self.sql(expression, "this") 5093 namespaces = self.expressions(expression, key="namespaces") 5094 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5095 passing = self.expressions(expression, key="passing") 5096 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5097 columns = self.expressions(expression, key="columns") 5098 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5099 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5100 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5101 5102 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5103 this = self.sql(expression, "this") 5104 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5105 5106 def export_sql(self, expression: exp.Export) -> str: 5107 this = self.sql(expression, "this") 5108 connection = self.sql(expression, "connection") 5109 connection = f"WITH CONNECTION {connection} " if connection else "" 5110 options = self.sql(expression, "options") 5111 return f"EXPORT DATA {connection}{options} AS {this}" 5112 5113 def declare_sql(self, expression: exp.Declare) -> str: 5114 return f"DECLARE {self.expressions(expression, flat=True)}" 5115 5116 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5117 variable = self.sql(expression, "this") 5118 default = self.sql(expression, "default") 5119 default = f" = {default}" if default else "" 5120 5121 kind = self.sql(expression, "kind") 5122 if isinstance(expression.args.get("kind"), exp.Schema): 5123 kind = f"TABLE {kind}" 5124 5125 return f"{variable} AS {kind}{default}" 5126 5127 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5128 kind = self.sql(expression, "kind") 5129 this = self.sql(expression, "this") 5130 set = self.sql(expression, "expression") 5131 using = self.sql(expression, "using") 5132 using = f" USING {using}" if using else "" 5133 5134 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5135 5136 return f"{kind_sql} {this} SET {set}{using}" 5137 5138 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5139 params = self.expressions(expression, key="params", flat=True) 5140 return self.func(expression.name, *expression.expressions) + f"({params})" 5141 5142 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5143 return self.func(expression.name, *expression.expressions) 5144 5145 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5146 return self.anonymousaggfunc_sql(expression) 5147 5148 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5149 return self.parameterizedagg_sql(expression) 5150 5151 def show_sql(self, expression: exp.Show) -> str: 5152 self.unsupported("Unsupported SHOW statement") 5153 return "" 5154 5155 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5156 # Snowflake GET/PUT statements: 5157 # PUT <file> <internalStage> <properties> 5158 # GET <internalStage> <file> <properties> 5159 props = expression.args.get("properties") 5160 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5161 this = self.sql(expression, "this") 5162 target = self.sql(expression, "target") 5163 5164 if isinstance(expression, exp.Put): 5165 return f"PUT {this} {target}{props_sql}" 5166 else: 5167 return f"GET {target} {this}{props_sql}" 5168 5169 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5170 this = self.sql(expression, "this") 5171 expr = self.sql(expression, "expression") 5172 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5173 return f"TRANSLATE({this} USING {expr}{with_error})" 5174 5175 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5176 if self.SUPPORTS_DECODE_CASE: 5177 return self.func("DECODE", *expression.expressions) 5178 5179 expression, *expressions = expression.expressions 5180 5181 ifs = [] 5182 for search, result in zip(expressions[::2], expressions[1::2]): 5183 if isinstance(search, exp.Literal): 5184 ifs.append(exp.If(this=expression.eq(search), true=result)) 5185 elif isinstance(search, exp.Null): 5186 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5187 else: 5188 if isinstance(search, exp.Binary): 5189 search = exp.paren(search) 5190 5191 cond = exp.or_( 5192 expression.eq(search), 5193 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5194 copy=False, 5195 ) 5196 ifs.append(exp.If(this=cond, true=result)) 5197 5198 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5199 return self.sql(case) 5200 5201 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5202 this = self.sql(expression, "this") 5203 this = self.seg(this, sep="") 5204 dimensions = self.expressions( 5205 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5206 ) 5207 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5208 metrics = self.expressions( 5209 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5210 ) 5211 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5212 where = self.sql(expression, "where") 5213 where = self.seg(f"WHERE {where}") if where else "" 5214 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}" 5215 5216 def getextract_sql(self, expression: exp.GetExtract) -> str: 5217 this = expression.this 5218 expr = expression.expression 5219 5220 if not this.type or not expression.type: 5221 from sqlglot.optimizer.annotate_types import annotate_types 5222 5223 this = annotate_types(this, dialect=self.dialect) 5224 5225 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5226 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5227 5228 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5229 5230 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5231 return self.sql( 5232 exp.DateAdd( 5233 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5234 expression=expression.this, 5235 unit=exp.var("DAY"), 5236 ) 5237 ) 5238 5239 def space_sql(self: Generator, expression: exp.Space) -> str: 5240 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5241 5242 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5243 return f"BUILD {self.sql(expression, 'this')}" 5244 5245 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5246 method = self.sql(expression, "method") 5247 kind = expression.args.get("kind") 5248 if not kind: 5249 return f"REFRESH {method}" 5250 5251 every = self.sql(expression, "every") 5252 unit = self.sql(expression, "unit") 5253 every = f" EVERY {every} {unit}" if every else "" 5254 starts = self.sql(expression, "starts") 5255 starts = f" STARTS {starts}" if starts else "" 5256 5257 return f"REFRESH {method} ON {kind}{every}{starts}"
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: Union[str, Tuple[str, str]]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
30def unsupported_args( 31 *args: t.Union[str, t.Tuple[str, str]], 32) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 33 """ 34 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 35 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 36 """ 37 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 38 for arg in args: 39 if isinstance(arg, str): 40 diagnostic_by_arg[arg] = None 41 else: 42 diagnostic_by_arg[arg[0]] = arg[1] 43 44 def decorator(func: GeneratorMethod) -> GeneratorMethod: 45 @wraps(func) 46 def _func(generator: G, expression: E) -> str: 47 expression_name = expression.__class__.__name__ 48 dialect_name = generator.dialect.__class__.__name__ 49 50 for arg_name, diagnostic in diagnostic_by_arg.items(): 51 if expression.args.get(arg_name): 52 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 53 arg_name, expression_name, dialect_name 54 ) 55 generator.unsupported(diagnostic) 56 57 return func(generator, expression) 58 59 return _func 60 61 return decorator
Decorator that can be used to mark certain args of an Expression subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
class
Generator:
75class Generator(metaclass=_Generator): 76 """ 77 Generator converts a given syntax tree to the corresponding SQL string. 78 79 Args: 80 pretty: Whether to format the produced SQL string. 81 Default: False. 82 identify: Determines when an identifier should be quoted. Possible values are: 83 False (default): Never quote, except in cases where it's mandatory by the dialect. 84 True or 'always': Always quote. 85 'safe': Only quote identifiers that are case insensitive. 86 normalize: Whether to normalize identifiers to lowercase. 87 Default: False. 88 pad: The pad size in a formatted string. For example, this affects the indentation of 89 a projection in a query, relative to its nesting level. 90 Default: 2. 91 indent: The indentation size in a formatted string. For example, this affects the 92 indentation of subqueries and filters under a `WHERE` clause. 93 Default: 2. 94 normalize_functions: How to normalize function names. Possible values are: 95 "upper" or True (default): Convert names to uppercase. 96 "lower": Convert names to lowercase. 97 False: Disables function name normalization. 98 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 99 Default ErrorLevel.WARN. 100 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 101 This is only relevant if unsupported_level is ErrorLevel.RAISE. 102 Default: 3 103 leading_comma: Whether the comma is leading or trailing in select expressions. 104 This is only relevant when generating in pretty mode. 105 Default: False 106 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 107 The default is on the smaller end because the length only represents a segment and not the true 108 line length. 109 Default: 80 110 comments: Whether to preserve comments in the output SQL code. 111 Default: True 112 """ 113 114 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 115 **JSON_PATH_PART_TRANSFORMS, 116 exp.AllowedValuesProperty: lambda self, 117 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 118 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 119 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 120 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 121 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 122 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 123 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 124 exp.CaseSpecificColumnConstraint: lambda _, 125 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 126 exp.Ceil: lambda self, e: self.ceil_floor(e), 127 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 128 exp.CharacterSetProperty: lambda self, 129 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 130 exp.ClusteredColumnConstraint: lambda self, 131 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 132 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 133 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 134 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 135 exp.ConvertToCharset: lambda self, e: self.func( 136 "CONVERT", e.this, e.args["dest"], e.args.get("source") 137 ), 138 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 139 exp.CredentialsProperty: lambda self, 140 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 141 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 142 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 143 exp.DynamicProperty: lambda *_: "DYNAMIC", 144 exp.EmptyProperty: lambda *_: "EMPTY", 145 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 146 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 147 exp.EphemeralColumnConstraint: lambda self, 148 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 149 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 150 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 151 exp.Except: lambda self, e: self.set_operations(e), 152 exp.ExternalProperty: lambda *_: "EXTERNAL", 153 exp.Floor: lambda self, e: self.ceil_floor(e), 154 exp.Get: lambda self, e: self.get_put_sql(e), 155 exp.GlobalProperty: lambda *_: "GLOBAL", 156 exp.HeapProperty: lambda *_: "HEAP", 157 exp.IcebergProperty: lambda *_: "ICEBERG", 158 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 159 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 160 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 161 exp.Intersect: lambda self, e: self.set_operations(e), 162 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 163 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 164 exp.LanguageProperty: lambda self, e: self.naked_property(e), 165 exp.LocationProperty: lambda self, e: self.naked_property(e), 166 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 167 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 168 exp.NonClusteredColumnConstraint: lambda self, 169 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 170 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 171 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 172 exp.OnCommitProperty: lambda _, 173 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 174 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 175 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 176 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 177 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 178 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 179 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 180 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 181 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 182 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 183 exp.ProjectionPolicyColumnConstraint: lambda self, 184 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 185 exp.Put: lambda self, e: self.get_put_sql(e), 186 exp.RemoteWithConnectionModelProperty: lambda self, 187 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 188 exp.ReturnsProperty: lambda self, e: ( 189 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 190 ), 191 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 192 exp.SecureProperty: lambda *_: "SECURE", 193 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 194 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 195 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 196 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 197 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 198 exp.SqlReadWriteProperty: lambda _, e: e.name, 199 exp.SqlSecurityProperty: lambda _, 200 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 201 exp.StabilityProperty: lambda _, e: e.name, 202 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 203 exp.StreamingTableProperty: lambda *_: "STREAMING", 204 exp.StrictProperty: lambda *_: "STRICT", 205 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 206 exp.TableColumn: lambda self, e: self.sql(e.this), 207 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 208 exp.TemporaryProperty: lambda *_: "TEMPORARY", 209 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 210 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 211 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 212 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 213 exp.TransientProperty: lambda *_: "TRANSIENT", 214 exp.Union: lambda self, e: self.set_operations(e), 215 exp.UnloggedProperty: lambda *_: "UNLOGGED", 216 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 217 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 218 exp.Uuid: lambda *_: "UUID()", 219 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 220 exp.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 UNSUPPORTED_TYPES: set[exp.DataType.Type] = set() 516 517 TIME_PART_SINGULARS = { 518 "MICROSECONDS": "MICROSECOND", 519 "SECONDS": "SECOND", 520 "MINUTES": "MINUTE", 521 "HOURS": "HOUR", 522 "DAYS": "DAY", 523 "WEEKS": "WEEK", 524 "MONTHS": "MONTH", 525 "QUARTERS": "QUARTER", 526 "YEARS": "YEAR", 527 } 528 529 AFTER_HAVING_MODIFIER_TRANSFORMS = { 530 "cluster": lambda self, e: self.sql(e, "cluster"), 531 "distribute": lambda self, e: self.sql(e, "distribute"), 532 "sort": lambda self, e: self.sql(e, "sort"), 533 "windows": lambda self, e: ( 534 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 535 if e.args.get("windows") 536 else "" 537 ), 538 "qualify": lambda self, e: self.sql(e, "qualify"), 539 } 540 541 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 542 543 STRUCT_DELIMITER = ("<", ">") 544 545 PARAMETER_TOKEN = "@" 546 NAMED_PLACEHOLDER_TOKEN = ":" 547 548 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 549 550 PROPERTIES_LOCATION = { 551 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 552 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 553 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 556 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 557 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 559 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 560 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 561 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 562 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 566 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 568 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 569 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 571 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 573 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 575 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 579 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 580 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 581 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 582 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 583 exp.HeapProperty: exp.Properties.Location.POST_WITH, 584 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 586 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 589 exp.JournalProperty: exp.Properties.Location.POST_NAME, 590 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 593 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 594 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 595 exp.LogProperty: exp.Properties.Location.POST_NAME, 596 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 597 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 598 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 599 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 600 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 601 exp.Order: exp.Properties.Location.POST_SCHEMA, 602 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 604 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 606 exp.Property: exp.Properties.Location.POST_WITH, 607 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 611 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 615 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 617 exp.Set: exp.Properties.Location.POST_SCHEMA, 618 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.SetProperty: exp.Properties.Location.POST_CREATE, 620 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 622 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 623 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 626 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 629 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 630 exp.Tags: exp.Properties.Location.POST_WITH, 631 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 632 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 634 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 636 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 637 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 640 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 641 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 642 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 643 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 644 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 645 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 646 } 647 648 # Keywords that can't be used as unquoted identifier names 649 RESERVED_KEYWORDS: t.Set[str] = set() 650 651 # Expressions whose comments are separated from them for better formatting 652 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 653 exp.Command, 654 exp.Create, 655 exp.Describe, 656 exp.Delete, 657 exp.Drop, 658 exp.From, 659 exp.Insert, 660 exp.Join, 661 exp.MultitableInserts, 662 exp.Order, 663 exp.Group, 664 exp.Having, 665 exp.Select, 666 exp.SetOperation, 667 exp.Update, 668 exp.Where, 669 exp.With, 670 ) 671 672 # Expressions that should not have their comments generated in maybe_comment 673 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 674 exp.Binary, 675 exp.SetOperation, 676 ) 677 678 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 679 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 680 exp.Column, 681 exp.Literal, 682 exp.Neg, 683 exp.Paren, 684 ) 685 686 PARAMETERIZABLE_TEXT_TYPES = { 687 exp.DataType.Type.NVARCHAR, 688 exp.DataType.Type.VARCHAR, 689 exp.DataType.Type.CHAR, 690 exp.DataType.Type.NCHAR, 691 } 692 693 # Expressions that need to have all CTEs under them bubbled up to them 694 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 695 696 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 697 698 SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE 699 700 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 701 702 __slots__ = ( 703 "pretty", 704 "identify", 705 "normalize", 706 "pad", 707 "_indent", 708 "normalize_functions", 709 "unsupported_level", 710 "max_unsupported", 711 "leading_comma", 712 "max_text_width", 713 "comments", 714 "dialect", 715 "unsupported_messages", 716 "_escaped_quote_end", 717 "_escaped_identifier_end", 718 "_next_name", 719 "_identifier_start", 720 "_identifier_end", 721 "_quote_json_path_key_using_brackets", 722 ) 723 724 def __init__( 725 self, 726 pretty: t.Optional[bool] = None, 727 identify: str | bool = False, 728 normalize: bool = False, 729 pad: int = 2, 730 indent: int = 2, 731 normalize_functions: t.Optional[str | bool] = None, 732 unsupported_level: ErrorLevel = ErrorLevel.WARN, 733 max_unsupported: int = 3, 734 leading_comma: bool = False, 735 max_text_width: int = 80, 736 comments: bool = True, 737 dialect: DialectType = None, 738 ): 739 import sqlglot 740 from sqlglot.dialects import Dialect 741 742 self.pretty = pretty if pretty is not None else sqlglot.pretty 743 self.identify = identify 744 self.normalize = normalize 745 self.pad = pad 746 self._indent = indent 747 self.unsupported_level = unsupported_level 748 self.max_unsupported = max_unsupported 749 self.leading_comma = leading_comma 750 self.max_text_width = max_text_width 751 self.comments = comments 752 self.dialect = Dialect.get_or_raise(dialect) 753 754 # This is both a Dialect property and a Generator argument, so we prioritize the latter 755 self.normalize_functions = ( 756 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 757 ) 758 759 self.unsupported_messages: t.List[str] = [] 760 self._escaped_quote_end: str = ( 761 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 762 ) 763 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 764 765 self._next_name = name_sequence("_t") 766 767 self._identifier_start = self.dialect.IDENTIFIER_START 768 self._identifier_end = self.dialect.IDENTIFIER_END 769 770 self._quote_json_path_key_using_brackets = True 771 772 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 773 """ 774 Generates the SQL string corresponding to the given syntax tree. 775 776 Args: 777 expression: The syntax tree. 778 copy: Whether to copy the expression. The generator performs mutations so 779 it is safer to copy. 780 781 Returns: 782 The SQL string corresponding to `expression`. 783 """ 784 if copy: 785 expression = expression.copy() 786 787 expression = self.preprocess(expression) 788 789 self.unsupported_messages = [] 790 sql = self.sql(expression).strip() 791 792 if self.pretty: 793 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 794 795 if self.unsupported_level == ErrorLevel.IGNORE: 796 return sql 797 798 if self.unsupported_level == ErrorLevel.WARN: 799 for msg in self.unsupported_messages: 800 logger.warning(msg) 801 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 802 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 803 804 return sql 805 806 def preprocess(self, expression: exp.Expression) -> exp.Expression: 807 """Apply generic preprocessing transformations to a given expression.""" 808 expression = self._move_ctes_to_top_level(expression) 809 810 if self.ENSURE_BOOLS: 811 from sqlglot.transforms import ensure_bools 812 813 expression = ensure_bools(expression) 814 815 return expression 816 817 def _move_ctes_to_top_level(self, expression: E) -> E: 818 if ( 819 not expression.parent 820 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 821 and any(node.parent is not expression for node in expression.find_all(exp.With)) 822 ): 823 from sqlglot.transforms import move_ctes_to_top_level 824 825 expression = move_ctes_to_top_level(expression) 826 return expression 827 828 def unsupported(self, message: str) -> None: 829 if self.unsupported_level == ErrorLevel.IMMEDIATE: 830 raise UnsupportedError(message) 831 self.unsupported_messages.append(message) 832 833 def sep(self, sep: str = " ") -> str: 834 return f"{sep.strip()}\n" if self.pretty else sep 835 836 def seg(self, sql: str, sep: str = " ") -> str: 837 return f"{self.sep(sep)}{sql}" 838 839 def sanitize_comment(self, comment: str) -> str: 840 comment = " " + comment if comment[0].strip() else comment 841 comment = comment + " " if comment[-1].strip() else comment 842 843 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 844 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 845 comment = comment.replace("*/", "* /") 846 847 return comment 848 849 def maybe_comment( 850 self, 851 sql: str, 852 expression: t.Optional[exp.Expression] = None, 853 comments: t.Optional[t.List[str]] = None, 854 separated: bool = False, 855 ) -> str: 856 comments = ( 857 ((expression and expression.comments) if comments is None else comments) # type: ignore 858 if self.comments 859 else None 860 ) 861 862 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 863 return sql 864 865 comments_sql = " ".join( 866 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 867 ) 868 869 if not comments_sql: 870 return sql 871 872 comments_sql = self._replace_line_breaks(comments_sql) 873 874 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 875 return ( 876 f"{self.sep()}{comments_sql}{sql}" 877 if not sql or sql[0].isspace() 878 else f"{comments_sql}{self.sep()}{sql}" 879 ) 880 881 return f"{sql} {comments_sql}" 882 883 def wrap(self, expression: exp.Expression | str) -> str: 884 this_sql = ( 885 self.sql(expression) 886 if isinstance(expression, exp.UNWRAPPED_QUERIES) 887 else self.sql(expression, "this") 888 ) 889 if not this_sql: 890 return "()" 891 892 this_sql = self.indent(this_sql, level=1, pad=0) 893 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 894 895 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 896 original = self.identify 897 self.identify = False 898 result = func(*args, **kwargs) 899 self.identify = original 900 return result 901 902 def normalize_func(self, name: str) -> str: 903 if self.normalize_functions == "upper" or self.normalize_functions is True: 904 return name.upper() 905 if self.normalize_functions == "lower": 906 return name.lower() 907 return name 908 909 def indent( 910 self, 911 sql: str, 912 level: int = 0, 913 pad: t.Optional[int] = None, 914 skip_first: bool = False, 915 skip_last: bool = False, 916 ) -> str: 917 if not self.pretty or not sql: 918 return sql 919 920 pad = self.pad if pad is None else pad 921 lines = sql.split("\n") 922 923 return "\n".join( 924 ( 925 line 926 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 927 else f"{' ' * (level * self._indent + pad)}{line}" 928 ) 929 for i, line in enumerate(lines) 930 ) 931 932 def sql( 933 self, 934 expression: t.Optional[str | exp.Expression], 935 key: t.Optional[str] = None, 936 comment: bool = True, 937 ) -> str: 938 if not expression: 939 return "" 940 941 if isinstance(expression, str): 942 return expression 943 944 if key: 945 value = expression.args.get(key) 946 if value: 947 return self.sql(value) 948 return "" 949 950 transform = self.TRANSFORMS.get(expression.__class__) 951 952 if callable(transform): 953 sql = transform(self, expression) 954 elif isinstance(expression, exp.Expression): 955 exp_handler_name = f"{expression.key}_sql" 956 957 if hasattr(self, exp_handler_name): 958 sql = getattr(self, exp_handler_name)(expression) 959 elif isinstance(expression, exp.Func): 960 sql = self.function_fallback_sql(expression) 961 elif isinstance(expression, exp.Property): 962 sql = self.property_sql(expression) 963 else: 964 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 965 else: 966 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 967 968 return self.maybe_comment(sql, expression) if self.comments and comment else sql 969 970 def uncache_sql(self, expression: exp.Uncache) -> str: 971 table = self.sql(expression, "this") 972 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 973 return f"UNCACHE TABLE{exists_sql} {table}" 974 975 def cache_sql(self, expression: exp.Cache) -> str: 976 lazy = " LAZY" if expression.args.get("lazy") else "" 977 table = self.sql(expression, "this") 978 options = expression.args.get("options") 979 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 980 sql = self.sql(expression, "expression") 981 sql = f" AS{self.sep()}{sql}" if sql else "" 982 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 983 return self.prepend_ctes(expression, sql) 984 985 def characterset_sql(self, expression: exp.CharacterSet) -> str: 986 if isinstance(expression.parent, exp.Cast): 987 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 988 default = "DEFAULT " if expression.args.get("default") else "" 989 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 990 991 def column_parts(self, expression: exp.Column) -> str: 992 return ".".join( 993 self.sql(part) 994 for part in ( 995 expression.args.get("catalog"), 996 expression.args.get("db"), 997 expression.args.get("table"), 998 expression.args.get("this"), 999 ) 1000 if part 1001 ) 1002 1003 def column_sql(self, expression: exp.Column) -> str: 1004 join_mark = " (+)" if expression.args.get("join_mark") else "" 1005 1006 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1007 join_mark = "" 1008 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1009 1010 return f"{self.column_parts(expression)}{join_mark}" 1011 1012 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1013 this = self.sql(expression, "this") 1014 this = f" {this}" if this else "" 1015 position = self.sql(expression, "position") 1016 return f"{position}{this}" 1017 1018 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1019 column = self.sql(expression, "this") 1020 kind = self.sql(expression, "kind") 1021 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1022 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1023 kind = f"{sep}{kind}" if kind else "" 1024 constraints = f" {constraints}" if constraints else "" 1025 position = self.sql(expression, "position") 1026 position = f" {position}" if position else "" 1027 1028 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1029 kind = "" 1030 1031 return f"{exists}{column}{kind}{constraints}{position}" 1032 1033 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1034 this = self.sql(expression, "this") 1035 kind_sql = self.sql(expression, "kind").strip() 1036 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1037 1038 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1039 this = self.sql(expression, "this") 1040 if expression.args.get("not_null"): 1041 persisted = " PERSISTED NOT NULL" 1042 elif expression.args.get("persisted"): 1043 persisted = " PERSISTED" 1044 else: 1045 persisted = "" 1046 1047 return f"AS {this}{persisted}" 1048 1049 def autoincrementcolumnconstraint_sql(self, _) -> str: 1050 return self.token_sql(TokenType.AUTO_INCREMENT) 1051 1052 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1053 if isinstance(expression.this, list): 1054 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1055 else: 1056 this = self.sql(expression, "this") 1057 1058 return f"COMPRESS {this}" 1059 1060 def generatedasidentitycolumnconstraint_sql( 1061 self, expression: exp.GeneratedAsIdentityColumnConstraint 1062 ) -> str: 1063 this = "" 1064 if expression.this is not None: 1065 on_null = " ON NULL" if expression.args.get("on_null") else "" 1066 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1067 1068 start = expression.args.get("start") 1069 start = f"START WITH {start}" if start else "" 1070 increment = expression.args.get("increment") 1071 increment = f" INCREMENT BY {increment}" if increment else "" 1072 minvalue = expression.args.get("minvalue") 1073 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1074 maxvalue = expression.args.get("maxvalue") 1075 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1076 cycle = expression.args.get("cycle") 1077 cycle_sql = "" 1078 1079 if cycle is not None: 1080 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1081 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1082 1083 sequence_opts = "" 1084 if start or increment or cycle_sql: 1085 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1086 sequence_opts = f" ({sequence_opts.strip()})" 1087 1088 expr = self.sql(expression, "expression") 1089 expr = f"({expr})" if expr else "IDENTITY" 1090 1091 return f"GENERATED{this} AS {expr}{sequence_opts}" 1092 1093 def generatedasrowcolumnconstraint_sql( 1094 self, expression: exp.GeneratedAsRowColumnConstraint 1095 ) -> str: 1096 start = "START" if expression.args.get("start") else "END" 1097 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1098 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1099 1100 def periodforsystemtimeconstraint_sql( 1101 self, expression: exp.PeriodForSystemTimeConstraint 1102 ) -> str: 1103 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1104 1105 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1106 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1107 1108 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1109 desc = expression.args.get("desc") 1110 if desc is not None: 1111 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1112 options = self.expressions(expression, key="options", flat=True, sep=" ") 1113 options = f" {options}" if options else "" 1114 return f"PRIMARY KEY{options}" 1115 1116 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1117 this = self.sql(expression, "this") 1118 this = f" {this}" if this else "" 1119 index_type = expression.args.get("index_type") 1120 index_type = f" USING {index_type}" if index_type else "" 1121 on_conflict = self.sql(expression, "on_conflict") 1122 on_conflict = f" {on_conflict}" if on_conflict else "" 1123 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1124 options = self.expressions(expression, key="options", flat=True, sep=" ") 1125 options = f" {options}" if options else "" 1126 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1127 1128 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1129 return self.sql(expression, "this") 1130 1131 def create_sql(self, expression: exp.Create) -> str: 1132 kind = self.sql(expression, "kind") 1133 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1134 properties = expression.args.get("properties") 1135 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1136 1137 this = self.createable_sql(expression, properties_locs) 1138 1139 properties_sql = "" 1140 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1141 exp.Properties.Location.POST_WITH 1142 ): 1143 props_ast = exp.Properties( 1144 expressions=[ 1145 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1146 *properties_locs[exp.Properties.Location.POST_WITH], 1147 ] 1148 ) 1149 props_ast.parent = expression 1150 properties_sql = self.sql(props_ast) 1151 1152 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1153 properties_sql = self.sep() + properties_sql 1154 elif not self.pretty: 1155 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1156 properties_sql = f" {properties_sql}" 1157 1158 begin = " BEGIN" if expression.args.get("begin") else "" 1159 end = " END" if expression.args.get("end") else "" 1160 1161 expression_sql = self.sql(expression, "expression") 1162 if expression_sql: 1163 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1164 1165 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1166 postalias_props_sql = "" 1167 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1168 postalias_props_sql = self.properties( 1169 exp.Properties( 1170 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1171 ), 1172 wrapped=False, 1173 ) 1174 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1175 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1176 1177 postindex_props_sql = "" 1178 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1179 postindex_props_sql = self.properties( 1180 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1181 wrapped=False, 1182 prefix=" ", 1183 ) 1184 1185 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1186 indexes = f" {indexes}" if indexes else "" 1187 index_sql = indexes + postindex_props_sql 1188 1189 replace = " OR REPLACE" if expression.args.get("replace") else "" 1190 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1191 unique = " UNIQUE" if expression.args.get("unique") else "" 1192 1193 clustered = expression.args.get("clustered") 1194 if clustered is None: 1195 clustered_sql = "" 1196 elif clustered: 1197 clustered_sql = " CLUSTERED COLUMNSTORE" 1198 else: 1199 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1200 1201 postcreate_props_sql = "" 1202 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1203 postcreate_props_sql = self.properties( 1204 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1205 sep=" ", 1206 prefix=" ", 1207 wrapped=False, 1208 ) 1209 1210 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1211 1212 postexpression_props_sql = "" 1213 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1214 postexpression_props_sql = self.properties( 1215 exp.Properties( 1216 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1217 ), 1218 sep=" ", 1219 prefix=" ", 1220 wrapped=False, 1221 ) 1222 1223 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1224 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1225 no_schema_binding = ( 1226 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1227 ) 1228 1229 clone = self.sql(expression, "clone") 1230 clone = f" {clone}" if clone else "" 1231 1232 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1233 properties_expression = f"{expression_sql}{properties_sql}" 1234 else: 1235 properties_expression = f"{properties_sql}{expression_sql}" 1236 1237 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1238 return self.prepend_ctes(expression, expression_sql) 1239 1240 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1241 start = self.sql(expression, "start") 1242 start = f"START WITH {start}" if start else "" 1243 increment = self.sql(expression, "increment") 1244 increment = f" INCREMENT BY {increment}" if increment else "" 1245 minvalue = self.sql(expression, "minvalue") 1246 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1247 maxvalue = self.sql(expression, "maxvalue") 1248 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1249 owned = self.sql(expression, "owned") 1250 owned = f" OWNED BY {owned}" if owned else "" 1251 1252 cache = expression.args.get("cache") 1253 if cache is None: 1254 cache_str = "" 1255 elif cache is True: 1256 cache_str = " CACHE" 1257 else: 1258 cache_str = f" CACHE {cache}" 1259 1260 options = self.expressions(expression, key="options", flat=True, sep=" ") 1261 options = f" {options}" if options else "" 1262 1263 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1264 1265 def clone_sql(self, expression: exp.Clone) -> str: 1266 this = self.sql(expression, "this") 1267 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1268 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1269 return f"{shallow}{keyword} {this}" 1270 1271 def describe_sql(self, expression: exp.Describe) -> str: 1272 style = expression.args.get("style") 1273 style = f" {style}" if style else "" 1274 partition = self.sql(expression, "partition") 1275 partition = f" {partition}" if partition else "" 1276 format = self.sql(expression, "format") 1277 format = f" {format}" if format else "" 1278 1279 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1280 1281 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1282 tag = self.sql(expression, "tag") 1283 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1284 1285 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1286 with_ = self.sql(expression, "with") 1287 if with_: 1288 sql = f"{with_}{self.sep()}{sql}" 1289 return sql 1290 1291 def with_sql(self, expression: exp.With) -> str: 1292 sql = self.expressions(expression, flat=True) 1293 recursive = ( 1294 "RECURSIVE " 1295 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1296 else "" 1297 ) 1298 search = self.sql(expression, "search") 1299 search = f" {search}" if search else "" 1300 1301 return f"WITH {recursive}{sql}{search}" 1302 1303 def cte_sql(self, expression: exp.CTE) -> str: 1304 alias = expression.args.get("alias") 1305 if alias: 1306 alias.add_comments(expression.pop_comments()) 1307 1308 alias_sql = self.sql(expression, "alias") 1309 1310 materialized = expression.args.get("materialized") 1311 if materialized is False: 1312 materialized = "NOT MATERIALIZED " 1313 elif materialized: 1314 materialized = "MATERIALIZED " 1315 1316 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1317 1318 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1319 alias = self.sql(expression, "this") 1320 columns = self.expressions(expression, key="columns", flat=True) 1321 columns = f"({columns})" if columns else "" 1322 1323 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1324 columns = "" 1325 self.unsupported("Named columns are not supported in table alias.") 1326 1327 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1328 alias = self._next_name() 1329 1330 return f"{alias}{columns}" 1331 1332 def bitstring_sql(self, expression: exp.BitString) -> str: 1333 this = self.sql(expression, "this") 1334 if self.dialect.BIT_START: 1335 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1336 return f"{int(this, 2)}" 1337 1338 def hexstring_sql( 1339 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1340 ) -> str: 1341 this = self.sql(expression, "this") 1342 is_integer_type = expression.args.get("is_integer") 1343 1344 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1345 not self.dialect.HEX_START and not binary_function_repr 1346 ): 1347 # Integer representation will be returned if: 1348 # - The read dialect treats the hex value as integer literal but not the write 1349 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1350 return f"{int(this, 16)}" 1351 1352 if not is_integer_type: 1353 # Read dialect treats the hex value as BINARY/BLOB 1354 if binary_function_repr: 1355 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1356 return self.func(binary_function_repr, exp.Literal.string(this)) 1357 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1358 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1359 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1360 1361 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1362 1363 def bytestring_sql(self, expression: exp.ByteString) -> str: 1364 this = self.sql(expression, "this") 1365 if self.dialect.BYTE_START: 1366 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1367 return this 1368 1369 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1370 this = self.sql(expression, "this") 1371 escape = expression.args.get("escape") 1372 1373 if self.dialect.UNICODE_START: 1374 escape_substitute = r"\\\1" 1375 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1376 else: 1377 escape_substitute = r"\\u\1" 1378 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1379 1380 if escape: 1381 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1382 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1383 else: 1384 escape_pattern = ESCAPED_UNICODE_RE 1385 escape_sql = "" 1386 1387 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1388 this = escape_pattern.sub(escape_substitute, this) 1389 1390 return f"{left_quote}{this}{right_quote}{escape_sql}" 1391 1392 def rawstring_sql(self, expression: exp.RawString) -> str: 1393 string = expression.this 1394 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1395 string = string.replace("\\", "\\\\") 1396 1397 string = self.escape_str(string, escape_backslash=False) 1398 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1399 1400 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1401 this = self.sql(expression, "this") 1402 specifier = self.sql(expression, "expression") 1403 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1404 return f"{this}{specifier}" 1405 1406 def datatype_sql(self, expression: exp.DataType) -> str: 1407 nested = "" 1408 values = "" 1409 interior = self.expressions(expression, flat=True) 1410 1411 type_value = expression.this 1412 if type_value in self.UNSUPPORTED_TYPES: 1413 self.unsupported( 1414 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1415 ) 1416 1417 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1418 type_sql = self.sql(expression, "kind") 1419 else: 1420 type_sql = ( 1421 self.TYPE_MAPPING.get(type_value, type_value.value) 1422 if isinstance(type_value, exp.DataType.Type) 1423 else type_value 1424 ) 1425 1426 if interior: 1427 if expression.args.get("nested"): 1428 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1429 if expression.args.get("values") is not None: 1430 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1431 values = self.expressions(expression, key="values", flat=True) 1432 values = f"{delimiters[0]}{values}{delimiters[1]}" 1433 elif type_value == exp.DataType.Type.INTERVAL: 1434 nested = f" {interior}" 1435 else: 1436 nested = f"({interior})" 1437 1438 type_sql = f"{type_sql}{nested}{values}" 1439 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1440 exp.DataType.Type.TIMETZ, 1441 exp.DataType.Type.TIMESTAMPTZ, 1442 ): 1443 type_sql = f"{type_sql} WITH TIME ZONE" 1444 1445 return type_sql 1446 1447 def directory_sql(self, expression: exp.Directory) -> str: 1448 local = "LOCAL " if expression.args.get("local") else "" 1449 row_format = self.sql(expression, "row_format") 1450 row_format = f" {row_format}" if row_format else "" 1451 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1452 1453 def delete_sql(self, expression: exp.Delete) -> str: 1454 this = self.sql(expression, "this") 1455 this = f" FROM {this}" if this else "" 1456 using = self.sql(expression, "using") 1457 using = f" USING {using}" if using else "" 1458 cluster = self.sql(expression, "cluster") 1459 cluster = f" {cluster}" if cluster else "" 1460 where = self.sql(expression, "where") 1461 returning = self.sql(expression, "returning") 1462 limit = self.sql(expression, "limit") 1463 tables = self.expressions(expression, key="tables") 1464 tables = f" {tables}" if tables else "" 1465 if self.RETURNING_END: 1466 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1467 else: 1468 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1469 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1470 1471 def drop_sql(self, expression: exp.Drop) -> str: 1472 this = self.sql(expression, "this") 1473 expressions = self.expressions(expression, flat=True) 1474 expressions = f" ({expressions})" if expressions else "" 1475 kind = expression.args["kind"] 1476 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1477 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1478 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1479 on_cluster = self.sql(expression, "cluster") 1480 on_cluster = f" {on_cluster}" if on_cluster else "" 1481 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1482 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1483 cascade = " CASCADE" if expression.args.get("cascade") else "" 1484 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1485 purge = " PURGE" if expression.args.get("purge") else "" 1486 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1487 1488 def set_operation(self, expression: exp.SetOperation) -> str: 1489 op_type = type(expression) 1490 op_name = op_type.key.upper() 1491 1492 distinct = expression.args.get("distinct") 1493 if ( 1494 distinct is False 1495 and op_type in (exp.Except, exp.Intersect) 1496 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1497 ): 1498 self.unsupported(f"{op_name} ALL is not supported") 1499 1500 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1501 1502 if distinct is None: 1503 distinct = default_distinct 1504 if distinct is None: 1505 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1506 1507 if distinct is default_distinct: 1508 distinct_or_all = "" 1509 else: 1510 distinct_or_all = " DISTINCT" if distinct else " ALL" 1511 1512 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1513 side_kind = f"{side_kind} " if side_kind else "" 1514 1515 by_name = " BY NAME" if expression.args.get("by_name") else "" 1516 on = self.expressions(expression, key="on", flat=True) 1517 on = f" ON ({on})" if on else "" 1518 1519 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1520 1521 def set_operations(self, expression: exp.SetOperation) -> str: 1522 if not self.SET_OP_MODIFIERS: 1523 limit = expression.args.get("limit") 1524 order = expression.args.get("order") 1525 1526 if limit or order: 1527 select = self._move_ctes_to_top_level( 1528 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1529 ) 1530 1531 if limit: 1532 select = select.limit(limit.pop(), copy=False) 1533 if order: 1534 select = select.order_by(order.pop(), copy=False) 1535 return self.sql(select) 1536 1537 sqls: t.List[str] = [] 1538 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1539 1540 while stack: 1541 node = stack.pop() 1542 1543 if isinstance(node, exp.SetOperation): 1544 stack.append(node.expression) 1545 stack.append( 1546 self.maybe_comment( 1547 self.set_operation(node), comments=node.comments, separated=True 1548 ) 1549 ) 1550 stack.append(node.this) 1551 else: 1552 sqls.append(self.sql(node)) 1553 1554 this = self.sep().join(sqls) 1555 this = self.query_modifiers(expression, this) 1556 return self.prepend_ctes(expression, this) 1557 1558 def fetch_sql(self, expression: exp.Fetch) -> str: 1559 direction = expression.args.get("direction") 1560 direction = f" {direction}" if direction else "" 1561 count = self.sql(expression, "count") 1562 count = f" {count}" if count else "" 1563 limit_options = self.sql(expression, "limit_options") 1564 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1565 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1566 1567 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1568 percent = " PERCENT" if expression.args.get("percent") else "" 1569 rows = " ROWS" if expression.args.get("rows") else "" 1570 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1571 if not with_ties and rows: 1572 with_ties = " ONLY" 1573 return f"{percent}{rows}{with_ties}" 1574 1575 def filter_sql(self, expression: exp.Filter) -> str: 1576 if self.AGGREGATE_FILTER_SUPPORTED: 1577 this = self.sql(expression, "this") 1578 where = self.sql(expression, "expression").strip() 1579 return f"{this} FILTER({where})" 1580 1581 agg = expression.this 1582 agg_arg = agg.this 1583 cond = expression.expression.this 1584 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1585 return self.sql(agg) 1586 1587 def hint_sql(self, expression: exp.Hint) -> str: 1588 if not self.QUERY_HINTS: 1589 self.unsupported("Hints are not supported") 1590 return "" 1591 1592 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1593 1594 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1595 using = self.sql(expression, "using") 1596 using = f" USING {using}" if using else "" 1597 columns = self.expressions(expression, key="columns", flat=True) 1598 columns = f"({columns})" if columns else "" 1599 partition_by = self.expressions(expression, key="partition_by", flat=True) 1600 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1601 where = self.sql(expression, "where") 1602 include = self.expressions(expression, key="include", flat=True) 1603 if include: 1604 include = f" INCLUDE ({include})" 1605 with_storage = self.expressions(expression, key="with_storage", flat=True) 1606 with_storage = f" WITH ({with_storage})" if with_storage else "" 1607 tablespace = self.sql(expression, "tablespace") 1608 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1609 on = self.sql(expression, "on") 1610 on = f" ON {on}" if on else "" 1611 1612 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1613 1614 def index_sql(self, expression: exp.Index) -> str: 1615 unique = "UNIQUE " if expression.args.get("unique") else "" 1616 primary = "PRIMARY " if expression.args.get("primary") else "" 1617 amp = "AMP " if expression.args.get("amp") else "" 1618 name = self.sql(expression, "this") 1619 name = f"{name} " if name else "" 1620 table = self.sql(expression, "table") 1621 table = f"{self.INDEX_ON} {table}" if table else "" 1622 1623 index = "INDEX " if not table else "" 1624 1625 params = self.sql(expression, "params") 1626 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1627 1628 def identifier_sql(self, expression: exp.Identifier) -> str: 1629 text = expression.name 1630 lower = text.lower() 1631 text = lower if self.normalize and not expression.quoted else text 1632 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1633 if ( 1634 expression.quoted 1635 or self.dialect.can_identify(text, self.identify) 1636 or lower in self.RESERVED_KEYWORDS 1637 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1638 ): 1639 text = f"{self._identifier_start}{text}{self._identifier_end}" 1640 return text 1641 1642 def hex_sql(self, expression: exp.Hex) -> str: 1643 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1644 if self.dialect.HEX_LOWERCASE: 1645 text = self.func("LOWER", text) 1646 1647 return text 1648 1649 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1650 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1651 if not self.dialect.HEX_LOWERCASE: 1652 text = self.func("LOWER", text) 1653 return text 1654 1655 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1656 input_format = self.sql(expression, "input_format") 1657 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1658 output_format = self.sql(expression, "output_format") 1659 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1660 return self.sep().join((input_format, output_format)) 1661 1662 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1663 string = self.sql(exp.Literal.string(expression.name)) 1664 return f"{prefix}{string}" 1665 1666 def partition_sql(self, expression: exp.Partition) -> str: 1667 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1668 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1669 1670 def properties_sql(self, expression: exp.Properties) -> str: 1671 root_properties = [] 1672 with_properties = [] 1673 1674 for p in expression.expressions: 1675 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1676 if p_loc == exp.Properties.Location.POST_WITH: 1677 with_properties.append(p) 1678 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1679 root_properties.append(p) 1680 1681 root_props_ast = exp.Properties(expressions=root_properties) 1682 root_props_ast.parent = expression.parent 1683 1684 with_props_ast = exp.Properties(expressions=with_properties) 1685 with_props_ast.parent = expression.parent 1686 1687 root_props = self.root_properties(root_props_ast) 1688 with_props = self.with_properties(with_props_ast) 1689 1690 if root_props and with_props and not self.pretty: 1691 with_props = " " + with_props 1692 1693 return root_props + with_props 1694 1695 def root_properties(self, properties: exp.Properties) -> str: 1696 if properties.expressions: 1697 return self.expressions(properties, indent=False, sep=" ") 1698 return "" 1699 1700 def properties( 1701 self, 1702 properties: exp.Properties, 1703 prefix: str = "", 1704 sep: str = ", ", 1705 suffix: str = "", 1706 wrapped: bool = True, 1707 ) -> str: 1708 if properties.expressions: 1709 expressions = self.expressions(properties, sep=sep, indent=False) 1710 if expressions: 1711 expressions = self.wrap(expressions) if wrapped else expressions 1712 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1713 return "" 1714 1715 def with_properties(self, properties: exp.Properties) -> str: 1716 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1717 1718 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1719 properties_locs = defaultdict(list) 1720 for p in properties.expressions: 1721 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1722 if p_loc != exp.Properties.Location.UNSUPPORTED: 1723 properties_locs[p_loc].append(p) 1724 else: 1725 self.unsupported(f"Unsupported property {p.key}") 1726 1727 return properties_locs 1728 1729 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1730 if isinstance(expression.this, exp.Dot): 1731 return self.sql(expression, "this") 1732 return f"'{expression.name}'" if string_key else expression.name 1733 1734 def property_sql(self, expression: exp.Property) -> str: 1735 property_cls = expression.__class__ 1736 if property_cls == exp.Property: 1737 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1738 1739 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1740 if not property_name: 1741 self.unsupported(f"Unsupported property {expression.key}") 1742 1743 return f"{property_name}={self.sql(expression, 'this')}" 1744 1745 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1746 if self.SUPPORTS_CREATE_TABLE_LIKE: 1747 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1748 options = f" {options}" if options else "" 1749 1750 like = f"LIKE {self.sql(expression, 'this')}{options}" 1751 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1752 like = f"({like})" 1753 1754 return like 1755 1756 if expression.expressions: 1757 self.unsupported("Transpilation of LIKE property options is unsupported") 1758 1759 select = exp.select("*").from_(expression.this).limit(0) 1760 return f"AS {self.sql(select)}" 1761 1762 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1763 no = "NO " if expression.args.get("no") else "" 1764 protection = " PROTECTION" if expression.args.get("protection") else "" 1765 return f"{no}FALLBACK{protection}" 1766 1767 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1768 no = "NO " if expression.args.get("no") else "" 1769 local = expression.args.get("local") 1770 local = f"{local} " if local else "" 1771 dual = "DUAL " if expression.args.get("dual") else "" 1772 before = "BEFORE " if expression.args.get("before") else "" 1773 after = "AFTER " if expression.args.get("after") else "" 1774 return f"{no}{local}{dual}{before}{after}JOURNAL" 1775 1776 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1777 freespace = self.sql(expression, "this") 1778 percent = " PERCENT" if expression.args.get("percent") else "" 1779 return f"FREESPACE={freespace}{percent}" 1780 1781 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1782 if expression.args.get("default"): 1783 property = "DEFAULT" 1784 elif expression.args.get("on"): 1785 property = "ON" 1786 else: 1787 property = "OFF" 1788 return f"CHECKSUM={property}" 1789 1790 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1791 if expression.args.get("no"): 1792 return "NO MERGEBLOCKRATIO" 1793 if expression.args.get("default"): 1794 return "DEFAULT MERGEBLOCKRATIO" 1795 1796 percent = " PERCENT" if expression.args.get("percent") else "" 1797 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1798 1799 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1800 default = expression.args.get("default") 1801 minimum = expression.args.get("minimum") 1802 maximum = expression.args.get("maximum") 1803 if default or minimum or maximum: 1804 if default: 1805 prop = "DEFAULT" 1806 elif minimum: 1807 prop = "MINIMUM" 1808 else: 1809 prop = "MAXIMUM" 1810 return f"{prop} DATABLOCKSIZE" 1811 units = expression.args.get("units") 1812 units = f" {units}" if units else "" 1813 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1814 1815 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1816 autotemp = expression.args.get("autotemp") 1817 always = expression.args.get("always") 1818 default = expression.args.get("default") 1819 manual = expression.args.get("manual") 1820 never = expression.args.get("never") 1821 1822 if autotemp is not None: 1823 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1824 elif always: 1825 prop = "ALWAYS" 1826 elif default: 1827 prop = "DEFAULT" 1828 elif manual: 1829 prop = "MANUAL" 1830 elif never: 1831 prop = "NEVER" 1832 return f"BLOCKCOMPRESSION={prop}" 1833 1834 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1835 no = expression.args.get("no") 1836 no = " NO" if no else "" 1837 concurrent = expression.args.get("concurrent") 1838 concurrent = " CONCURRENT" if concurrent else "" 1839 target = self.sql(expression, "target") 1840 target = f" {target}" if target else "" 1841 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1842 1843 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1844 if isinstance(expression.this, list): 1845 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1846 if expression.this: 1847 modulus = self.sql(expression, "this") 1848 remainder = self.sql(expression, "expression") 1849 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1850 1851 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1852 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1853 return f"FROM ({from_expressions}) TO ({to_expressions})" 1854 1855 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1856 this = self.sql(expression, "this") 1857 1858 for_values_or_default = expression.expression 1859 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1860 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1861 else: 1862 for_values_or_default = " DEFAULT" 1863 1864 return f"PARTITION OF {this}{for_values_or_default}" 1865 1866 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1867 kind = expression.args.get("kind") 1868 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1869 for_or_in = expression.args.get("for_or_in") 1870 for_or_in = f" {for_or_in}" if for_or_in else "" 1871 lock_type = expression.args.get("lock_type") 1872 override = " OVERRIDE" if expression.args.get("override") else "" 1873 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1874 1875 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1876 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1877 statistics = expression.args.get("statistics") 1878 statistics_sql = "" 1879 if statistics is not None: 1880 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1881 return f"{data_sql}{statistics_sql}" 1882 1883 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1884 this = self.sql(expression, "this") 1885 this = f"HISTORY_TABLE={this}" if this else "" 1886 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1887 data_consistency = ( 1888 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1889 ) 1890 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1891 retention_period = ( 1892 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1893 ) 1894 1895 if this: 1896 on_sql = self.func("ON", this, data_consistency, retention_period) 1897 else: 1898 on_sql = "ON" if expression.args.get("on") else "OFF" 1899 1900 sql = f"SYSTEM_VERSIONING={on_sql}" 1901 1902 return f"WITH({sql})" if expression.args.get("with") else sql 1903 1904 def insert_sql(self, expression: exp.Insert) -> str: 1905 hint = self.sql(expression, "hint") 1906 overwrite = expression.args.get("overwrite") 1907 1908 if isinstance(expression.this, exp.Directory): 1909 this = " OVERWRITE" if overwrite else " INTO" 1910 else: 1911 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1912 1913 stored = self.sql(expression, "stored") 1914 stored = f" {stored}" if stored else "" 1915 alternative = expression.args.get("alternative") 1916 alternative = f" OR {alternative}" if alternative else "" 1917 ignore = " IGNORE" if expression.args.get("ignore") else "" 1918 is_function = expression.args.get("is_function") 1919 if is_function: 1920 this = f"{this} FUNCTION" 1921 this = f"{this} {self.sql(expression, 'this')}" 1922 1923 exists = " IF EXISTS" if expression.args.get("exists") else "" 1924 where = self.sql(expression, "where") 1925 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1926 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1927 on_conflict = self.sql(expression, "conflict") 1928 on_conflict = f" {on_conflict}" if on_conflict else "" 1929 by_name = " BY NAME" if expression.args.get("by_name") else "" 1930 returning = self.sql(expression, "returning") 1931 1932 if self.RETURNING_END: 1933 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1934 else: 1935 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1936 1937 partition_by = self.sql(expression, "partition") 1938 partition_by = f" {partition_by}" if partition_by else "" 1939 settings = self.sql(expression, "settings") 1940 settings = f" {settings}" if settings else "" 1941 1942 source = self.sql(expression, "source") 1943 source = f"TABLE {source}" if source else "" 1944 1945 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1946 return self.prepend_ctes(expression, sql) 1947 1948 def introducer_sql(self, expression: exp.Introducer) -> str: 1949 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1950 1951 def kill_sql(self, expression: exp.Kill) -> str: 1952 kind = self.sql(expression, "kind") 1953 kind = f" {kind}" if kind else "" 1954 this = self.sql(expression, "this") 1955 this = f" {this}" if this else "" 1956 return f"KILL{kind}{this}" 1957 1958 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1959 return expression.name 1960 1961 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1962 return expression.name 1963 1964 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1965 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1966 1967 constraint = self.sql(expression, "constraint") 1968 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1969 1970 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1971 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1972 action = self.sql(expression, "action") 1973 1974 expressions = self.expressions(expression, flat=True) 1975 if expressions: 1976 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1977 expressions = f" {set_keyword}{expressions}" 1978 1979 where = self.sql(expression, "where") 1980 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1981 1982 def returning_sql(self, expression: exp.Returning) -> str: 1983 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1984 1985 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1986 fields = self.sql(expression, "fields") 1987 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1988 escaped = self.sql(expression, "escaped") 1989 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1990 items = self.sql(expression, "collection_items") 1991 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1992 keys = self.sql(expression, "map_keys") 1993 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1994 lines = self.sql(expression, "lines") 1995 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1996 null = self.sql(expression, "null") 1997 null = f" NULL DEFINED AS {null}" if null else "" 1998 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1999 2000 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 2001 return f"WITH ({self.expressions(expression, flat=True)})" 2002 2003 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 2004 this = f"{self.sql(expression, 'this')} INDEX" 2005 target = self.sql(expression, "target") 2006 target = f" FOR {target}" if target else "" 2007 return f"{this}{target} ({self.expressions(expression, flat=True)})" 2008 2009 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 2010 this = self.sql(expression, "this") 2011 kind = self.sql(expression, "kind") 2012 expr = self.sql(expression, "expression") 2013 return f"{this} ({kind} => {expr})" 2014 2015 def table_parts(self, expression: exp.Table) -> str: 2016 return ".".join( 2017 self.sql(part) 2018 for part in ( 2019 expression.args.get("catalog"), 2020 expression.args.get("db"), 2021 expression.args.get("this"), 2022 ) 2023 if part is not None 2024 ) 2025 2026 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2027 table = self.table_parts(expression) 2028 only = "ONLY " if expression.args.get("only") else "" 2029 partition = self.sql(expression, "partition") 2030 partition = f" {partition}" if partition else "" 2031 version = self.sql(expression, "version") 2032 version = f" {version}" if version else "" 2033 alias = self.sql(expression, "alias") 2034 alias = f"{sep}{alias}" if alias else "" 2035 2036 sample = self.sql(expression, "sample") 2037 if self.dialect.ALIAS_POST_TABLESAMPLE: 2038 sample_pre_alias = sample 2039 sample_post_alias = "" 2040 else: 2041 sample_pre_alias = "" 2042 sample_post_alias = sample 2043 2044 hints = self.expressions(expression, key="hints", sep=" ") 2045 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2046 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2047 joins = self.indent( 2048 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2049 ) 2050 laterals = self.expressions(expression, key="laterals", sep="") 2051 2052 file_format = self.sql(expression, "format") 2053 if file_format: 2054 pattern = self.sql(expression, "pattern") 2055 pattern = f", PATTERN => {pattern}" if pattern else "" 2056 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2057 2058 ordinality = expression.args.get("ordinality") or "" 2059 if ordinality: 2060 ordinality = f" WITH ORDINALITY{alias}" 2061 alias = "" 2062 2063 when = self.sql(expression, "when") 2064 if when: 2065 table = f"{table} {when}" 2066 2067 changes = self.sql(expression, "changes") 2068 changes = f" {changes}" if changes else "" 2069 2070 rows_from = self.expressions(expression, key="rows_from") 2071 if rows_from: 2072 table = f"ROWS FROM {self.wrap(rows_from)}" 2073 2074 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2075 2076 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2077 table = self.func("TABLE", expression.this) 2078 alias = self.sql(expression, "alias") 2079 alias = f" AS {alias}" if alias else "" 2080 sample = self.sql(expression, "sample") 2081 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2082 joins = self.indent( 2083 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2084 ) 2085 return f"{table}{alias}{pivots}{sample}{joins}" 2086 2087 def tablesample_sql( 2088 self, 2089 expression: exp.TableSample, 2090 tablesample_keyword: t.Optional[str] = None, 2091 ) -> str: 2092 method = self.sql(expression, "method") 2093 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2094 numerator = self.sql(expression, "bucket_numerator") 2095 denominator = self.sql(expression, "bucket_denominator") 2096 field = self.sql(expression, "bucket_field") 2097 field = f" ON {field}" if field else "" 2098 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2099 seed = self.sql(expression, "seed") 2100 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2101 2102 size = self.sql(expression, "size") 2103 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2104 size = f"{size} ROWS" 2105 2106 percent = self.sql(expression, "percent") 2107 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2108 percent = f"{percent} PERCENT" 2109 2110 expr = f"{bucket}{percent}{size}" 2111 if self.TABLESAMPLE_REQUIRES_PARENS: 2112 expr = f"({expr})" 2113 2114 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2115 2116 def pivot_sql(self, expression: exp.Pivot) -> str: 2117 expressions = self.expressions(expression, flat=True) 2118 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2119 2120 group = self.sql(expression, "group") 2121 2122 if expression.this: 2123 this = self.sql(expression, "this") 2124 if not expressions: 2125 return f"UNPIVOT {this}" 2126 2127 on = f"{self.seg('ON')} {expressions}" 2128 into = self.sql(expression, "into") 2129 into = f"{self.seg('INTO')} {into}" if into else "" 2130 using = self.expressions(expression, key="using", flat=True) 2131 using = f"{self.seg('USING')} {using}" if using else "" 2132 return f"{direction} {this}{on}{into}{using}{group}" 2133 2134 alias = self.sql(expression, "alias") 2135 alias = f" AS {alias}" if alias else "" 2136 2137 fields = self.expressions( 2138 expression, 2139 "fields", 2140 sep=" ", 2141 dynamic=True, 2142 new_line=True, 2143 skip_first=True, 2144 skip_last=True, 2145 ) 2146 2147 include_nulls = expression.args.get("include_nulls") 2148 if include_nulls is not None: 2149 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2150 else: 2151 nulls = "" 2152 2153 default_on_null = self.sql(expression, "default_on_null") 2154 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2155 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2156 2157 def version_sql(self, expression: exp.Version) -> str: 2158 this = f"FOR {expression.name}" 2159 kind = expression.text("kind") 2160 expr = self.sql(expression, "expression") 2161 return f"{this} {kind} {expr}" 2162 2163 def tuple_sql(self, expression: exp.Tuple) -> str: 2164 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2165 2166 def update_sql(self, expression: exp.Update) -> str: 2167 this = self.sql(expression, "this") 2168 set_sql = self.expressions(expression, flat=True) 2169 from_sql = self.sql(expression, "from") 2170 where_sql = self.sql(expression, "where") 2171 returning = self.sql(expression, "returning") 2172 order = self.sql(expression, "order") 2173 limit = self.sql(expression, "limit") 2174 if self.RETURNING_END: 2175 expression_sql = f"{from_sql}{where_sql}{returning}" 2176 else: 2177 expression_sql = f"{returning}{from_sql}{where_sql}" 2178 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2179 return self.prepend_ctes(expression, sql) 2180 2181 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2182 values_as_table = values_as_table and self.VALUES_AS_TABLE 2183 2184 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2185 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2186 args = self.expressions(expression) 2187 alias = self.sql(expression, "alias") 2188 values = f"VALUES{self.seg('')}{args}" 2189 values = ( 2190 f"({values})" 2191 if self.WRAP_DERIVED_VALUES 2192 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2193 else values 2194 ) 2195 return f"{values} AS {alias}" if alias else values 2196 2197 # Converts `VALUES...` expression into a series of select unions. 2198 alias_node = expression.args.get("alias") 2199 column_names = alias_node and alias_node.columns 2200 2201 selects: t.List[exp.Query] = [] 2202 2203 for i, tup in enumerate(expression.expressions): 2204 row = tup.expressions 2205 2206 if i == 0 and column_names: 2207 row = [ 2208 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2209 ] 2210 2211 selects.append(exp.Select(expressions=row)) 2212 2213 if self.pretty: 2214 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2215 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2216 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2217 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2218 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2219 2220 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2221 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2222 return f"({unions}){alias}" 2223 2224 def var_sql(self, expression: exp.Var) -> str: 2225 return self.sql(expression, "this") 2226 2227 @unsupported_args("expressions") 2228 def into_sql(self, expression: exp.Into) -> str: 2229 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2230 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2231 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2232 2233 def from_sql(self, expression: exp.From) -> str: 2234 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2235 2236 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2237 grouping_sets = self.expressions(expression, indent=False) 2238 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2239 2240 def rollup_sql(self, expression: exp.Rollup) -> str: 2241 expressions = self.expressions(expression, indent=False) 2242 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2243 2244 def cube_sql(self, expression: exp.Cube) -> str: 2245 expressions = self.expressions(expression, indent=False) 2246 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2247 2248 def group_sql(self, expression: exp.Group) -> str: 2249 group_by_all = expression.args.get("all") 2250 if group_by_all is True: 2251 modifier = " ALL" 2252 elif group_by_all is False: 2253 modifier = " DISTINCT" 2254 else: 2255 modifier = "" 2256 2257 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2258 2259 grouping_sets = self.expressions(expression, key="grouping_sets") 2260 cube = self.expressions(expression, key="cube") 2261 rollup = self.expressions(expression, key="rollup") 2262 2263 groupings = csv( 2264 self.seg(grouping_sets) if grouping_sets else "", 2265 self.seg(cube) if cube else "", 2266 self.seg(rollup) if rollup else "", 2267 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2268 sep=self.GROUPINGS_SEP, 2269 ) 2270 2271 if ( 2272 expression.expressions 2273 and groupings 2274 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2275 ): 2276 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2277 2278 return f"{group_by}{groupings}" 2279 2280 def having_sql(self, expression: exp.Having) -> str: 2281 this = self.indent(self.sql(expression, "this")) 2282 return f"{self.seg('HAVING')}{self.sep()}{this}" 2283 2284 def connect_sql(self, expression: exp.Connect) -> str: 2285 start = self.sql(expression, "start") 2286 start = self.seg(f"START WITH {start}") if start else "" 2287 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2288 connect = self.sql(expression, "connect") 2289 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2290 return start + connect 2291 2292 def prior_sql(self, expression: exp.Prior) -> str: 2293 return f"PRIOR {self.sql(expression, 'this')}" 2294 2295 def join_sql(self, expression: exp.Join) -> str: 2296 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2297 side = None 2298 else: 2299 side = expression.side 2300 2301 op_sql = " ".join( 2302 op 2303 for op in ( 2304 expression.method, 2305 "GLOBAL" if expression.args.get("global") else None, 2306 side, 2307 expression.kind, 2308 expression.hint if self.JOIN_HINTS else None, 2309 ) 2310 if op 2311 ) 2312 match_cond = self.sql(expression, "match_condition") 2313 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2314 on_sql = self.sql(expression, "on") 2315 using = expression.args.get("using") 2316 2317 if not on_sql and using: 2318 on_sql = csv(*(self.sql(column) for column in using)) 2319 2320 this = expression.this 2321 this_sql = self.sql(this) 2322 2323 exprs = self.expressions(expression) 2324 if exprs: 2325 this_sql = f"{this_sql},{self.seg(exprs)}" 2326 2327 if on_sql: 2328 on_sql = self.indent(on_sql, skip_first=True) 2329 space = self.seg(" " * self.pad) if self.pretty else " " 2330 if using: 2331 on_sql = f"{space}USING ({on_sql})" 2332 else: 2333 on_sql = f"{space}ON {on_sql}" 2334 elif not op_sql: 2335 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2336 return f" {this_sql}" 2337 2338 return f", {this_sql}" 2339 2340 if op_sql != "STRAIGHT_JOIN": 2341 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2342 2343 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2344 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2345 2346 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2347 args = self.expressions(expression, flat=True) 2348 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2349 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2350 2351 def lateral_op(self, expression: exp.Lateral) -> str: 2352 cross_apply = expression.args.get("cross_apply") 2353 2354 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2355 if cross_apply is True: 2356 op = "INNER JOIN " 2357 elif cross_apply is False: 2358 op = "LEFT JOIN " 2359 else: 2360 op = "" 2361 2362 return f"{op}LATERAL" 2363 2364 def lateral_sql(self, expression: exp.Lateral) -> str: 2365 this = self.sql(expression, "this") 2366 2367 if expression.args.get("view"): 2368 alias = expression.args["alias"] 2369 columns = self.expressions(alias, key="columns", flat=True) 2370 table = f" {alias.name}" if alias.name else "" 2371 columns = f" AS {columns}" if columns else "" 2372 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2373 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2374 2375 alias = self.sql(expression, "alias") 2376 alias = f" AS {alias}" if alias else "" 2377 2378 ordinality = expression.args.get("ordinality") or "" 2379 if ordinality: 2380 ordinality = f" WITH ORDINALITY{alias}" 2381 alias = "" 2382 2383 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2384 2385 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2386 this = self.sql(expression, "this") 2387 2388 args = [ 2389 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2390 for e in (expression.args.get(k) for k in ("offset", "expression")) 2391 if e 2392 ] 2393 2394 args_sql = ", ".join(self.sql(e) for e in args) 2395 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2396 expressions = self.expressions(expression, flat=True) 2397 limit_options = self.sql(expression, "limit_options") 2398 expressions = f" BY {expressions}" if expressions else "" 2399 2400 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2401 2402 def offset_sql(self, expression: exp.Offset) -> str: 2403 this = self.sql(expression, "this") 2404 value = expression.expression 2405 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2406 expressions = self.expressions(expression, flat=True) 2407 expressions = f" BY {expressions}" if expressions else "" 2408 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2409 2410 def setitem_sql(self, expression: exp.SetItem) -> str: 2411 kind = self.sql(expression, "kind") 2412 kind = f"{kind} " if kind else "" 2413 this = self.sql(expression, "this") 2414 expressions = self.expressions(expression) 2415 collate = self.sql(expression, "collate") 2416 collate = f" COLLATE {collate}" if collate else "" 2417 global_ = "GLOBAL " if expression.args.get("global") else "" 2418 return f"{global_}{kind}{this}{expressions}{collate}" 2419 2420 def set_sql(self, expression: exp.Set) -> str: 2421 expressions = f" {self.expressions(expression, flat=True)}" 2422 tag = " TAG" if expression.args.get("tag") else "" 2423 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2424 2425 def queryband_sql(self, expression: exp.QueryBand) -> str: 2426 this = self.sql(expression, "this") 2427 update = " UPDATE" if expression.args.get("update") else "" 2428 scope = self.sql(expression, "scope") 2429 scope = f" FOR {scope}" if scope else "" 2430 2431 return f"QUERY_BAND = {this}{update}{scope}" 2432 2433 def pragma_sql(self, expression: exp.Pragma) -> str: 2434 return f"PRAGMA {self.sql(expression, 'this')}" 2435 2436 def lock_sql(self, expression: exp.Lock) -> str: 2437 if not self.LOCKING_READS_SUPPORTED: 2438 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2439 return "" 2440 2441 update = expression.args["update"] 2442 key = expression.args.get("key") 2443 if update: 2444 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2445 else: 2446 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2447 expressions = self.expressions(expression, flat=True) 2448 expressions = f" OF {expressions}" if expressions else "" 2449 wait = expression.args.get("wait") 2450 2451 if wait is not None: 2452 if isinstance(wait, exp.Literal): 2453 wait = f" WAIT {self.sql(wait)}" 2454 else: 2455 wait = " NOWAIT" if wait else " SKIP LOCKED" 2456 2457 return f"{lock_type}{expressions}{wait or ''}" 2458 2459 def literal_sql(self, expression: exp.Literal) -> str: 2460 text = expression.this or "" 2461 if expression.is_string: 2462 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2463 return text 2464 2465 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2466 if self.dialect.ESCAPED_SEQUENCES: 2467 to_escaped = self.dialect.ESCAPED_SEQUENCES 2468 text = "".join( 2469 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2470 ) 2471 2472 return self._replace_line_breaks(text).replace( 2473 self.dialect.QUOTE_END, self._escaped_quote_end 2474 ) 2475 2476 def loaddata_sql(self, expression: exp.LoadData) -> str: 2477 local = " LOCAL" if expression.args.get("local") else "" 2478 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2479 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2480 this = f" INTO TABLE {self.sql(expression, 'this')}" 2481 partition = self.sql(expression, "partition") 2482 partition = f" {partition}" if partition else "" 2483 input_format = self.sql(expression, "input_format") 2484 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2485 serde = self.sql(expression, "serde") 2486 serde = f" SERDE {serde}" if serde else "" 2487 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2488 2489 def null_sql(self, *_) -> str: 2490 return "NULL" 2491 2492 def boolean_sql(self, expression: exp.Boolean) -> str: 2493 return "TRUE" if expression.this else "FALSE" 2494 2495 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2496 this = self.sql(expression, "this") 2497 this = f"{this} " if this else this 2498 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2499 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2500 2501 def withfill_sql(self, expression: exp.WithFill) -> str: 2502 from_sql = self.sql(expression, "from") 2503 from_sql = f" FROM {from_sql}" if from_sql else "" 2504 to_sql = self.sql(expression, "to") 2505 to_sql = f" TO {to_sql}" if to_sql else "" 2506 step_sql = self.sql(expression, "step") 2507 step_sql = f" STEP {step_sql}" if step_sql else "" 2508 interpolated_values = [ 2509 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2510 if isinstance(e, exp.Alias) 2511 else self.sql(e, "this") 2512 for e in expression.args.get("interpolate") or [] 2513 ] 2514 interpolate = ( 2515 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2516 ) 2517 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2518 2519 def cluster_sql(self, expression: exp.Cluster) -> str: 2520 return self.op_expressions("CLUSTER BY", expression) 2521 2522 def distribute_sql(self, expression: exp.Distribute) -> str: 2523 return self.op_expressions("DISTRIBUTE BY", expression) 2524 2525 def sort_sql(self, expression: exp.Sort) -> str: 2526 return self.op_expressions("SORT BY", expression) 2527 2528 def ordered_sql(self, expression: exp.Ordered) -> str: 2529 desc = expression.args.get("desc") 2530 asc = not desc 2531 2532 nulls_first = expression.args.get("nulls_first") 2533 nulls_last = not nulls_first 2534 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2535 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2536 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2537 2538 this = self.sql(expression, "this") 2539 2540 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2541 nulls_sort_change = "" 2542 if nulls_first and ( 2543 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2544 ): 2545 nulls_sort_change = " NULLS FIRST" 2546 elif ( 2547 nulls_last 2548 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2549 and not nulls_are_last 2550 ): 2551 nulls_sort_change = " NULLS LAST" 2552 2553 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2554 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2555 window = expression.find_ancestor(exp.Window, exp.Select) 2556 if isinstance(window, exp.Window) and window.args.get("spec"): 2557 self.unsupported( 2558 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2559 ) 2560 nulls_sort_change = "" 2561 elif self.NULL_ORDERING_SUPPORTED is False and ( 2562 (asc and nulls_sort_change == " NULLS LAST") 2563 or (desc and nulls_sort_change == " NULLS FIRST") 2564 ): 2565 # BigQuery does not allow these ordering/nulls combinations when used under 2566 # an aggregation func or under a window containing one 2567 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2568 2569 if isinstance(ancestor, exp.Window): 2570 ancestor = ancestor.this 2571 if isinstance(ancestor, exp.AggFunc): 2572 self.unsupported( 2573 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2574 ) 2575 nulls_sort_change = "" 2576 elif self.NULL_ORDERING_SUPPORTED is None: 2577 if expression.this.is_int: 2578 self.unsupported( 2579 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2580 ) 2581 elif not isinstance(expression.this, exp.Rand): 2582 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2583 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2584 nulls_sort_change = "" 2585 2586 with_fill = self.sql(expression, "with_fill") 2587 with_fill = f" {with_fill}" if with_fill else "" 2588 2589 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2590 2591 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2592 window_frame = self.sql(expression, "window_frame") 2593 window_frame = f"{window_frame} " if window_frame else "" 2594 2595 this = self.sql(expression, "this") 2596 2597 return f"{window_frame}{this}" 2598 2599 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2600 partition = self.partition_by_sql(expression) 2601 order = self.sql(expression, "order") 2602 measures = self.expressions(expression, key="measures") 2603 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2604 rows = self.sql(expression, "rows") 2605 rows = self.seg(rows) if rows else "" 2606 after = self.sql(expression, "after") 2607 after = self.seg(after) if after else "" 2608 pattern = self.sql(expression, "pattern") 2609 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2610 definition_sqls = [ 2611 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2612 for definition in expression.args.get("define", []) 2613 ] 2614 definitions = self.expressions(sqls=definition_sqls) 2615 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2616 body = "".join( 2617 ( 2618 partition, 2619 order, 2620 measures, 2621 rows, 2622 after, 2623 pattern, 2624 define, 2625 ) 2626 ) 2627 alias = self.sql(expression, "alias") 2628 alias = f" {alias}" if alias else "" 2629 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2630 2631 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2632 limit = expression.args.get("limit") 2633 2634 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2635 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2636 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2637 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2638 2639 return csv( 2640 *sqls, 2641 *[self.sql(join) for join in expression.args.get("joins") or []], 2642 self.sql(expression, "match"), 2643 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2644 self.sql(expression, "prewhere"), 2645 self.sql(expression, "where"), 2646 self.sql(expression, "connect"), 2647 self.sql(expression, "group"), 2648 self.sql(expression, "having"), 2649 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2650 self.sql(expression, "order"), 2651 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2652 *self.after_limit_modifiers(expression), 2653 self.options_modifier(expression), 2654 self.for_modifiers(expression), 2655 sep="", 2656 ) 2657 2658 def options_modifier(self, expression: exp.Expression) -> str: 2659 options = self.expressions(expression, key="options") 2660 return f" {options}" if options else "" 2661 2662 def for_modifiers(self, expression: exp.Expression) -> str: 2663 for_modifiers = self.expressions(expression, key="for") 2664 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2665 2666 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2667 self.unsupported("Unsupported query option.") 2668 return "" 2669 2670 def offset_limit_modifiers( 2671 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2672 ) -> t.List[str]: 2673 return [ 2674 self.sql(expression, "offset") if fetch else self.sql(limit), 2675 self.sql(limit) if fetch else self.sql(expression, "offset"), 2676 ] 2677 2678 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2679 locks = self.expressions(expression, key="locks", sep=" ") 2680 locks = f" {locks}" if locks else "" 2681 return [locks, self.sql(expression, "sample")] 2682 2683 def select_sql(self, expression: exp.Select) -> str: 2684 into = expression.args.get("into") 2685 if not self.SUPPORTS_SELECT_INTO and into: 2686 into.pop() 2687 2688 hint = self.sql(expression, "hint") 2689 distinct = self.sql(expression, "distinct") 2690 distinct = f" {distinct}" if distinct else "" 2691 kind = self.sql(expression, "kind") 2692 2693 limit = expression.args.get("limit") 2694 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2695 top = self.limit_sql(limit, top=True) 2696 limit.pop() 2697 else: 2698 top = "" 2699 2700 expressions = self.expressions(expression) 2701 2702 if kind: 2703 if kind in self.SELECT_KINDS: 2704 kind = f" AS {kind}" 2705 else: 2706 if kind == "STRUCT": 2707 expressions = self.expressions( 2708 sqls=[ 2709 self.sql( 2710 exp.Struct( 2711 expressions=[ 2712 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2713 if isinstance(e, exp.Alias) 2714 else e 2715 for e in expression.expressions 2716 ] 2717 ) 2718 ) 2719 ] 2720 ) 2721 kind = "" 2722 2723 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2724 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2725 2726 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2727 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2728 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2729 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2730 sql = self.query_modifiers( 2731 expression, 2732 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2733 self.sql(expression, "into", comment=False), 2734 self.sql(expression, "from", comment=False), 2735 ) 2736 2737 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2738 if expression.args.get("with"): 2739 sql = self.maybe_comment(sql, expression) 2740 expression.pop_comments() 2741 2742 sql = self.prepend_ctes(expression, sql) 2743 2744 if not self.SUPPORTS_SELECT_INTO and into: 2745 if into.args.get("temporary"): 2746 table_kind = " TEMPORARY" 2747 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2748 table_kind = " UNLOGGED" 2749 else: 2750 table_kind = "" 2751 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2752 2753 return sql 2754 2755 def schema_sql(self, expression: exp.Schema) -> str: 2756 this = self.sql(expression, "this") 2757 sql = self.schema_columns_sql(expression) 2758 return f"{this} {sql}" if this and sql else this or sql 2759 2760 def schema_columns_sql(self, expression: exp.Schema) -> str: 2761 if expression.expressions: 2762 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2763 return "" 2764 2765 def star_sql(self, expression: exp.Star) -> str: 2766 except_ = self.expressions(expression, key="except", flat=True) 2767 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2768 replace = self.expressions(expression, key="replace", flat=True) 2769 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2770 rename = self.expressions(expression, key="rename", flat=True) 2771 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2772 return f"*{except_}{replace}{rename}" 2773 2774 def parameter_sql(self, expression: exp.Parameter) -> str: 2775 this = self.sql(expression, "this") 2776 return f"{self.PARAMETER_TOKEN}{this}" 2777 2778 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2779 this = self.sql(expression, "this") 2780 kind = expression.text("kind") 2781 if kind: 2782 kind = f"{kind}." 2783 return f"@@{kind}{this}" 2784 2785 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2786 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2787 2788 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2789 alias = self.sql(expression, "alias") 2790 alias = f"{sep}{alias}" if alias else "" 2791 sample = self.sql(expression, "sample") 2792 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2793 alias = f"{sample}{alias}" 2794 2795 # Set to None so it's not generated again by self.query_modifiers() 2796 expression.set("sample", None) 2797 2798 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2799 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2800 return self.prepend_ctes(expression, sql) 2801 2802 def qualify_sql(self, expression: exp.Qualify) -> str: 2803 this = self.indent(self.sql(expression, "this")) 2804 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2805 2806 def unnest_sql(self, expression: exp.Unnest) -> str: 2807 args = self.expressions(expression, flat=True) 2808 2809 alias = expression.args.get("alias") 2810 offset = expression.args.get("offset") 2811 2812 if self.UNNEST_WITH_ORDINALITY: 2813 if alias and isinstance(offset, exp.Expression): 2814 alias.append("columns", offset) 2815 2816 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2817 columns = alias.columns 2818 alias = self.sql(columns[0]) if columns else "" 2819 else: 2820 alias = self.sql(alias) 2821 2822 alias = f" AS {alias}" if alias else alias 2823 if self.UNNEST_WITH_ORDINALITY: 2824 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2825 else: 2826 if isinstance(offset, exp.Expression): 2827 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2828 elif offset: 2829 suffix = f"{alias} WITH OFFSET" 2830 else: 2831 suffix = alias 2832 2833 return f"UNNEST({args}){suffix}" 2834 2835 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2836 return "" 2837 2838 def where_sql(self, expression: exp.Where) -> str: 2839 this = self.indent(self.sql(expression, "this")) 2840 return f"{self.seg('WHERE')}{self.sep()}{this}" 2841 2842 def window_sql(self, expression: exp.Window) -> str: 2843 this = self.sql(expression, "this") 2844 partition = self.partition_by_sql(expression) 2845 order = expression.args.get("order") 2846 order = self.order_sql(order, flat=True) if order else "" 2847 spec = self.sql(expression, "spec") 2848 alias = self.sql(expression, "alias") 2849 over = self.sql(expression, "over") or "OVER" 2850 2851 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2852 2853 first = expression.args.get("first") 2854 if first is None: 2855 first = "" 2856 else: 2857 first = "FIRST" if first else "LAST" 2858 2859 if not partition and not order and not spec and alias: 2860 return f"{this} {alias}" 2861 2862 args = self.format_args( 2863 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2864 ) 2865 return f"{this} ({args})" 2866 2867 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2868 partition = self.expressions(expression, key="partition_by", flat=True) 2869 return f"PARTITION BY {partition}" if partition else "" 2870 2871 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2872 kind = self.sql(expression, "kind") 2873 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2874 end = ( 2875 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2876 or "CURRENT ROW" 2877 ) 2878 2879 window_spec = f"{kind} BETWEEN {start} AND {end}" 2880 2881 exclude = self.sql(expression, "exclude") 2882 if exclude: 2883 if self.SUPPORTS_WINDOW_EXCLUDE: 2884 window_spec += f" EXCLUDE {exclude}" 2885 else: 2886 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2887 2888 return window_spec 2889 2890 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2891 this = self.sql(expression, "this") 2892 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2893 return f"{this} WITHIN GROUP ({expression_sql})" 2894 2895 def between_sql(self, expression: exp.Between) -> str: 2896 this = self.sql(expression, "this") 2897 low = self.sql(expression, "low") 2898 high = self.sql(expression, "high") 2899 symmetric = expression.args.get("symmetric") 2900 2901 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2902 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2903 2904 flag = ( 2905 " SYMMETRIC" 2906 if symmetric 2907 else " ASYMMETRIC" 2908 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2909 else "" # silently drop ASYMMETRIC – semantics identical 2910 ) 2911 return f"{this} BETWEEN{flag} {low} AND {high}" 2912 2913 def bracket_offset_expressions( 2914 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2915 ) -> t.List[exp.Expression]: 2916 return apply_index_offset( 2917 expression.this, 2918 expression.expressions, 2919 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2920 dialect=self.dialect, 2921 ) 2922 2923 def bracket_sql(self, expression: exp.Bracket) -> str: 2924 expressions = self.bracket_offset_expressions(expression) 2925 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2926 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2927 2928 def all_sql(self, expression: exp.All) -> str: 2929 this = self.sql(expression, "this") 2930 if not isinstance(expression.this, (exp.Tuple, exp.Paren)): 2931 this = self.wrap(this) 2932 return f"ALL {this}" 2933 2934 def any_sql(self, expression: exp.Any) -> str: 2935 this = self.sql(expression, "this") 2936 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2937 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2938 this = self.wrap(this) 2939 return f"ANY{this}" 2940 return f"ANY {this}" 2941 2942 def exists_sql(self, expression: exp.Exists) -> str: 2943 return f"EXISTS{self.wrap(expression)}" 2944 2945 def case_sql(self, expression: exp.Case) -> str: 2946 this = self.sql(expression, "this") 2947 statements = [f"CASE {this}" if this else "CASE"] 2948 2949 for e in expression.args["ifs"]: 2950 statements.append(f"WHEN {self.sql(e, 'this')}") 2951 statements.append(f"THEN {self.sql(e, 'true')}") 2952 2953 default = self.sql(expression, "default") 2954 2955 if default: 2956 statements.append(f"ELSE {default}") 2957 2958 statements.append("END") 2959 2960 if self.pretty and self.too_wide(statements): 2961 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2962 2963 return " ".join(statements) 2964 2965 def constraint_sql(self, expression: exp.Constraint) -> str: 2966 this = self.sql(expression, "this") 2967 expressions = self.expressions(expression, flat=True) 2968 return f"CONSTRAINT {this} {expressions}" 2969 2970 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2971 order = expression.args.get("order") 2972 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2973 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2974 2975 def extract_sql(self, expression: exp.Extract) -> str: 2976 from sqlglot.dialects.dialect import map_date_part 2977 2978 this = ( 2979 map_date_part(expression.this, self.dialect) 2980 if self.NORMALIZE_EXTRACT_DATE_PARTS 2981 else expression.this 2982 ) 2983 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2984 expression_sql = self.sql(expression, "expression") 2985 2986 return f"EXTRACT({this_sql} FROM {expression_sql})" 2987 2988 def trim_sql(self, expression: exp.Trim) -> str: 2989 trim_type = self.sql(expression, "position") 2990 2991 if trim_type == "LEADING": 2992 func_name = "LTRIM" 2993 elif trim_type == "TRAILING": 2994 func_name = "RTRIM" 2995 else: 2996 func_name = "TRIM" 2997 2998 return self.func(func_name, expression.this, expression.expression) 2999 3000 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3001 args = expression.expressions 3002 if isinstance(expression, exp.ConcatWs): 3003 args = args[1:] # Skip the delimiter 3004 3005 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3006 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3007 3008 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3009 3010 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3011 if not e.type: 3012 from sqlglot.optimizer.annotate_types import annotate_types 3013 3014 e = annotate_types(e, dialect=self.dialect) 3015 3016 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3017 return e 3018 3019 return exp.func("coalesce", e, exp.Literal.string("")) 3020 3021 args = [_wrap_with_coalesce(e) for e in args] 3022 3023 return args 3024 3025 def concat_sql(self, expression: exp.Concat) -> str: 3026 expressions = self.convert_concat_args(expression) 3027 3028 # Some dialects don't allow a single-argument CONCAT call 3029 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3030 return self.sql(expressions[0]) 3031 3032 return self.func("CONCAT", *expressions) 3033 3034 def concatws_sql(self, expression: exp.ConcatWs) -> str: 3035 return self.func( 3036 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 3037 ) 3038 3039 def check_sql(self, expression: exp.Check) -> str: 3040 this = self.sql(expression, key="this") 3041 return f"CHECK ({this})" 3042 3043 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3044 expressions = self.expressions(expression, flat=True) 3045 expressions = f" ({expressions})" if expressions else "" 3046 reference = self.sql(expression, "reference") 3047 reference = f" {reference}" if reference else "" 3048 delete = self.sql(expression, "delete") 3049 delete = f" ON DELETE {delete}" if delete else "" 3050 update = self.sql(expression, "update") 3051 update = f" ON UPDATE {update}" if update else "" 3052 options = self.expressions(expression, key="options", flat=True, sep=" ") 3053 options = f" {options}" if options else "" 3054 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3055 3056 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3057 expressions = self.expressions(expression, flat=True) 3058 include = self.sql(expression, "include") 3059 options = self.expressions(expression, key="options", flat=True, sep=" ") 3060 options = f" {options}" if options else "" 3061 return f"PRIMARY KEY ({expressions}){include}{options}" 3062 3063 def if_sql(self, expression: exp.If) -> str: 3064 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3065 3066 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3067 modifier = expression.args.get("modifier") 3068 modifier = f" {modifier}" if modifier else "" 3069 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3070 3071 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3072 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3073 3074 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3075 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3076 3077 if expression.args.get("escape"): 3078 path = self.escape_str(path) 3079 3080 if self.QUOTE_JSON_PATH: 3081 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3082 3083 return path 3084 3085 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3086 if isinstance(expression, exp.JSONPathPart): 3087 transform = self.TRANSFORMS.get(expression.__class__) 3088 if not callable(transform): 3089 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3090 return "" 3091 3092 return transform(self, expression) 3093 3094 if isinstance(expression, int): 3095 return str(expression) 3096 3097 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3098 escaped = expression.replace("'", "\\'") 3099 escaped = f"\\'{expression}\\'" 3100 else: 3101 escaped = expression.replace('"', '\\"') 3102 escaped = f'"{escaped}"' 3103 3104 return escaped 3105 3106 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3107 return f"{self.sql(expression, 'this')} FORMAT JSON" 3108 3109 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3110 # Output the Teradata column FORMAT override. 3111 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3112 this = self.sql(expression, "this") 3113 fmt = self.sql(expression, "format") 3114 return f"{this} (FORMAT {fmt})" 3115 3116 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3117 null_handling = expression.args.get("null_handling") 3118 null_handling = f" {null_handling}" if null_handling else "" 3119 3120 unique_keys = expression.args.get("unique_keys") 3121 if unique_keys is not None: 3122 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3123 else: 3124 unique_keys = "" 3125 3126 return_type = self.sql(expression, "return_type") 3127 return_type = f" RETURNING {return_type}" if return_type else "" 3128 encoding = self.sql(expression, "encoding") 3129 encoding = f" ENCODING {encoding}" if encoding else "" 3130 3131 return self.func( 3132 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3133 *expression.expressions, 3134 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3135 ) 3136 3137 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3138 return self.jsonobject_sql(expression) 3139 3140 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3141 null_handling = expression.args.get("null_handling") 3142 null_handling = f" {null_handling}" if null_handling else "" 3143 return_type = self.sql(expression, "return_type") 3144 return_type = f" RETURNING {return_type}" if return_type else "" 3145 strict = " STRICT" if expression.args.get("strict") else "" 3146 return self.func( 3147 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3148 ) 3149 3150 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3151 this = self.sql(expression, "this") 3152 order = self.sql(expression, "order") 3153 null_handling = expression.args.get("null_handling") 3154 null_handling = f" {null_handling}" if null_handling else "" 3155 return_type = self.sql(expression, "return_type") 3156 return_type = f" RETURNING {return_type}" if return_type else "" 3157 strict = " STRICT" if expression.args.get("strict") else "" 3158 return self.func( 3159 "JSON_ARRAYAGG", 3160 this, 3161 suffix=f"{order}{null_handling}{return_type}{strict})", 3162 ) 3163 3164 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3165 path = self.sql(expression, "path") 3166 path = f" PATH {path}" if path else "" 3167 nested_schema = self.sql(expression, "nested_schema") 3168 3169 if nested_schema: 3170 return f"NESTED{path} {nested_schema}" 3171 3172 this = self.sql(expression, "this") 3173 kind = self.sql(expression, "kind") 3174 kind = f" {kind}" if kind else "" 3175 return f"{this}{kind}{path}" 3176 3177 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3178 return self.func("COLUMNS", *expression.expressions) 3179 3180 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3181 this = self.sql(expression, "this") 3182 path = self.sql(expression, "path") 3183 path = f", {path}" if path else "" 3184 error_handling = expression.args.get("error_handling") 3185 error_handling = f" {error_handling}" if error_handling else "" 3186 empty_handling = expression.args.get("empty_handling") 3187 empty_handling = f" {empty_handling}" if empty_handling else "" 3188 schema = self.sql(expression, "schema") 3189 return self.func( 3190 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3191 ) 3192 3193 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3194 this = self.sql(expression, "this") 3195 kind = self.sql(expression, "kind") 3196 path = self.sql(expression, "path") 3197 path = f" {path}" if path else "" 3198 as_json = " AS JSON" if expression.args.get("as_json") else "" 3199 return f"{this} {kind}{path}{as_json}" 3200 3201 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3202 this = self.sql(expression, "this") 3203 path = self.sql(expression, "path") 3204 path = f", {path}" if path else "" 3205 expressions = self.expressions(expression) 3206 with_ = ( 3207 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3208 if expressions 3209 else "" 3210 ) 3211 return f"OPENJSON({this}{path}){with_}" 3212 3213 def in_sql(self, expression: exp.In) -> str: 3214 query = expression.args.get("query") 3215 unnest = expression.args.get("unnest") 3216 field = expression.args.get("field") 3217 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3218 3219 if query: 3220 in_sql = self.sql(query) 3221 elif unnest: 3222 in_sql = self.in_unnest_op(unnest) 3223 elif field: 3224 in_sql = self.sql(field) 3225 else: 3226 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3227 3228 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3229 3230 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3231 return f"(SELECT {self.sql(unnest)})" 3232 3233 def interval_sql(self, expression: exp.Interval) -> str: 3234 unit = self.sql(expression, "unit") 3235 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3236 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3237 unit = f" {unit}" if unit else "" 3238 3239 if self.SINGLE_STRING_INTERVAL: 3240 this = expression.this.name if expression.this else "" 3241 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3242 3243 this = self.sql(expression, "this") 3244 if this: 3245 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3246 this = f" {this}" if unwrapped else f" ({this})" 3247 3248 return f"INTERVAL{this}{unit}" 3249 3250 def return_sql(self, expression: exp.Return) -> str: 3251 return f"RETURN {self.sql(expression, 'this')}" 3252 3253 def reference_sql(self, expression: exp.Reference) -> str: 3254 this = self.sql(expression, "this") 3255 expressions = self.expressions(expression, flat=True) 3256 expressions = f"({expressions})" if expressions else "" 3257 options = self.expressions(expression, key="options", flat=True, sep=" ") 3258 options = f" {options}" if options else "" 3259 return f"REFERENCES {this}{expressions}{options}" 3260 3261 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3262 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3263 parent = expression.parent 3264 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3265 return self.func( 3266 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3267 ) 3268 3269 def paren_sql(self, expression: exp.Paren) -> str: 3270 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3271 return f"({sql}{self.seg(')', sep='')}" 3272 3273 def neg_sql(self, expression: exp.Neg) -> str: 3274 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3275 this_sql = self.sql(expression, "this") 3276 sep = " " if this_sql[0] == "-" else "" 3277 return f"-{sep}{this_sql}" 3278 3279 def not_sql(self, expression: exp.Not) -> str: 3280 return f"NOT {self.sql(expression, 'this')}" 3281 3282 def alias_sql(self, expression: exp.Alias) -> str: 3283 alias = self.sql(expression, "alias") 3284 alias = f" AS {alias}" if alias else "" 3285 return f"{self.sql(expression, 'this')}{alias}" 3286 3287 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3288 alias = expression.args["alias"] 3289 3290 parent = expression.parent 3291 pivot = parent and parent.parent 3292 3293 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3294 identifier_alias = isinstance(alias, exp.Identifier) 3295 literal_alias = isinstance(alias, exp.Literal) 3296 3297 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3298 alias.replace(exp.Literal.string(alias.output_name)) 3299 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3300 alias.replace(exp.to_identifier(alias.output_name)) 3301 3302 return self.alias_sql(expression) 3303 3304 def aliases_sql(self, expression: exp.Aliases) -> str: 3305 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3306 3307 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3308 this = self.sql(expression, "this") 3309 index = self.sql(expression, "expression") 3310 return f"{this} AT {index}" 3311 3312 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3313 this = self.sql(expression, "this") 3314 zone = self.sql(expression, "zone") 3315 return f"{this} AT TIME ZONE {zone}" 3316 3317 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3318 this = self.sql(expression, "this") 3319 zone = self.sql(expression, "zone") 3320 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3321 3322 def add_sql(self, expression: exp.Add) -> str: 3323 return self.binary(expression, "+") 3324 3325 def and_sql( 3326 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3327 ) -> str: 3328 return self.connector_sql(expression, "AND", stack) 3329 3330 def or_sql( 3331 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3332 ) -> str: 3333 return self.connector_sql(expression, "OR", stack) 3334 3335 def xor_sql( 3336 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3337 ) -> str: 3338 return self.connector_sql(expression, "XOR", stack) 3339 3340 def connector_sql( 3341 self, 3342 expression: exp.Connector, 3343 op: str, 3344 stack: t.Optional[t.List[str | exp.Expression]] = None, 3345 ) -> str: 3346 if stack is not None: 3347 if expression.expressions: 3348 stack.append(self.expressions(expression, sep=f" {op} ")) 3349 else: 3350 stack.append(expression.right) 3351 if expression.comments and self.comments: 3352 for comment in expression.comments: 3353 if comment: 3354 op += f" /*{self.sanitize_comment(comment)}*/" 3355 stack.extend((op, expression.left)) 3356 return op 3357 3358 stack = [expression] 3359 sqls: t.List[str] = [] 3360 ops = set() 3361 3362 while stack: 3363 node = stack.pop() 3364 if isinstance(node, exp.Connector): 3365 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3366 else: 3367 sql = self.sql(node) 3368 if sqls and sqls[-1] in ops: 3369 sqls[-1] += f" {sql}" 3370 else: 3371 sqls.append(sql) 3372 3373 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3374 return sep.join(sqls) 3375 3376 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3377 return self.binary(expression, "&") 3378 3379 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3380 return self.binary(expression, "<<") 3381 3382 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3383 return f"~{self.sql(expression, 'this')}" 3384 3385 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3386 return self.binary(expression, "|") 3387 3388 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3389 return self.binary(expression, ">>") 3390 3391 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3392 return self.binary(expression, "^") 3393 3394 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3395 format_sql = self.sql(expression, "format") 3396 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3397 to_sql = self.sql(expression, "to") 3398 to_sql = f" {to_sql}" if to_sql else "" 3399 action = self.sql(expression, "action") 3400 action = f" {action}" if action else "" 3401 default = self.sql(expression, "default") 3402 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3403 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3404 3405 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3406 zone = self.sql(expression, "this") 3407 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3408 3409 def collate_sql(self, expression: exp.Collate) -> str: 3410 if self.COLLATE_IS_FUNC: 3411 return self.function_fallback_sql(expression) 3412 return self.binary(expression, "COLLATE") 3413 3414 def command_sql(self, expression: exp.Command) -> str: 3415 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3416 3417 def comment_sql(self, expression: exp.Comment) -> str: 3418 this = self.sql(expression, "this") 3419 kind = expression.args["kind"] 3420 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3421 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3422 expression_sql = self.sql(expression, "expression") 3423 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3424 3425 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3426 this = self.sql(expression, "this") 3427 delete = " DELETE" if expression.args.get("delete") else "" 3428 recompress = self.sql(expression, "recompress") 3429 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3430 to_disk = self.sql(expression, "to_disk") 3431 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3432 to_volume = self.sql(expression, "to_volume") 3433 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3434 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3435 3436 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3437 where = self.sql(expression, "where") 3438 group = self.sql(expression, "group") 3439 aggregates = self.expressions(expression, key="aggregates") 3440 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3441 3442 if not (where or group or aggregates) and len(expression.expressions) == 1: 3443 return f"TTL {self.expressions(expression, flat=True)}" 3444 3445 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3446 3447 def transaction_sql(self, expression: exp.Transaction) -> str: 3448 return "BEGIN" 3449 3450 def commit_sql(self, expression: exp.Commit) -> str: 3451 chain = expression.args.get("chain") 3452 if chain is not None: 3453 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3454 3455 return f"COMMIT{chain or ''}" 3456 3457 def rollback_sql(self, expression: exp.Rollback) -> str: 3458 savepoint = expression.args.get("savepoint") 3459 savepoint = f" TO {savepoint}" if savepoint else "" 3460 return f"ROLLBACK{savepoint}" 3461 3462 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3463 this = self.sql(expression, "this") 3464 3465 dtype = self.sql(expression, "dtype") 3466 if dtype: 3467 collate = self.sql(expression, "collate") 3468 collate = f" COLLATE {collate}" if collate else "" 3469 using = self.sql(expression, "using") 3470 using = f" USING {using}" if using else "" 3471 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3472 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3473 3474 default = self.sql(expression, "default") 3475 if default: 3476 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3477 3478 comment = self.sql(expression, "comment") 3479 if comment: 3480 return f"ALTER COLUMN {this} COMMENT {comment}" 3481 3482 visible = expression.args.get("visible") 3483 if visible: 3484 return f"ALTER COLUMN {this} SET {visible}" 3485 3486 allow_null = expression.args.get("allow_null") 3487 drop = expression.args.get("drop") 3488 3489 if not drop and not allow_null: 3490 self.unsupported("Unsupported ALTER COLUMN syntax") 3491 3492 if allow_null is not None: 3493 keyword = "DROP" if drop else "SET" 3494 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3495 3496 return f"ALTER COLUMN {this} DROP DEFAULT" 3497 3498 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3499 this = self.sql(expression, "this") 3500 3501 visible = expression.args.get("visible") 3502 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3503 3504 return f"ALTER INDEX {this} {visible_sql}" 3505 3506 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3507 this = self.sql(expression, "this") 3508 if not isinstance(expression.this, exp.Var): 3509 this = f"KEY DISTKEY {this}" 3510 return f"ALTER DISTSTYLE {this}" 3511 3512 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3513 compound = " COMPOUND" if expression.args.get("compound") else "" 3514 this = self.sql(expression, "this") 3515 expressions = self.expressions(expression, flat=True) 3516 expressions = f"({expressions})" if expressions else "" 3517 return f"ALTER{compound} SORTKEY {this or expressions}" 3518 3519 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3520 if not self.RENAME_TABLE_WITH_DB: 3521 # Remove db from tables 3522 expression = expression.transform( 3523 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3524 ).assert_is(exp.AlterRename) 3525 this = self.sql(expression, "this") 3526 to_kw = " TO" if include_to else "" 3527 return f"RENAME{to_kw} {this}" 3528 3529 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3530 exists = " IF EXISTS" if expression.args.get("exists") else "" 3531 old_column = self.sql(expression, "this") 3532 new_column = self.sql(expression, "to") 3533 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3534 3535 def alterset_sql(self, expression: exp.AlterSet) -> str: 3536 exprs = self.expressions(expression, flat=True) 3537 if self.ALTER_SET_WRAPPED: 3538 exprs = f"({exprs})" 3539 3540 return f"SET {exprs}" 3541 3542 def alter_sql(self, expression: exp.Alter) -> str: 3543 actions = expression.args["actions"] 3544 3545 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3546 actions[0], exp.ColumnDef 3547 ): 3548 actions_sql = self.expressions(expression, key="actions", flat=True) 3549 actions_sql = f"ADD {actions_sql}" 3550 else: 3551 actions_list = [] 3552 for action in actions: 3553 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3554 action_sql = self.add_column_sql(action) 3555 else: 3556 action_sql = self.sql(action) 3557 if isinstance(action, exp.Query): 3558 action_sql = f"AS {action_sql}" 3559 3560 actions_list.append(action_sql) 3561 3562 actions_sql = self.format_args(*actions_list).lstrip("\n") 3563 3564 exists = " IF EXISTS" if expression.args.get("exists") else "" 3565 on_cluster = self.sql(expression, "cluster") 3566 on_cluster = f" {on_cluster}" if on_cluster else "" 3567 only = " ONLY" if expression.args.get("only") else "" 3568 options = self.expressions(expression, key="options") 3569 options = f", {options}" if options else "" 3570 kind = self.sql(expression, "kind") 3571 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3572 check = " WITH CHECK" if expression.args.get("check") else "" 3573 3574 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}" 3575 3576 def add_column_sql(self, expression: exp.Expression) -> str: 3577 sql = self.sql(expression) 3578 if isinstance(expression, exp.Schema): 3579 column_text = " COLUMNS" 3580 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3581 column_text = " COLUMN" 3582 else: 3583 column_text = "" 3584 3585 return f"ADD{column_text} {sql}" 3586 3587 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3588 expressions = self.expressions(expression) 3589 exists = " IF EXISTS " if expression.args.get("exists") else " " 3590 return f"DROP{exists}{expressions}" 3591 3592 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3593 return f"ADD {self.expressions(expression, indent=False)}" 3594 3595 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3596 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3597 location = self.sql(expression, "location") 3598 location = f" {location}" if location else "" 3599 return f"ADD {exists}{self.sql(expression.this)}{location}" 3600 3601 def distinct_sql(self, expression: exp.Distinct) -> str: 3602 this = self.expressions(expression, flat=True) 3603 3604 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3605 case = exp.case() 3606 for arg in expression.expressions: 3607 case = case.when(arg.is_(exp.null()), exp.null()) 3608 this = self.sql(case.else_(f"({this})")) 3609 3610 this = f" {this}" if this else "" 3611 3612 on = self.sql(expression, "on") 3613 on = f" ON {on}" if on else "" 3614 return f"DISTINCT{this}{on}" 3615 3616 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3617 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3618 3619 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3620 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3621 3622 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3623 this_sql = self.sql(expression, "this") 3624 expression_sql = self.sql(expression, "expression") 3625 kind = "MAX" if expression.args.get("max") else "MIN" 3626 return f"{this_sql} HAVING {kind} {expression_sql}" 3627 3628 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3629 return self.sql( 3630 exp.Cast( 3631 this=exp.Div(this=expression.this, expression=expression.expression), 3632 to=exp.DataType(this=exp.DataType.Type.INT), 3633 ) 3634 ) 3635 3636 def dpipe_sql(self, expression: exp.DPipe) -> str: 3637 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3638 return self.func( 3639 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3640 ) 3641 return self.binary(expression, "||") 3642 3643 def div_sql(self, expression: exp.Div) -> str: 3644 l, r = expression.left, expression.right 3645 3646 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3647 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3648 3649 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3650 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3651 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3652 3653 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3654 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3655 return self.sql( 3656 exp.cast( 3657 l / r, 3658 to=exp.DataType.Type.BIGINT, 3659 ) 3660 ) 3661 3662 return self.binary(expression, "/") 3663 3664 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3665 n = exp._wrap(expression.this, exp.Binary) 3666 d = exp._wrap(expression.expression, exp.Binary) 3667 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3668 3669 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3670 return self.binary(expression, "OVERLAPS") 3671 3672 def distance_sql(self, expression: exp.Distance) -> str: 3673 return self.binary(expression, "<->") 3674 3675 def dot_sql(self, expression: exp.Dot) -> str: 3676 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3677 3678 def eq_sql(self, expression: exp.EQ) -> str: 3679 return self.binary(expression, "=") 3680 3681 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3682 return self.binary(expression, ":=") 3683 3684 def escape_sql(self, expression: exp.Escape) -> str: 3685 return self.binary(expression, "ESCAPE") 3686 3687 def glob_sql(self, expression: exp.Glob) -> str: 3688 return self.binary(expression, "GLOB") 3689 3690 def gt_sql(self, expression: exp.GT) -> str: 3691 return self.binary(expression, ">") 3692 3693 def gte_sql(self, expression: exp.GTE) -> str: 3694 return self.binary(expression, ">=") 3695 3696 def is_sql(self, expression: exp.Is) -> str: 3697 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3698 return self.sql( 3699 expression.this if expression.expression.this else exp.not_(expression.this) 3700 ) 3701 return self.binary(expression, "IS") 3702 3703 def _like_sql(self, expression: exp.Like | exp.ILike) -> str: 3704 this = expression.this 3705 rhs = expression.expression 3706 3707 if isinstance(expression, exp.Like): 3708 exp_class: t.Type[exp.Like | exp.ILike] = exp.Like 3709 op = "LIKE" 3710 else: 3711 exp_class = exp.ILike 3712 op = "ILIKE" 3713 3714 if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS: 3715 exprs = rhs.this.unnest() 3716 3717 if isinstance(exprs, exp.Tuple): 3718 exprs = exprs.expressions 3719 3720 connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_ 3721 3722 like_expr: exp.Expression = exp_class(this=this, expression=exprs[0]) 3723 for expr in exprs[1:]: 3724 like_expr = connective(like_expr, exp_class(this=this, expression=expr)) 3725 3726 return self.sql(like_expr) 3727 3728 return self.binary(expression, op) 3729 3730 def like_sql(self, expression: exp.Like) -> str: 3731 return self._like_sql(expression) 3732 3733 def ilike_sql(self, expression: exp.ILike) -> str: 3734 return self._like_sql(expression) 3735 3736 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3737 return self.binary(expression, "SIMILAR TO") 3738 3739 def lt_sql(self, expression: exp.LT) -> str: 3740 return self.binary(expression, "<") 3741 3742 def lte_sql(self, expression: exp.LTE) -> str: 3743 return self.binary(expression, "<=") 3744 3745 def mod_sql(self, expression: exp.Mod) -> str: 3746 return self.binary(expression, "%") 3747 3748 def mul_sql(self, expression: exp.Mul) -> str: 3749 return self.binary(expression, "*") 3750 3751 def neq_sql(self, expression: exp.NEQ) -> str: 3752 return self.binary(expression, "<>") 3753 3754 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3755 return self.binary(expression, "IS NOT DISTINCT FROM") 3756 3757 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3758 return self.binary(expression, "IS DISTINCT FROM") 3759 3760 def slice_sql(self, expression: exp.Slice) -> str: 3761 return self.binary(expression, ":") 3762 3763 def sub_sql(self, expression: exp.Sub) -> str: 3764 return self.binary(expression, "-") 3765 3766 def trycast_sql(self, expression: exp.TryCast) -> str: 3767 return self.cast_sql(expression, safe_prefix="TRY_") 3768 3769 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3770 return self.cast_sql(expression) 3771 3772 def try_sql(self, expression: exp.Try) -> str: 3773 if not self.TRY_SUPPORTED: 3774 self.unsupported("Unsupported TRY function") 3775 return self.sql(expression, "this") 3776 3777 return self.func("TRY", expression.this) 3778 3779 def log_sql(self, expression: exp.Log) -> str: 3780 this = expression.this 3781 expr = expression.expression 3782 3783 if self.dialect.LOG_BASE_FIRST is False: 3784 this, expr = expr, this 3785 elif self.dialect.LOG_BASE_FIRST is None and expr: 3786 if this.name in ("2", "10"): 3787 return self.func(f"LOG{this.name}", expr) 3788 3789 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3790 3791 return self.func("LOG", this, expr) 3792 3793 def use_sql(self, expression: exp.Use) -> str: 3794 kind = self.sql(expression, "kind") 3795 kind = f" {kind}" if kind else "" 3796 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3797 this = f" {this}" if this else "" 3798 return f"USE{kind}{this}" 3799 3800 def binary(self, expression: exp.Binary, op: str) -> str: 3801 sqls: t.List[str] = [] 3802 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3803 binary_type = type(expression) 3804 3805 while stack: 3806 node = stack.pop() 3807 3808 if type(node) is binary_type: 3809 op_func = node.args.get("operator") 3810 if op_func: 3811 op = f"OPERATOR({self.sql(op_func)})" 3812 3813 stack.append(node.right) 3814 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3815 stack.append(node.left) 3816 else: 3817 sqls.append(self.sql(node)) 3818 3819 return "".join(sqls) 3820 3821 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3822 to_clause = self.sql(expression, "to") 3823 if to_clause: 3824 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3825 3826 return self.function_fallback_sql(expression) 3827 3828 def function_fallback_sql(self, expression: exp.Func) -> str: 3829 args = [] 3830 3831 for key in expression.arg_types: 3832 arg_value = expression.args.get(key) 3833 3834 if isinstance(arg_value, list): 3835 for value in arg_value: 3836 args.append(value) 3837 elif arg_value is not None: 3838 args.append(arg_value) 3839 3840 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3841 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3842 else: 3843 name = expression.sql_name() 3844 3845 return self.func(name, *args) 3846 3847 def func( 3848 self, 3849 name: str, 3850 *args: t.Optional[exp.Expression | str], 3851 prefix: str = "(", 3852 suffix: str = ")", 3853 normalize: bool = True, 3854 ) -> str: 3855 name = self.normalize_func(name) if normalize else name 3856 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3857 3858 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3859 arg_sqls = tuple( 3860 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3861 ) 3862 if self.pretty and self.too_wide(arg_sqls): 3863 return self.indent( 3864 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3865 ) 3866 return sep.join(arg_sqls) 3867 3868 def too_wide(self, args: t.Iterable) -> bool: 3869 return sum(len(arg) for arg in args) > self.max_text_width 3870 3871 def format_time( 3872 self, 3873 expression: exp.Expression, 3874 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3875 inverse_time_trie: t.Optional[t.Dict] = None, 3876 ) -> t.Optional[str]: 3877 return format_time( 3878 self.sql(expression, "format"), 3879 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3880 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3881 ) 3882 3883 def expressions( 3884 self, 3885 expression: t.Optional[exp.Expression] = None, 3886 key: t.Optional[str] = None, 3887 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3888 flat: bool = False, 3889 indent: bool = True, 3890 skip_first: bool = False, 3891 skip_last: bool = False, 3892 sep: str = ", ", 3893 prefix: str = "", 3894 dynamic: bool = False, 3895 new_line: bool = False, 3896 ) -> str: 3897 expressions = expression.args.get(key or "expressions") if expression else sqls 3898 3899 if not expressions: 3900 return "" 3901 3902 if flat: 3903 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3904 3905 num_sqls = len(expressions) 3906 result_sqls = [] 3907 3908 for i, e in enumerate(expressions): 3909 sql = self.sql(e, comment=False) 3910 if not sql: 3911 continue 3912 3913 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3914 3915 if self.pretty: 3916 if self.leading_comma: 3917 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3918 else: 3919 result_sqls.append( 3920 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3921 ) 3922 else: 3923 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3924 3925 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3926 if new_line: 3927 result_sqls.insert(0, "") 3928 result_sqls.append("") 3929 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3930 else: 3931 result_sql = "".join(result_sqls) 3932 3933 return ( 3934 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3935 if indent 3936 else result_sql 3937 ) 3938 3939 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3940 flat = flat or isinstance(expression.parent, exp.Properties) 3941 expressions_sql = self.expressions(expression, flat=flat) 3942 if flat: 3943 return f"{op} {expressions_sql}" 3944 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3945 3946 def naked_property(self, expression: exp.Property) -> str: 3947 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3948 if not property_name: 3949 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3950 return f"{property_name} {self.sql(expression, 'this')}" 3951 3952 def tag_sql(self, expression: exp.Tag) -> str: 3953 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3954 3955 def token_sql(self, token_type: TokenType) -> str: 3956 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3957 3958 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3959 this = self.sql(expression, "this") 3960 expressions = self.no_identify(self.expressions, expression) 3961 expressions = ( 3962 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3963 ) 3964 return f"{this}{expressions}" if expressions.strip() != "" else this 3965 3966 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3967 this = self.sql(expression, "this") 3968 expressions = self.expressions(expression, flat=True) 3969 return f"{this}({expressions})" 3970 3971 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3972 return self.binary(expression, "=>") 3973 3974 def when_sql(self, expression: exp.When) -> str: 3975 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3976 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3977 condition = self.sql(expression, "condition") 3978 condition = f" AND {condition}" if condition else "" 3979 3980 then_expression = expression.args.get("then") 3981 if isinstance(then_expression, exp.Insert): 3982 this = self.sql(then_expression, "this") 3983 this = f"INSERT {this}" if this else "INSERT" 3984 then = self.sql(then_expression, "expression") 3985 then = f"{this} VALUES {then}" if then else this 3986 elif isinstance(then_expression, exp.Update): 3987 if isinstance(then_expression.args.get("expressions"), exp.Star): 3988 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3989 else: 3990 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3991 else: 3992 then = self.sql(then_expression) 3993 return f"WHEN {matched}{source}{condition} THEN {then}" 3994 3995 def whens_sql(self, expression: exp.Whens) -> str: 3996 return self.expressions(expression, sep=" ", indent=False) 3997 3998 def merge_sql(self, expression: exp.Merge) -> str: 3999 table = expression.this 4000 table_alias = "" 4001 4002 hints = table.args.get("hints") 4003 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4004 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4005 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4006 4007 this = self.sql(table) 4008 using = f"USING {self.sql(expression, 'using')}" 4009 on = f"ON {self.sql(expression, 'on')}" 4010 whens = self.sql(expression, "whens") 4011 4012 returning = self.sql(expression, "returning") 4013 if returning: 4014 whens = f"{whens}{returning}" 4015 4016 sep = self.sep() 4017 4018 return self.prepend_ctes( 4019 expression, 4020 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4021 ) 4022 4023 @unsupported_args("format") 4024 def tochar_sql(self, expression: exp.ToChar) -> str: 4025 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 4026 4027 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4028 if not self.SUPPORTS_TO_NUMBER: 4029 self.unsupported("Unsupported TO_NUMBER function") 4030 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4031 4032 fmt = expression.args.get("format") 4033 if not fmt: 4034 self.unsupported("Conversion format is required for TO_NUMBER") 4035 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4036 4037 return self.func("TO_NUMBER", expression.this, fmt) 4038 4039 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4040 this = self.sql(expression, "this") 4041 kind = self.sql(expression, "kind") 4042 settings_sql = self.expressions(expression, key="settings", sep=" ") 4043 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4044 return f"{this}({kind}{args})" 4045 4046 def dictrange_sql(self, expression: exp.DictRange) -> str: 4047 this = self.sql(expression, "this") 4048 max = self.sql(expression, "max") 4049 min = self.sql(expression, "min") 4050 return f"{this}(MIN {min} MAX {max})" 4051 4052 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 4053 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 4054 4055 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 4056 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 4057 4058 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 4059 def uniquekeyproperty_sql( 4060 self, expression: exp.UniqueKeyProperty, prefix: str = "UNIQUE KEY" 4061 ) -> str: 4062 return f"{prefix} ({self.expressions(expression, flat=True)})" 4063 4064 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 4065 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4066 expressions = self.expressions(expression, flat=True) 4067 expressions = f" {self.wrap(expressions)}" if expressions else "" 4068 buckets = self.sql(expression, "buckets") 4069 kind = self.sql(expression, "kind") 4070 buckets = f" BUCKETS {buckets}" if buckets else "" 4071 order = self.sql(expression, "order") 4072 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4073 4074 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4075 return "" 4076 4077 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4078 expressions = self.expressions(expression, key="expressions", flat=True) 4079 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4080 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4081 buckets = self.sql(expression, "buckets") 4082 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4083 4084 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4085 this = self.sql(expression, "this") 4086 having = self.sql(expression, "having") 4087 4088 if having: 4089 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4090 4091 return self.func("ANY_VALUE", this) 4092 4093 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4094 transform = self.func("TRANSFORM", *expression.expressions) 4095 row_format_before = self.sql(expression, "row_format_before") 4096 row_format_before = f" {row_format_before}" if row_format_before else "" 4097 record_writer = self.sql(expression, "record_writer") 4098 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4099 using = f" USING {self.sql(expression, 'command_script')}" 4100 schema = self.sql(expression, "schema") 4101 schema = f" AS {schema}" if schema else "" 4102 row_format_after = self.sql(expression, "row_format_after") 4103 row_format_after = f" {row_format_after}" if row_format_after else "" 4104 record_reader = self.sql(expression, "record_reader") 4105 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4106 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4107 4108 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4109 key_block_size = self.sql(expression, "key_block_size") 4110 if key_block_size: 4111 return f"KEY_BLOCK_SIZE = {key_block_size}" 4112 4113 using = self.sql(expression, "using") 4114 if using: 4115 return f"USING {using}" 4116 4117 parser = self.sql(expression, "parser") 4118 if parser: 4119 return f"WITH PARSER {parser}" 4120 4121 comment = self.sql(expression, "comment") 4122 if comment: 4123 return f"COMMENT {comment}" 4124 4125 visible = expression.args.get("visible") 4126 if visible is not None: 4127 return "VISIBLE" if visible else "INVISIBLE" 4128 4129 engine_attr = self.sql(expression, "engine_attr") 4130 if engine_attr: 4131 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4132 4133 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4134 if secondary_engine_attr: 4135 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4136 4137 self.unsupported("Unsupported index constraint option.") 4138 return "" 4139 4140 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4141 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4142 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4143 4144 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4145 kind = self.sql(expression, "kind") 4146 kind = f"{kind} INDEX" if kind else "INDEX" 4147 this = self.sql(expression, "this") 4148 this = f" {this}" if this else "" 4149 index_type = self.sql(expression, "index_type") 4150 index_type = f" USING {index_type}" if index_type else "" 4151 expressions = self.expressions(expression, flat=True) 4152 expressions = f" ({expressions})" if expressions else "" 4153 options = self.expressions(expression, key="options", sep=" ") 4154 options = f" {options}" if options else "" 4155 return f"{kind}{this}{index_type}{expressions}{options}" 4156 4157 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4158 if self.NVL2_SUPPORTED: 4159 return self.function_fallback_sql(expression) 4160 4161 case = exp.Case().when( 4162 expression.this.is_(exp.null()).not_(copy=False), 4163 expression.args["true"], 4164 copy=False, 4165 ) 4166 else_cond = expression.args.get("false") 4167 if else_cond: 4168 case.else_(else_cond, copy=False) 4169 4170 return self.sql(case) 4171 4172 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4173 this = self.sql(expression, "this") 4174 expr = self.sql(expression, "expression") 4175 iterator = self.sql(expression, "iterator") 4176 condition = self.sql(expression, "condition") 4177 condition = f" IF {condition}" if condition else "" 4178 return f"{this} FOR {expr} IN {iterator}{condition}" 4179 4180 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4181 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4182 4183 def opclass_sql(self, expression: exp.Opclass) -> str: 4184 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4185 4186 def predict_sql(self, expression: exp.Predict) -> str: 4187 model = self.sql(expression, "this") 4188 model = f"MODEL {model}" 4189 table = self.sql(expression, "expression") 4190 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4191 parameters = self.sql(expression, "params_struct") 4192 return self.func("PREDICT", model, table, parameters or None) 4193 4194 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4195 model = self.sql(expression, "this") 4196 model = f"MODEL {model}" 4197 table = self.sql(expression, "expression") 4198 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4199 parameters = self.sql(expression, "params_struct") 4200 return self.func("GENERATE_EMBEDDING", model, table, parameters or None) 4201 4202 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4203 this_sql = self.sql(expression, "this") 4204 if isinstance(expression.this, exp.Table): 4205 this_sql = f"TABLE {this_sql}" 4206 4207 return self.func( 4208 "FEATURES_AT_TIME", 4209 this_sql, 4210 expression.args.get("time"), 4211 expression.args.get("num_rows"), 4212 expression.args.get("ignore_feature_nulls"), 4213 ) 4214 4215 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4216 this_sql = self.sql(expression, "this") 4217 if isinstance(expression.this, exp.Table): 4218 this_sql = f"TABLE {this_sql}" 4219 4220 query_table = self.sql(expression, "query_table") 4221 if isinstance(expression.args["query_table"], exp.Table): 4222 query_table = f"TABLE {query_table}" 4223 4224 return self.func( 4225 "VECTOR_SEARCH", 4226 this_sql, 4227 expression.args.get("column_to_search"), 4228 query_table, 4229 expression.args.get("query_column_to_search"), 4230 expression.args.get("top_k"), 4231 expression.args.get("distance_type"), 4232 expression.args.get("options"), 4233 ) 4234 4235 def forin_sql(self, expression: exp.ForIn) -> str: 4236 this = self.sql(expression, "this") 4237 expression_sql = self.sql(expression, "expression") 4238 return f"FOR {this} DO {expression_sql}" 4239 4240 def refresh_sql(self, expression: exp.Refresh) -> str: 4241 this = self.sql(expression, "this") 4242 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4243 return f"REFRESH {table}{this}" 4244 4245 def toarray_sql(self, expression: exp.ToArray) -> str: 4246 arg = expression.this 4247 if not arg.type: 4248 from sqlglot.optimizer.annotate_types import annotate_types 4249 4250 arg = annotate_types(arg, dialect=self.dialect) 4251 4252 if arg.is_type(exp.DataType.Type.ARRAY): 4253 return self.sql(arg) 4254 4255 cond_for_null = arg.is_(exp.null()) 4256 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4257 4258 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4259 this = expression.this 4260 time_format = self.format_time(expression) 4261 4262 if time_format: 4263 return self.sql( 4264 exp.cast( 4265 exp.StrToTime(this=this, format=expression.args["format"]), 4266 exp.DataType.Type.TIME, 4267 ) 4268 ) 4269 4270 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4271 return self.sql(this) 4272 4273 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4274 4275 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4276 this = expression.this 4277 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4278 return self.sql(this) 4279 4280 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4281 4282 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4283 this = expression.this 4284 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4285 return self.sql(this) 4286 4287 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4288 4289 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4290 this = expression.this 4291 time_format = self.format_time(expression) 4292 4293 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4294 return self.sql( 4295 exp.cast( 4296 exp.StrToTime(this=this, format=expression.args["format"]), 4297 exp.DataType.Type.DATE, 4298 ) 4299 ) 4300 4301 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4302 return self.sql(this) 4303 4304 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4305 4306 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4307 return self.sql( 4308 exp.func( 4309 "DATEDIFF", 4310 expression.this, 4311 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4312 "day", 4313 ) 4314 ) 4315 4316 def lastday_sql(self, expression: exp.LastDay) -> str: 4317 if self.LAST_DAY_SUPPORTS_DATE_PART: 4318 return self.function_fallback_sql(expression) 4319 4320 unit = expression.text("unit") 4321 if unit and unit != "MONTH": 4322 self.unsupported("Date parts are not supported in LAST_DAY.") 4323 4324 return self.func("LAST_DAY", expression.this) 4325 4326 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4327 from sqlglot.dialects.dialect import unit_to_str 4328 4329 return self.func( 4330 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4331 ) 4332 4333 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4334 if self.CAN_IMPLEMENT_ARRAY_ANY: 4335 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4336 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4337 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4338 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4339 4340 from sqlglot.dialects import Dialect 4341 4342 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4343 if self.dialect.__class__ != Dialect: 4344 self.unsupported("ARRAY_ANY is unsupported") 4345 4346 return self.function_fallback_sql(expression) 4347 4348 def struct_sql(self, expression: exp.Struct) -> str: 4349 expression.set( 4350 "expressions", 4351 [ 4352 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4353 if isinstance(e, exp.PropertyEQ) 4354 else e 4355 for e in expression.expressions 4356 ], 4357 ) 4358 4359 return self.function_fallback_sql(expression) 4360 4361 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4362 low = self.sql(expression, "this") 4363 high = self.sql(expression, "expression") 4364 4365 return f"{low} TO {high}" 4366 4367 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4368 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4369 tables = f" {self.expressions(expression)}" 4370 4371 exists = " IF EXISTS" if expression.args.get("exists") else "" 4372 4373 on_cluster = self.sql(expression, "cluster") 4374 on_cluster = f" {on_cluster}" if on_cluster else "" 4375 4376 identity = self.sql(expression, "identity") 4377 identity = f" {identity} IDENTITY" if identity else "" 4378 4379 option = self.sql(expression, "option") 4380 option = f" {option}" if option else "" 4381 4382 partition = self.sql(expression, "partition") 4383 partition = f" {partition}" if partition else "" 4384 4385 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4386 4387 # This transpiles T-SQL's CONVERT function 4388 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4389 def convert_sql(self, expression: exp.Convert) -> str: 4390 to = expression.this 4391 value = expression.expression 4392 style = expression.args.get("style") 4393 safe = expression.args.get("safe") 4394 strict = expression.args.get("strict") 4395 4396 if not to or not value: 4397 return "" 4398 4399 # Retrieve length of datatype and override to default if not specified 4400 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4401 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4402 4403 transformed: t.Optional[exp.Expression] = None 4404 cast = exp.Cast if strict else exp.TryCast 4405 4406 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4407 if isinstance(style, exp.Literal) and style.is_int: 4408 from sqlglot.dialects.tsql import TSQL 4409 4410 style_value = style.name 4411 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4412 if not converted_style: 4413 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4414 4415 fmt = exp.Literal.string(converted_style) 4416 4417 if to.this == exp.DataType.Type.DATE: 4418 transformed = exp.StrToDate(this=value, format=fmt) 4419 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4420 transformed = exp.StrToTime(this=value, format=fmt) 4421 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4422 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4423 elif to.this == exp.DataType.Type.TEXT: 4424 transformed = exp.TimeToStr(this=value, format=fmt) 4425 4426 if not transformed: 4427 transformed = cast(this=value, to=to, safe=safe) 4428 4429 return self.sql(transformed) 4430 4431 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4432 this = expression.this 4433 if isinstance(this, exp.JSONPathWildcard): 4434 this = self.json_path_part(this) 4435 return f".{this}" if this else "" 4436 4437 if self.SAFE_JSON_PATH_KEY_RE.match(this): 4438 return f".{this}" 4439 4440 this = self.json_path_part(this) 4441 return ( 4442 f"[{this}]" 4443 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4444 else f".{this}" 4445 ) 4446 4447 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4448 this = self.json_path_part(expression.this) 4449 return f"[{this}]" if this else "" 4450 4451 def _simplify_unless_literal(self, expression: E) -> E: 4452 if not isinstance(expression, exp.Literal): 4453 from sqlglot.optimizer.simplify import simplify 4454 4455 expression = simplify(expression, dialect=self.dialect) 4456 4457 return expression 4458 4459 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4460 this = expression.this 4461 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4462 self.unsupported( 4463 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4464 ) 4465 return self.sql(this) 4466 4467 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4468 # The first modifier here will be the one closest to the AggFunc's arg 4469 mods = sorted( 4470 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4471 key=lambda x: 0 4472 if isinstance(x, exp.HavingMax) 4473 else (1 if isinstance(x, exp.Order) else 2), 4474 ) 4475 4476 if mods: 4477 mod = mods[0] 4478 this = expression.__class__(this=mod.this.copy()) 4479 this.meta["inline"] = True 4480 mod.this.replace(this) 4481 return self.sql(expression.this) 4482 4483 agg_func = expression.find(exp.AggFunc) 4484 4485 if agg_func: 4486 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4487 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4488 4489 return f"{self.sql(expression, 'this')} {text}" 4490 4491 def _replace_line_breaks(self, string: str) -> str: 4492 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4493 if self.pretty: 4494 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4495 return string 4496 4497 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4498 option = self.sql(expression, "this") 4499 4500 if expression.expressions: 4501 upper = option.upper() 4502 4503 # Snowflake FILE_FORMAT options are separated by whitespace 4504 sep = " " if upper == "FILE_FORMAT" else ", " 4505 4506 # Databricks copy/format options do not set their list of values with EQ 4507 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4508 values = self.expressions(expression, flat=True, sep=sep) 4509 return f"{option}{op}({values})" 4510 4511 value = self.sql(expression, "expression") 4512 4513 if not value: 4514 return option 4515 4516 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4517 4518 return f"{option}{op}{value}" 4519 4520 def credentials_sql(self, expression: exp.Credentials) -> str: 4521 cred_expr = expression.args.get("credentials") 4522 if isinstance(cred_expr, exp.Literal): 4523 # Redshift case: CREDENTIALS <string> 4524 credentials = self.sql(expression, "credentials") 4525 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4526 else: 4527 # Snowflake case: CREDENTIALS = (...) 4528 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4529 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4530 4531 storage = self.sql(expression, "storage") 4532 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4533 4534 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4535 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4536 4537 iam_role = self.sql(expression, "iam_role") 4538 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4539 4540 region = self.sql(expression, "region") 4541 region = f" REGION {region}" if region else "" 4542 4543 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4544 4545 def copy_sql(self, expression: exp.Copy) -> str: 4546 this = self.sql(expression, "this") 4547 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4548 4549 credentials = self.sql(expression, "credentials") 4550 credentials = self.seg(credentials) if credentials else "" 4551 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4552 files = self.expressions(expression, key="files", flat=True) 4553 4554 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4555 params = self.expressions( 4556 expression, 4557 key="params", 4558 sep=sep, 4559 new_line=True, 4560 skip_last=True, 4561 skip_first=True, 4562 indent=self.COPY_PARAMS_ARE_WRAPPED, 4563 ) 4564 4565 if params: 4566 if self.COPY_PARAMS_ARE_WRAPPED: 4567 params = f" WITH ({params})" 4568 elif not self.pretty: 4569 params = f" {params}" 4570 4571 return f"COPY{this}{kind} {files}{credentials}{params}" 4572 4573 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4574 return "" 4575 4576 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4577 on_sql = "ON" if expression.args.get("on") else "OFF" 4578 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4579 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4580 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4581 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4582 4583 if filter_col or retention_period: 4584 on_sql = self.func("ON", filter_col, retention_period) 4585 4586 return f"DATA_DELETION={on_sql}" 4587 4588 def maskingpolicycolumnconstraint_sql( 4589 self, expression: exp.MaskingPolicyColumnConstraint 4590 ) -> str: 4591 this = self.sql(expression, "this") 4592 expressions = self.expressions(expression, flat=True) 4593 expressions = f" USING ({expressions})" if expressions else "" 4594 return f"MASKING POLICY {this}{expressions}" 4595 4596 def gapfill_sql(self, expression: exp.GapFill) -> str: 4597 this = self.sql(expression, "this") 4598 this = f"TABLE {this}" 4599 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4600 4601 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4602 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4603 4604 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4605 this = self.sql(expression, "this") 4606 expr = expression.expression 4607 4608 if isinstance(expr, exp.Func): 4609 # T-SQL's CLR functions are case sensitive 4610 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4611 else: 4612 expr = self.sql(expression, "expression") 4613 4614 return self.scope_resolution(expr, this) 4615 4616 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4617 if self.PARSE_JSON_NAME is None: 4618 return self.sql(expression.this) 4619 4620 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4621 4622 def rand_sql(self, expression: exp.Rand) -> str: 4623 lower = self.sql(expression, "lower") 4624 upper = self.sql(expression, "upper") 4625 4626 if lower and upper: 4627 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4628 return self.func("RAND", expression.this) 4629 4630 def changes_sql(self, expression: exp.Changes) -> str: 4631 information = self.sql(expression, "information") 4632 information = f"INFORMATION => {information}" 4633 at_before = self.sql(expression, "at_before") 4634 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4635 end = self.sql(expression, "end") 4636 end = f"{self.seg('')}{end}" if end else "" 4637 4638 return f"CHANGES ({information}){at_before}{end}" 4639 4640 def pad_sql(self, expression: exp.Pad) -> str: 4641 prefix = "L" if expression.args.get("is_left") else "R" 4642 4643 fill_pattern = self.sql(expression, "fill_pattern") or None 4644 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4645 fill_pattern = "' '" 4646 4647 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4648 4649 def summarize_sql(self, expression: exp.Summarize) -> str: 4650 table = " TABLE" if expression.args.get("table") else "" 4651 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4652 4653 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4654 generate_series = exp.GenerateSeries(**expression.args) 4655 4656 parent = expression.parent 4657 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4658 parent = parent.parent 4659 4660 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4661 return self.sql(exp.Unnest(expressions=[generate_series])) 4662 4663 if isinstance(parent, exp.Select): 4664 self.unsupported("GenerateSeries projection unnesting is not supported.") 4665 4666 return self.sql(generate_series) 4667 4668 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4669 exprs = expression.expressions 4670 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4671 if len(exprs) == 0: 4672 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4673 else: 4674 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4675 else: 4676 rhs = self.expressions(expression) # type: ignore 4677 4678 return self.func(name, expression.this, rhs or None) 4679 4680 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4681 if self.SUPPORTS_CONVERT_TIMEZONE: 4682 return self.function_fallback_sql(expression) 4683 4684 source_tz = expression.args.get("source_tz") 4685 target_tz = expression.args.get("target_tz") 4686 timestamp = expression.args.get("timestamp") 4687 4688 if source_tz and timestamp: 4689 timestamp = exp.AtTimeZone( 4690 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4691 ) 4692 4693 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4694 4695 return self.sql(expr) 4696 4697 def json_sql(self, expression: exp.JSON) -> str: 4698 this = self.sql(expression, "this") 4699 this = f" {this}" if this else "" 4700 4701 _with = expression.args.get("with") 4702 4703 if _with is None: 4704 with_sql = "" 4705 elif not _with: 4706 with_sql = " WITHOUT" 4707 else: 4708 with_sql = " WITH" 4709 4710 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4711 4712 return f"JSON{this}{with_sql}{unique_sql}" 4713 4714 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4715 def _generate_on_options(arg: t.Any) -> str: 4716 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4717 4718 path = self.sql(expression, "path") 4719 returning = self.sql(expression, "returning") 4720 returning = f" RETURNING {returning}" if returning else "" 4721 4722 on_condition = self.sql(expression, "on_condition") 4723 on_condition = f" {on_condition}" if on_condition else "" 4724 4725 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4726 4727 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4728 else_ = "ELSE " if expression.args.get("else_") else "" 4729 condition = self.sql(expression, "expression") 4730 condition = f"WHEN {condition} THEN " if condition else else_ 4731 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4732 return f"{condition}{insert}" 4733 4734 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4735 kind = self.sql(expression, "kind") 4736 expressions = self.seg(self.expressions(expression, sep=" ")) 4737 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4738 return res 4739 4740 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4741 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4742 empty = expression.args.get("empty") 4743 empty = ( 4744 f"DEFAULT {empty} ON EMPTY" 4745 if isinstance(empty, exp.Expression) 4746 else self.sql(expression, "empty") 4747 ) 4748 4749 error = expression.args.get("error") 4750 error = ( 4751 f"DEFAULT {error} ON ERROR" 4752 if isinstance(error, exp.Expression) 4753 else self.sql(expression, "error") 4754 ) 4755 4756 if error and empty: 4757 error = ( 4758 f"{empty} {error}" 4759 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4760 else f"{error} {empty}" 4761 ) 4762 empty = "" 4763 4764 null = self.sql(expression, "null") 4765 4766 return f"{empty}{error}{null}" 4767 4768 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4769 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4770 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4771 4772 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4773 this = self.sql(expression, "this") 4774 path = self.sql(expression, "path") 4775 4776 passing = self.expressions(expression, "passing") 4777 passing = f" PASSING {passing}" if passing else "" 4778 4779 on_condition = self.sql(expression, "on_condition") 4780 on_condition = f" {on_condition}" if on_condition else "" 4781 4782 path = f"{path}{passing}{on_condition}" 4783 4784 return self.func("JSON_EXISTS", this, path) 4785 4786 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4787 array_agg = self.function_fallback_sql(expression) 4788 4789 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4790 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4791 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4792 parent = expression.parent 4793 if isinstance(parent, exp.Filter): 4794 parent_cond = parent.expression.this 4795 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4796 else: 4797 this = expression.this 4798 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4799 if this.find(exp.Column): 4800 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4801 this_sql = ( 4802 self.expressions(this) 4803 if isinstance(this, exp.Distinct) 4804 else self.sql(expression, "this") 4805 ) 4806 4807 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4808 4809 return array_agg 4810 4811 def apply_sql(self, expression: exp.Apply) -> str: 4812 this = self.sql(expression, "this") 4813 expr = self.sql(expression, "expression") 4814 4815 return f"{this} APPLY({expr})" 4816 4817 def _grant_or_revoke_sql( 4818 self, 4819 expression: exp.Grant | exp.Revoke, 4820 keyword: str, 4821 preposition: str, 4822 grant_option_prefix: str = "", 4823 grant_option_suffix: str = "", 4824 ) -> str: 4825 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4826 4827 kind = self.sql(expression, "kind") 4828 kind = f" {kind}" if kind else "" 4829 4830 securable = self.sql(expression, "securable") 4831 securable = f" {securable}" if securable else "" 4832 4833 principals = self.expressions(expression, key="principals", flat=True) 4834 4835 if not expression.args.get("grant_option"): 4836 grant_option_prefix = grant_option_suffix = "" 4837 4838 # cascade for revoke only 4839 cascade = self.sql(expression, "cascade") 4840 cascade = f" {cascade}" if cascade else "" 4841 4842 return f"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}" 4843 4844 def grant_sql(self, expression: exp.Grant) -> str: 4845 return self._grant_or_revoke_sql( 4846 expression, 4847 keyword="GRANT", 4848 preposition="TO", 4849 grant_option_suffix=" WITH GRANT OPTION", 4850 ) 4851 4852 def revoke_sql(self, expression: exp.Revoke) -> str: 4853 return self._grant_or_revoke_sql( 4854 expression, 4855 keyword="REVOKE", 4856 preposition="FROM", 4857 grant_option_prefix="GRANT OPTION FOR ", 4858 ) 4859 4860 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4861 this = self.sql(expression, "this") 4862 columns = self.expressions(expression, flat=True) 4863 columns = f"({columns})" if columns else "" 4864 4865 return f"{this}{columns}" 4866 4867 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4868 this = self.sql(expression, "this") 4869 4870 kind = self.sql(expression, "kind") 4871 kind = f"{kind} " if kind else "" 4872 4873 return f"{kind}{this}" 4874 4875 def columns_sql(self, expression: exp.Columns): 4876 func = self.function_fallback_sql(expression) 4877 if expression.args.get("unpack"): 4878 func = f"*{func}" 4879 4880 return func 4881 4882 def overlay_sql(self, expression: exp.Overlay): 4883 this = self.sql(expression, "this") 4884 expr = self.sql(expression, "expression") 4885 from_sql = self.sql(expression, "from") 4886 for_sql = self.sql(expression, "for") 4887 for_sql = f" FOR {for_sql}" if for_sql else "" 4888 4889 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4890 4891 @unsupported_args("format") 4892 def todouble_sql(self, expression: exp.ToDouble) -> str: 4893 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4894 4895 def string_sql(self, expression: exp.String) -> str: 4896 this = expression.this 4897 zone = expression.args.get("zone") 4898 4899 if zone: 4900 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4901 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4902 # set for source_tz to transpile the time conversion before the STRING cast 4903 this = exp.ConvertTimezone( 4904 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4905 ) 4906 4907 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4908 4909 def median_sql(self, expression: exp.Median): 4910 if not self.SUPPORTS_MEDIAN: 4911 return self.sql( 4912 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4913 ) 4914 4915 return self.function_fallback_sql(expression) 4916 4917 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4918 filler = self.sql(expression, "this") 4919 filler = f" {filler}" if filler else "" 4920 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4921 return f"TRUNCATE{filler} {with_count}" 4922 4923 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4924 if self.SUPPORTS_UNIX_SECONDS: 4925 return self.function_fallback_sql(expression) 4926 4927 start_ts = exp.cast( 4928 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4929 ) 4930 4931 return self.sql( 4932 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4933 ) 4934 4935 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4936 dim = expression.expression 4937 4938 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4939 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4940 if not (dim.is_int and dim.name == "1"): 4941 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4942 dim = None 4943 4944 # If dimension is required but not specified, default initialize it 4945 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4946 dim = exp.Literal.number(1) 4947 4948 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4949 4950 def attach_sql(self, expression: exp.Attach) -> str: 4951 this = self.sql(expression, "this") 4952 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4953 expressions = self.expressions(expression) 4954 expressions = f" ({expressions})" if expressions else "" 4955 4956 return f"ATTACH{exists_sql} {this}{expressions}" 4957 4958 def detach_sql(self, expression: exp.Detach) -> str: 4959 this = self.sql(expression, "this") 4960 # the DATABASE keyword is required if IF EXISTS is set 4961 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4962 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4963 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4964 4965 return f"DETACH{exists_sql} {this}" 4966 4967 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4968 this = self.sql(expression, "this") 4969 value = self.sql(expression, "expression") 4970 value = f" {value}" if value else "" 4971 return f"{this}{value}" 4972 4973 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4974 return ( 4975 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4976 ) 4977 4978 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4979 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4980 encode = f"{encode} {self.sql(expression, 'this')}" 4981 4982 properties = expression.args.get("properties") 4983 if properties: 4984 encode = f"{encode} {self.properties(properties)}" 4985 4986 return encode 4987 4988 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4989 this = self.sql(expression, "this") 4990 include = f"INCLUDE {this}" 4991 4992 column_def = self.sql(expression, "column_def") 4993 if column_def: 4994 include = f"{include} {column_def}" 4995 4996 alias = self.sql(expression, "alias") 4997 if alias: 4998 include = f"{include} AS {alias}" 4999 5000 return include 5001 5002 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 5003 name = f"NAME {self.sql(expression, 'this')}" 5004 return self.func("XMLELEMENT", name, *expression.expressions) 5005 5006 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 5007 this = self.sql(expression, "this") 5008 expr = self.sql(expression, "expression") 5009 expr = f"({expr})" if expr else "" 5010 return f"{this}{expr}" 5011 5012 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5013 partitions = self.expressions(expression, "partition_expressions") 5014 create = self.expressions(expression, "create_expressions") 5015 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 5016 5017 def partitionbyrangepropertydynamic_sql( 5018 self, expression: exp.PartitionByRangePropertyDynamic 5019 ) -> str: 5020 start = self.sql(expression, "start") 5021 end = self.sql(expression, "end") 5022 5023 every = expression.args["every"] 5024 if isinstance(every, exp.Interval) and every.this.is_string: 5025 every.this.replace(exp.Literal.number(every.name)) 5026 5027 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 5028 5029 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 5030 name = self.sql(expression, "this") 5031 values = self.expressions(expression, flat=True) 5032 5033 return f"NAME {name} VALUE {values}" 5034 5035 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 5036 kind = self.sql(expression, "kind") 5037 sample = self.sql(expression, "sample") 5038 return f"SAMPLE {sample} {kind}" 5039 5040 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5041 kind = self.sql(expression, "kind") 5042 option = self.sql(expression, "option") 5043 option = f" {option}" if option else "" 5044 this = self.sql(expression, "this") 5045 this = f" {this}" if this else "" 5046 columns = self.expressions(expression) 5047 columns = f" {columns}" if columns else "" 5048 return f"{kind}{option} STATISTICS{this}{columns}" 5049 5050 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5051 this = self.sql(expression, "this") 5052 columns = self.expressions(expression) 5053 inner_expression = self.sql(expression, "expression") 5054 inner_expression = f" {inner_expression}" if inner_expression else "" 5055 update_options = self.sql(expression, "update_options") 5056 update_options = f" {update_options} UPDATE" if update_options else "" 5057 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 5058 5059 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 5060 kind = self.sql(expression, "kind") 5061 kind = f" {kind}" if kind else "" 5062 return f"DELETE{kind} STATISTICS" 5063 5064 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 5065 inner_expression = self.sql(expression, "expression") 5066 return f"LIST CHAINED ROWS{inner_expression}" 5067 5068 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5069 kind = self.sql(expression, "kind") 5070 this = self.sql(expression, "this") 5071 this = f" {this}" if this else "" 5072 inner_expression = self.sql(expression, "expression") 5073 return f"VALIDATE {kind}{this}{inner_expression}" 5074 5075 def analyze_sql(self, expression: exp.Analyze) -> str: 5076 options = self.expressions(expression, key="options", sep=" ") 5077 options = f" {options}" if options else "" 5078 kind = self.sql(expression, "kind") 5079 kind = f" {kind}" if kind else "" 5080 this = self.sql(expression, "this") 5081 this = f" {this}" if this else "" 5082 mode = self.sql(expression, "mode") 5083 mode = f" {mode}" if mode else "" 5084 properties = self.sql(expression, "properties") 5085 properties = f" {properties}" if properties else "" 5086 partition = self.sql(expression, "partition") 5087 partition = f" {partition}" if partition else "" 5088 inner_expression = self.sql(expression, "expression") 5089 inner_expression = f" {inner_expression}" if inner_expression else "" 5090 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 5091 5092 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5093 this = self.sql(expression, "this") 5094 namespaces = self.expressions(expression, key="namespaces") 5095 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5096 passing = self.expressions(expression, key="passing") 5097 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5098 columns = self.expressions(expression, key="columns") 5099 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5100 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5101 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 5102 5103 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 5104 this = self.sql(expression, "this") 5105 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 5106 5107 def export_sql(self, expression: exp.Export) -> str: 5108 this = self.sql(expression, "this") 5109 connection = self.sql(expression, "connection") 5110 connection = f"WITH CONNECTION {connection} " if connection else "" 5111 options = self.sql(expression, "options") 5112 return f"EXPORT DATA {connection}{options} AS {this}" 5113 5114 def declare_sql(self, expression: exp.Declare) -> str: 5115 return f"DECLARE {self.expressions(expression, flat=True)}" 5116 5117 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5118 variable = self.sql(expression, "this") 5119 default = self.sql(expression, "default") 5120 default = f" = {default}" if default else "" 5121 5122 kind = self.sql(expression, "kind") 5123 if isinstance(expression.args.get("kind"), exp.Schema): 5124 kind = f"TABLE {kind}" 5125 5126 return f"{variable} AS {kind}{default}" 5127 5128 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5129 kind = self.sql(expression, "kind") 5130 this = self.sql(expression, "this") 5131 set = self.sql(expression, "expression") 5132 using = self.sql(expression, "using") 5133 using = f" USING {using}" if using else "" 5134 5135 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5136 5137 return f"{kind_sql} {this} SET {set}{using}" 5138 5139 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5140 params = self.expressions(expression, key="params", flat=True) 5141 return self.func(expression.name, *expression.expressions) + f"({params})" 5142 5143 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5144 return self.func(expression.name, *expression.expressions) 5145 5146 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5147 return self.anonymousaggfunc_sql(expression) 5148 5149 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5150 return self.parameterizedagg_sql(expression) 5151 5152 def show_sql(self, expression: exp.Show) -> str: 5153 self.unsupported("Unsupported SHOW statement") 5154 return "" 5155 5156 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5157 # Snowflake GET/PUT statements: 5158 # PUT <file> <internalStage> <properties> 5159 # GET <internalStage> <file> <properties> 5160 props = expression.args.get("properties") 5161 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5162 this = self.sql(expression, "this") 5163 target = self.sql(expression, "target") 5164 5165 if isinstance(expression, exp.Put): 5166 return f"PUT {this} {target}{props_sql}" 5167 else: 5168 return f"GET {target} {this}{props_sql}" 5169 5170 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5171 this = self.sql(expression, "this") 5172 expr = self.sql(expression, "expression") 5173 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5174 return f"TRANSLATE({this} USING {expr}{with_error})" 5175 5176 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5177 if self.SUPPORTS_DECODE_CASE: 5178 return self.func("DECODE", *expression.expressions) 5179 5180 expression, *expressions = expression.expressions 5181 5182 ifs = [] 5183 for search, result in zip(expressions[::2], expressions[1::2]): 5184 if isinstance(search, exp.Literal): 5185 ifs.append(exp.If(this=expression.eq(search), true=result)) 5186 elif isinstance(search, exp.Null): 5187 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5188 else: 5189 if isinstance(search, exp.Binary): 5190 search = exp.paren(search) 5191 5192 cond = exp.or_( 5193 expression.eq(search), 5194 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5195 copy=False, 5196 ) 5197 ifs.append(exp.If(this=cond, true=result)) 5198 5199 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5200 return self.sql(case) 5201 5202 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5203 this = self.sql(expression, "this") 5204 this = self.seg(this, sep="") 5205 dimensions = self.expressions( 5206 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5207 ) 5208 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5209 metrics = self.expressions( 5210 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5211 ) 5212 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5213 where = self.sql(expression, "where") 5214 where = self.seg(f"WHERE {where}") if where else "" 5215 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}" 5216 5217 def getextract_sql(self, expression: exp.GetExtract) -> str: 5218 this = expression.this 5219 expr = expression.expression 5220 5221 if not this.type or not expression.type: 5222 from sqlglot.optimizer.annotate_types import annotate_types 5223 5224 this = annotate_types(this, dialect=self.dialect) 5225 5226 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5227 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5228 5229 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr))) 5230 5231 def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str: 5232 return self.sql( 5233 exp.DateAdd( 5234 this=exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 5235 expression=expression.this, 5236 unit=exp.var("DAY"), 5237 ) 5238 ) 5239 5240 def space_sql(self: Generator, expression: exp.Space) -> str: 5241 return self.sql(exp.Repeat(this=exp.Literal.string(" "), times=expression.this)) 5242 5243 def buildproperty_sql(self, expression: exp.BuildProperty) -> str: 5244 return f"BUILD {self.sql(expression, 'this')}" 5245 5246 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5247 method = self.sql(expression, "method") 5248 kind = expression.args.get("kind") 5249 if not kind: 5250 return f"REFRESH {method}" 5251 5252 every = self.sql(expression, "every") 5253 unit = self.sql(expression, "unit") 5254 every = f" EVERY {every} {unit}" if every else "" 5255 starts = self.sql(expression, "starts") 5256 starts = f" STARTS {starts}" if starts else "" 5257 5258 return f"REFRESH {method} ON {kind}{every}{starts}"
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
724 def __init__( 725 self, 726 pretty: t.Optional[bool] = None, 727 identify: str | bool = False, 728 normalize: bool = False, 729 pad: int = 2, 730 indent: int = 2, 731 normalize_functions: t.Optional[str | bool] = None, 732 unsupported_level: ErrorLevel = ErrorLevel.WARN, 733 max_unsupported: int = 3, 734 leading_comma: bool = False, 735 max_text_width: int = 80, 736 comments: bool = True, 737 dialect: DialectType = None, 738 ): 739 import sqlglot 740 from sqlglot.dialects import Dialect 741 742 self.pretty = pretty if pretty is not None else sqlglot.pretty 743 self.identify = identify 744 self.normalize = normalize 745 self.pad = pad 746 self._indent = indent 747 self.unsupported_level = unsupported_level 748 self.max_unsupported = max_unsupported 749 self.leading_comma = leading_comma 750 self.max_text_width = max_text_width 751 self.comments = comments 752 self.dialect = Dialect.get_or_raise(dialect) 753 754 # This is both a Dialect property and a Generator argument, so we prioritize the latter 755 self.normalize_functions = ( 756 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 757 ) 758 759 self.unsupported_messages: t.List[str] = [] 760 self._escaped_quote_end: str = ( 761 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 762 ) 763 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 764 765 self._next_name = name_sequence("_t") 766 767 self._identifier_start = self.dialect.IDENTIFIER_START 768 self._identifier_end = self.dialect.IDENTIFIER_END 769 770 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.JSONPathRoot'>, <class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathKey'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathFilter'>, <class 'sqlglot.expressions.JSONPathUnion'>, <class 'sqlglot.expressions.JSONPathSubscript'>, <class 'sqlglot.expressions.JSONPathSelector'>, <class 'sqlglot.expressions.JSONPathSlice'>, <class 'sqlglot.expressions.JSONPathScript'>}
TYPE_MAPPING =
{<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Describe'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Order'>, <class 'sqlglot.expressions.Group'>, <class 'sqlglot.expressions.Having'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.NCHAR: 'NCHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>}
772 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 773 """ 774 Generates the SQL string corresponding to the given syntax tree. 775 776 Args: 777 expression: The syntax tree. 778 copy: Whether to copy the expression. The generator performs mutations so 779 it is safer to copy. 780 781 Returns: 782 The SQL string corresponding to `expression`. 783 """ 784 if copy: 785 expression = expression.copy() 786 787 expression = self.preprocess(expression) 788 789 self.unsupported_messages = [] 790 sql = self.sql(expression).strip() 791 792 if self.pretty: 793 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 794 795 if self.unsupported_level == ErrorLevel.IGNORE: 796 return sql 797 798 if self.unsupported_level == ErrorLevel.WARN: 799 for msg in self.unsupported_messages: 800 logger.warning(msg) 801 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 802 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 803 804 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:
806 def preprocess(self, expression: exp.Expression) -> exp.Expression: 807 """Apply generic preprocessing transformations to a given expression.""" 808 expression = self._move_ctes_to_top_level(expression) 809 810 if self.ENSURE_BOOLS: 811 from sqlglot.transforms import ensure_bools 812 813 expression = ensure_bools(expression) 814 815 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
839 def sanitize_comment(self, comment: str) -> str: 840 comment = " " + comment if comment[0].strip() else comment 841 comment = comment + " " if comment[-1].strip() else comment 842 843 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 844 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 845 comment = comment.replace("*/", "* /") 846 847 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
849 def maybe_comment( 850 self, 851 sql: str, 852 expression: t.Optional[exp.Expression] = None, 853 comments: t.Optional[t.List[str]] = None, 854 separated: bool = False, 855 ) -> str: 856 comments = ( 857 ((expression and expression.comments) if comments is None else comments) # type: ignore 858 if self.comments 859 else None 860 ) 861 862 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 863 return sql 864 865 comments_sql = " ".join( 866 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 867 ) 868 869 if not comments_sql: 870 return sql 871 872 comments_sql = self._replace_line_breaks(comments_sql) 873 874 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 875 return ( 876 f"{self.sep()}{comments_sql}{sql}" 877 if not sql or sql[0].isspace() 878 else f"{comments_sql}{self.sep()}{sql}" 879 ) 880 881 return f"{sql} {comments_sql}"
883 def wrap(self, expression: exp.Expression | str) -> str: 884 this_sql = ( 885 self.sql(expression) 886 if isinstance(expression, exp.UNWRAPPED_QUERIES) 887 else self.sql(expression, "this") 888 ) 889 if not this_sql: 890 return "()" 891 892 this_sql = self.indent(this_sql, level=1, pad=0) 893 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:
909 def indent( 910 self, 911 sql: str, 912 level: int = 0, 913 pad: t.Optional[int] = None, 914 skip_first: bool = False, 915 skip_last: bool = False, 916 ) -> str: 917 if not self.pretty or not sql: 918 return sql 919 920 pad = self.pad if pad is None else pad 921 lines = sql.split("\n") 922 923 return "\n".join( 924 ( 925 line 926 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 927 else f"{' ' * (level * self._indent + pad)}{line}" 928 ) 929 for i, line in enumerate(lines) 930 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
932 def sql( 933 self, 934 expression: t.Optional[str | exp.Expression], 935 key: t.Optional[str] = None, 936 comment: bool = True, 937 ) -> str: 938 if not expression: 939 return "" 940 941 if isinstance(expression, str): 942 return expression 943 944 if key: 945 value = expression.args.get(key) 946 if value: 947 return self.sql(value) 948 return "" 949 950 transform = self.TRANSFORMS.get(expression.__class__) 951 952 if callable(transform): 953 sql = transform(self, expression) 954 elif isinstance(expression, exp.Expression): 955 exp_handler_name = f"{expression.key}_sql" 956 957 if hasattr(self, exp_handler_name): 958 sql = getattr(self, exp_handler_name)(expression) 959 elif isinstance(expression, exp.Func): 960 sql = self.function_fallback_sql(expression) 961 elif isinstance(expression, exp.Property): 962 sql = self.property_sql(expression) 963 else: 964 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 965 else: 966 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 967 968 return self.maybe_comment(sql, expression) if self.comments and comment else sql
975 def cache_sql(self, expression: exp.Cache) -> str: 976 lazy = " LAZY" if expression.args.get("lazy") else "" 977 table = self.sql(expression, "this") 978 options = expression.args.get("options") 979 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 980 sql = self.sql(expression, "expression") 981 sql = f" AS{self.sep()}{sql}" if sql else "" 982 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 983 return self.prepend_ctes(expression, sql)
985 def characterset_sql(self, expression: exp.CharacterSet) -> str: 986 if isinstance(expression.parent, exp.Cast): 987 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 988 default = "DEFAULT " if expression.args.get("default") else "" 989 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
1003 def column_sql(self, expression: exp.Column) -> str: 1004 join_mark = " (+)" if expression.args.get("join_mark") else "" 1005 1006 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 1007 join_mark = "" 1008 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1009 1010 return f"{self.column_parts(expression)}{join_mark}"
1018 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1019 column = self.sql(expression, "this") 1020 kind = self.sql(expression, "kind") 1021 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1022 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1023 kind = f"{sep}{kind}" if kind else "" 1024 constraints = f" {constraints}" if constraints else "" 1025 position = self.sql(expression, "position") 1026 position = f" {position}" if position else "" 1027 1028 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1029 kind = "" 1030 1031 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1038 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1039 this = self.sql(expression, "this") 1040 if expression.args.get("not_null"): 1041 persisted = " PERSISTED NOT NULL" 1042 elif expression.args.get("persisted"): 1043 persisted = " PERSISTED" 1044 else: 1045 persisted = "" 1046 1047 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1060 def generatedasidentitycolumnconstraint_sql( 1061 self, expression: exp.GeneratedAsIdentityColumnConstraint 1062 ) -> str: 1063 this = "" 1064 if expression.this is not None: 1065 on_null = " ON NULL" if expression.args.get("on_null") else "" 1066 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1067 1068 start = expression.args.get("start") 1069 start = f"START WITH {start}" if start else "" 1070 increment = expression.args.get("increment") 1071 increment = f" INCREMENT BY {increment}" if increment else "" 1072 minvalue = expression.args.get("minvalue") 1073 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1074 maxvalue = expression.args.get("maxvalue") 1075 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1076 cycle = expression.args.get("cycle") 1077 cycle_sql = "" 1078 1079 if cycle is not None: 1080 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1081 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1082 1083 sequence_opts = "" 1084 if start or increment or cycle_sql: 1085 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1086 sequence_opts = f" ({sequence_opts.strip()})" 1087 1088 expr = self.sql(expression, "expression") 1089 expr = f"({expr})" if expr else "IDENTITY" 1090 1091 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1093 def generatedasrowcolumnconstraint_sql( 1094 self, expression: exp.GeneratedAsRowColumnConstraint 1095 ) -> str: 1096 start = "START" if expression.args.get("start") else "END" 1097 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1098 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:
1108 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1109 desc = expression.args.get("desc") 1110 if desc is not None: 1111 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1112 options = self.expressions(expression, key="options", flat=True, sep=" ") 1113 options = f" {options}" if options else "" 1114 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1116 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1117 this = self.sql(expression, "this") 1118 this = f" {this}" if this else "" 1119 index_type = expression.args.get("index_type") 1120 index_type = f" USING {index_type}" if index_type else "" 1121 on_conflict = self.sql(expression, "on_conflict") 1122 on_conflict = f" {on_conflict}" if on_conflict else "" 1123 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1124 options = self.expressions(expression, key="options", flat=True, sep=" ") 1125 options = f" {options}" if options else "" 1126 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1131 def create_sql(self, expression: exp.Create) -> str: 1132 kind = self.sql(expression, "kind") 1133 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1134 properties = expression.args.get("properties") 1135 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1136 1137 this = self.createable_sql(expression, properties_locs) 1138 1139 properties_sql = "" 1140 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1141 exp.Properties.Location.POST_WITH 1142 ): 1143 props_ast = exp.Properties( 1144 expressions=[ 1145 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1146 *properties_locs[exp.Properties.Location.POST_WITH], 1147 ] 1148 ) 1149 props_ast.parent = expression 1150 properties_sql = self.sql(props_ast) 1151 1152 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1153 properties_sql = self.sep() + properties_sql 1154 elif not self.pretty: 1155 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1156 properties_sql = f" {properties_sql}" 1157 1158 begin = " BEGIN" if expression.args.get("begin") else "" 1159 end = " END" if expression.args.get("end") else "" 1160 1161 expression_sql = self.sql(expression, "expression") 1162 if expression_sql: 1163 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1164 1165 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1166 postalias_props_sql = "" 1167 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1168 postalias_props_sql = self.properties( 1169 exp.Properties( 1170 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1171 ), 1172 wrapped=False, 1173 ) 1174 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1175 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1176 1177 postindex_props_sql = "" 1178 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1179 postindex_props_sql = self.properties( 1180 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1181 wrapped=False, 1182 prefix=" ", 1183 ) 1184 1185 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1186 indexes = f" {indexes}" if indexes else "" 1187 index_sql = indexes + postindex_props_sql 1188 1189 replace = " OR REPLACE" if expression.args.get("replace") else "" 1190 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1191 unique = " UNIQUE" if expression.args.get("unique") else "" 1192 1193 clustered = expression.args.get("clustered") 1194 if clustered is None: 1195 clustered_sql = "" 1196 elif clustered: 1197 clustered_sql = " CLUSTERED COLUMNSTORE" 1198 else: 1199 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1200 1201 postcreate_props_sql = "" 1202 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1203 postcreate_props_sql = self.properties( 1204 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1205 sep=" ", 1206 prefix=" ", 1207 wrapped=False, 1208 ) 1209 1210 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1211 1212 postexpression_props_sql = "" 1213 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1214 postexpression_props_sql = self.properties( 1215 exp.Properties( 1216 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1217 ), 1218 sep=" ", 1219 prefix=" ", 1220 wrapped=False, 1221 ) 1222 1223 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1224 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1225 no_schema_binding = ( 1226 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1227 ) 1228 1229 clone = self.sql(expression, "clone") 1230 clone = f" {clone}" if clone else "" 1231 1232 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1233 properties_expression = f"{expression_sql}{properties_sql}" 1234 else: 1235 properties_expression = f"{properties_sql}{expression_sql}" 1236 1237 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1238 return self.prepend_ctes(expression, expression_sql)
1240 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1241 start = self.sql(expression, "start") 1242 start = f"START WITH {start}" if start else "" 1243 increment = self.sql(expression, "increment") 1244 increment = f" INCREMENT BY {increment}" if increment else "" 1245 minvalue = self.sql(expression, "minvalue") 1246 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1247 maxvalue = self.sql(expression, "maxvalue") 1248 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1249 owned = self.sql(expression, "owned") 1250 owned = f" OWNED BY {owned}" if owned else "" 1251 1252 cache = expression.args.get("cache") 1253 if cache is None: 1254 cache_str = "" 1255 elif cache is True: 1256 cache_str = " CACHE" 1257 else: 1258 cache_str = f" CACHE {cache}" 1259 1260 options = self.expressions(expression, key="options", flat=True, sep=" ") 1261 options = f" {options}" if options else "" 1262 1263 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1265 def clone_sql(self, expression: exp.Clone) -> str: 1266 this = self.sql(expression, "this") 1267 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1268 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1269 return f"{shallow}{keyword} {this}"
1271 def describe_sql(self, expression: exp.Describe) -> str: 1272 style = expression.args.get("style") 1273 style = f" {style}" if style else "" 1274 partition = self.sql(expression, "partition") 1275 partition = f" {partition}" if partition else "" 1276 format = self.sql(expression, "format") 1277 format = f" {format}" if format else "" 1278 1279 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1291 def with_sql(self, expression: exp.With) -> str: 1292 sql = self.expressions(expression, flat=True) 1293 recursive = ( 1294 "RECURSIVE " 1295 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1296 else "" 1297 ) 1298 search = self.sql(expression, "search") 1299 search = f" {search}" if search else "" 1300 1301 return f"WITH {recursive}{sql}{search}"
1303 def cte_sql(self, expression: exp.CTE) -> str: 1304 alias = expression.args.get("alias") 1305 if alias: 1306 alias.add_comments(expression.pop_comments()) 1307 1308 alias_sql = self.sql(expression, "alias") 1309 1310 materialized = expression.args.get("materialized") 1311 if materialized is False: 1312 materialized = "NOT MATERIALIZED " 1313 elif materialized: 1314 materialized = "MATERIALIZED " 1315 1316 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1318 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1319 alias = self.sql(expression, "this") 1320 columns = self.expressions(expression, key="columns", flat=True) 1321 columns = f"({columns})" if columns else "" 1322 1323 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1324 columns = "" 1325 self.unsupported("Named columns are not supported in table alias.") 1326 1327 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1328 alias = self._next_name() 1329 1330 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1338 def hexstring_sql( 1339 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1340 ) -> str: 1341 this = self.sql(expression, "this") 1342 is_integer_type = expression.args.get("is_integer") 1343 1344 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1345 not self.dialect.HEX_START and not binary_function_repr 1346 ): 1347 # Integer representation will be returned if: 1348 # - The read dialect treats the hex value as integer literal but not the write 1349 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1350 return f"{int(this, 16)}" 1351 1352 if not is_integer_type: 1353 # Read dialect treats the hex value as BINARY/BLOB 1354 if binary_function_repr: 1355 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1356 return self.func(binary_function_repr, exp.Literal.string(this)) 1357 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1358 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1359 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1360 1361 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1369 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1370 this = self.sql(expression, "this") 1371 escape = expression.args.get("escape") 1372 1373 if self.dialect.UNICODE_START: 1374 escape_substitute = r"\\\1" 1375 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1376 else: 1377 escape_substitute = r"\\u\1" 1378 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1379 1380 if escape: 1381 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1382 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1383 else: 1384 escape_pattern = ESCAPED_UNICODE_RE 1385 escape_sql = "" 1386 1387 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1388 this = escape_pattern.sub(escape_substitute, this) 1389 1390 return f"{left_quote}{this}{right_quote}{escape_sql}"
1392 def rawstring_sql(self, expression: exp.RawString) -> str: 1393 string = expression.this 1394 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1395 string = string.replace("\\", "\\\\") 1396 1397 string = self.escape_str(string, escape_backslash=False) 1398 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1406 def datatype_sql(self, expression: exp.DataType) -> str: 1407 nested = "" 1408 values = "" 1409 interior = self.expressions(expression, flat=True) 1410 1411 type_value = expression.this 1412 if type_value in self.UNSUPPORTED_TYPES: 1413 self.unsupported( 1414 f"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}" 1415 ) 1416 1417 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1418 type_sql = self.sql(expression, "kind") 1419 else: 1420 type_sql = ( 1421 self.TYPE_MAPPING.get(type_value, type_value.value) 1422 if isinstance(type_value, exp.DataType.Type) 1423 else type_value 1424 ) 1425 1426 if interior: 1427 if expression.args.get("nested"): 1428 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1429 if expression.args.get("values") is not None: 1430 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1431 values = self.expressions(expression, key="values", flat=True) 1432 values = f"{delimiters[0]}{values}{delimiters[1]}" 1433 elif type_value == exp.DataType.Type.INTERVAL: 1434 nested = f" {interior}" 1435 else: 1436 nested = f"({interior})" 1437 1438 type_sql = f"{type_sql}{nested}{values}" 1439 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1440 exp.DataType.Type.TIMETZ, 1441 exp.DataType.Type.TIMESTAMPTZ, 1442 ): 1443 type_sql = f"{type_sql} WITH TIME ZONE" 1444 1445 return type_sql
1447 def directory_sql(self, expression: exp.Directory) -> str: 1448 local = "LOCAL " if expression.args.get("local") else "" 1449 row_format = self.sql(expression, "row_format") 1450 row_format = f" {row_format}" if row_format else "" 1451 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1453 def delete_sql(self, expression: exp.Delete) -> str: 1454 this = self.sql(expression, "this") 1455 this = f" FROM {this}" if this else "" 1456 using = self.sql(expression, "using") 1457 using = f" USING {using}" if using else "" 1458 cluster = self.sql(expression, "cluster") 1459 cluster = f" {cluster}" if cluster else "" 1460 where = self.sql(expression, "where") 1461 returning = self.sql(expression, "returning") 1462 limit = self.sql(expression, "limit") 1463 tables = self.expressions(expression, key="tables") 1464 tables = f" {tables}" if tables else "" 1465 if self.RETURNING_END: 1466 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1467 else: 1468 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1469 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1471 def drop_sql(self, expression: exp.Drop) -> str: 1472 this = self.sql(expression, "this") 1473 expressions = self.expressions(expression, flat=True) 1474 expressions = f" ({expressions})" if expressions else "" 1475 kind = expression.args["kind"] 1476 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1477 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1478 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1479 on_cluster = self.sql(expression, "cluster") 1480 on_cluster = f" {on_cluster}" if on_cluster else "" 1481 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1482 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1483 cascade = " CASCADE" if expression.args.get("cascade") else "" 1484 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1485 purge = " PURGE" if expression.args.get("purge") else "" 1486 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1488 def set_operation(self, expression: exp.SetOperation) -> str: 1489 op_type = type(expression) 1490 op_name = op_type.key.upper() 1491 1492 distinct = expression.args.get("distinct") 1493 if ( 1494 distinct is False 1495 and op_type in (exp.Except, exp.Intersect) 1496 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1497 ): 1498 self.unsupported(f"{op_name} ALL is not supported") 1499 1500 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1501 1502 if distinct is None: 1503 distinct = default_distinct 1504 if distinct is None: 1505 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1506 1507 if distinct is default_distinct: 1508 distinct_or_all = "" 1509 else: 1510 distinct_or_all = " DISTINCT" if distinct else " ALL" 1511 1512 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1513 side_kind = f"{side_kind} " if side_kind else "" 1514 1515 by_name = " BY NAME" if expression.args.get("by_name") else "" 1516 on = self.expressions(expression, key="on", flat=True) 1517 on = f" ON ({on})" if on else "" 1518 1519 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1521 def set_operations(self, expression: exp.SetOperation) -> str: 1522 if not self.SET_OP_MODIFIERS: 1523 limit = expression.args.get("limit") 1524 order = expression.args.get("order") 1525 1526 if limit or order: 1527 select = self._move_ctes_to_top_level( 1528 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1529 ) 1530 1531 if limit: 1532 select = select.limit(limit.pop(), copy=False) 1533 if order: 1534 select = select.order_by(order.pop(), copy=False) 1535 return self.sql(select) 1536 1537 sqls: t.List[str] = [] 1538 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1539 1540 while stack: 1541 node = stack.pop() 1542 1543 if isinstance(node, exp.SetOperation): 1544 stack.append(node.expression) 1545 stack.append( 1546 self.maybe_comment( 1547 self.set_operation(node), comments=node.comments, separated=True 1548 ) 1549 ) 1550 stack.append(node.this) 1551 else: 1552 sqls.append(self.sql(node)) 1553 1554 this = self.sep().join(sqls) 1555 this = self.query_modifiers(expression, this) 1556 return self.prepend_ctes(expression, this)
1558 def fetch_sql(self, expression: exp.Fetch) -> str: 1559 direction = expression.args.get("direction") 1560 direction = f" {direction}" if direction else "" 1561 count = self.sql(expression, "count") 1562 count = f" {count}" if count else "" 1563 limit_options = self.sql(expression, "limit_options") 1564 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1565 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1567 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1568 percent = " PERCENT" if expression.args.get("percent") else "" 1569 rows = " ROWS" if expression.args.get("rows") else "" 1570 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1571 if not with_ties and rows: 1572 with_ties = " ONLY" 1573 return f"{percent}{rows}{with_ties}"
1575 def filter_sql(self, expression: exp.Filter) -> str: 1576 if self.AGGREGATE_FILTER_SUPPORTED: 1577 this = self.sql(expression, "this") 1578 where = self.sql(expression, "expression").strip() 1579 return f"{this} FILTER({where})" 1580 1581 agg = expression.this 1582 agg_arg = agg.this 1583 cond = expression.expression.this 1584 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1585 return self.sql(agg)
1594 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1595 using = self.sql(expression, "using") 1596 using = f" USING {using}" if using else "" 1597 columns = self.expressions(expression, key="columns", flat=True) 1598 columns = f"({columns})" if columns else "" 1599 partition_by = self.expressions(expression, key="partition_by", flat=True) 1600 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1601 where = self.sql(expression, "where") 1602 include = self.expressions(expression, key="include", flat=True) 1603 if include: 1604 include = f" INCLUDE ({include})" 1605 with_storage = self.expressions(expression, key="with_storage", flat=True) 1606 with_storage = f" WITH ({with_storage})" if with_storage else "" 1607 tablespace = self.sql(expression, "tablespace") 1608 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1609 on = self.sql(expression, "on") 1610 on = f" ON {on}" if on else "" 1611 1612 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1614 def index_sql(self, expression: exp.Index) -> str: 1615 unique = "UNIQUE " if expression.args.get("unique") else "" 1616 primary = "PRIMARY " if expression.args.get("primary") else "" 1617 amp = "AMP " if expression.args.get("amp") else "" 1618 name = self.sql(expression, "this") 1619 name = f"{name} " if name else "" 1620 table = self.sql(expression, "table") 1621 table = f"{self.INDEX_ON} {table}" if table else "" 1622 1623 index = "INDEX " if not table else "" 1624 1625 params = self.sql(expression, "params") 1626 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1628 def identifier_sql(self, expression: exp.Identifier) -> str: 1629 text = expression.name 1630 lower = text.lower() 1631 text = lower if self.normalize and not expression.quoted else text 1632 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1633 if ( 1634 expression.quoted 1635 or self.dialect.can_identify(text, self.identify) 1636 or lower in self.RESERVED_KEYWORDS 1637 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1638 ): 1639 text = f"{self._identifier_start}{text}{self._identifier_end}" 1640 return text
1655 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1656 input_format = self.sql(expression, "input_format") 1657 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1658 output_format = self.sql(expression, "output_format") 1659 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1660 return self.sep().join((input_format, output_format))
1670 def properties_sql(self, expression: exp.Properties) -> str: 1671 root_properties = [] 1672 with_properties = [] 1673 1674 for p in expression.expressions: 1675 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1676 if p_loc == exp.Properties.Location.POST_WITH: 1677 with_properties.append(p) 1678 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1679 root_properties.append(p) 1680 1681 root_props_ast = exp.Properties(expressions=root_properties) 1682 root_props_ast.parent = expression.parent 1683 1684 with_props_ast = exp.Properties(expressions=with_properties) 1685 with_props_ast.parent = expression.parent 1686 1687 root_props = self.root_properties(root_props_ast) 1688 with_props = self.with_properties(with_props_ast) 1689 1690 if root_props and with_props and not self.pretty: 1691 with_props = " " + with_props 1692 1693 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1700 def properties( 1701 self, 1702 properties: exp.Properties, 1703 prefix: str = "", 1704 sep: str = ", ", 1705 suffix: str = "", 1706 wrapped: bool = True, 1707 ) -> str: 1708 if properties.expressions: 1709 expressions = self.expressions(properties, sep=sep, indent=False) 1710 if expressions: 1711 expressions = self.wrap(expressions) if wrapped else expressions 1712 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1713 return ""
1718 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1719 properties_locs = defaultdict(list) 1720 for p in properties.expressions: 1721 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1722 if p_loc != exp.Properties.Location.UNSUPPORTED: 1723 properties_locs[p_loc].append(p) 1724 else: 1725 self.unsupported(f"Unsupported property {p.key}") 1726 1727 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1734 def property_sql(self, expression: exp.Property) -> str: 1735 property_cls = expression.__class__ 1736 if property_cls == exp.Property: 1737 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1738 1739 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1740 if not property_name: 1741 self.unsupported(f"Unsupported property {expression.key}") 1742 1743 return f"{property_name}={self.sql(expression, 'this')}"
1745 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1746 if self.SUPPORTS_CREATE_TABLE_LIKE: 1747 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1748 options = f" {options}" if options else "" 1749 1750 like = f"LIKE {self.sql(expression, 'this')}{options}" 1751 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1752 like = f"({like})" 1753 1754 return like 1755 1756 if expression.expressions: 1757 self.unsupported("Transpilation of LIKE property options is unsupported") 1758 1759 select = exp.select("*").from_(expression.this).limit(0) 1760 return f"AS {self.sql(select)}"
1767 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1768 no = "NO " if expression.args.get("no") else "" 1769 local = expression.args.get("local") 1770 local = f"{local} " if local else "" 1771 dual = "DUAL " if expression.args.get("dual") else "" 1772 before = "BEFORE " if expression.args.get("before") else "" 1773 after = "AFTER " if expression.args.get("after") else "" 1774 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1790 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1791 if expression.args.get("no"): 1792 return "NO MERGEBLOCKRATIO" 1793 if expression.args.get("default"): 1794 return "DEFAULT MERGEBLOCKRATIO" 1795 1796 percent = " PERCENT" if expression.args.get("percent") else "" 1797 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1799 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1800 default = expression.args.get("default") 1801 minimum = expression.args.get("minimum") 1802 maximum = expression.args.get("maximum") 1803 if default or minimum or maximum: 1804 if default: 1805 prop = "DEFAULT" 1806 elif minimum: 1807 prop = "MINIMUM" 1808 else: 1809 prop = "MAXIMUM" 1810 return f"{prop} DATABLOCKSIZE" 1811 units = expression.args.get("units") 1812 units = f" {units}" if units else "" 1813 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1815 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1816 autotemp = expression.args.get("autotemp") 1817 always = expression.args.get("always") 1818 default = expression.args.get("default") 1819 manual = expression.args.get("manual") 1820 never = expression.args.get("never") 1821 1822 if autotemp is not None: 1823 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1824 elif always: 1825 prop = "ALWAYS" 1826 elif default: 1827 prop = "DEFAULT" 1828 elif manual: 1829 prop = "MANUAL" 1830 elif never: 1831 prop = "NEVER" 1832 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1834 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1835 no = expression.args.get("no") 1836 no = " NO" if no else "" 1837 concurrent = expression.args.get("concurrent") 1838 concurrent = " CONCURRENT" if concurrent else "" 1839 target = self.sql(expression, "target") 1840 target = f" {target}" if target else "" 1841 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1843 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1844 if isinstance(expression.this, list): 1845 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1846 if expression.this: 1847 modulus = self.sql(expression, "this") 1848 remainder = self.sql(expression, "expression") 1849 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1850 1851 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1852 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1853 return f"FROM ({from_expressions}) TO ({to_expressions})"
1855 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1856 this = self.sql(expression, "this") 1857 1858 for_values_or_default = expression.expression 1859 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1860 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1861 else: 1862 for_values_or_default = " DEFAULT" 1863 1864 return f"PARTITION OF {this}{for_values_or_default}"
1866 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1867 kind = expression.args.get("kind") 1868 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1869 for_or_in = expression.args.get("for_or_in") 1870 for_or_in = f" {for_or_in}" if for_or_in else "" 1871 lock_type = expression.args.get("lock_type") 1872 override = " OVERRIDE" if expression.args.get("override") else "" 1873 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1875 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1876 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1877 statistics = expression.args.get("statistics") 1878 statistics_sql = "" 1879 if statistics is not None: 1880 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1881 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1883 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1884 this = self.sql(expression, "this") 1885 this = f"HISTORY_TABLE={this}" if this else "" 1886 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1887 data_consistency = ( 1888 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1889 ) 1890 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1891 retention_period = ( 1892 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1893 ) 1894 1895 if this: 1896 on_sql = self.func("ON", this, data_consistency, retention_period) 1897 else: 1898 on_sql = "ON" if expression.args.get("on") else "OFF" 1899 1900 sql = f"SYSTEM_VERSIONING={on_sql}" 1901 1902 return f"WITH({sql})" if expression.args.get("with") else sql
1904 def insert_sql(self, expression: exp.Insert) -> str: 1905 hint = self.sql(expression, "hint") 1906 overwrite = expression.args.get("overwrite") 1907 1908 if isinstance(expression.this, exp.Directory): 1909 this = " OVERWRITE" if overwrite else " INTO" 1910 else: 1911 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1912 1913 stored = self.sql(expression, "stored") 1914 stored = f" {stored}" if stored else "" 1915 alternative = expression.args.get("alternative") 1916 alternative = f" OR {alternative}" if alternative else "" 1917 ignore = " IGNORE" if expression.args.get("ignore") else "" 1918 is_function = expression.args.get("is_function") 1919 if is_function: 1920 this = f"{this} FUNCTION" 1921 this = f"{this} {self.sql(expression, 'this')}" 1922 1923 exists = " IF EXISTS" if expression.args.get("exists") else "" 1924 where = self.sql(expression, "where") 1925 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1926 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1927 on_conflict = self.sql(expression, "conflict") 1928 on_conflict = f" {on_conflict}" if on_conflict else "" 1929 by_name = " BY NAME" if expression.args.get("by_name") else "" 1930 returning = self.sql(expression, "returning") 1931 1932 if self.RETURNING_END: 1933 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1934 else: 1935 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1936 1937 partition_by = self.sql(expression, "partition") 1938 partition_by = f" {partition_by}" if partition_by else "" 1939 settings = self.sql(expression, "settings") 1940 settings = f" {settings}" if settings else "" 1941 1942 source = self.sql(expression, "source") 1943 source = f"TABLE {source}" if source else "" 1944 1945 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1946 return self.prepend_ctes(expression, sql)
1964 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1965 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1966 1967 constraint = self.sql(expression, "constraint") 1968 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1969 1970 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1971 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1972 action = self.sql(expression, "action") 1973 1974 expressions = self.expressions(expression, flat=True) 1975 if expressions: 1976 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1977 expressions = f" {set_keyword}{expressions}" 1978 1979 where = self.sql(expression, "where") 1980 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1985 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1986 fields = self.sql(expression, "fields") 1987 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1988 escaped = self.sql(expression, "escaped") 1989 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1990 items = self.sql(expression, "collection_items") 1991 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1992 keys = self.sql(expression, "map_keys") 1993 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1994 lines = self.sql(expression, "lines") 1995 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1996 null = self.sql(expression, "null") 1997 null = f" NULL DEFINED AS {null}" if null else "" 1998 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2026 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2027 table = self.table_parts(expression) 2028 only = "ONLY " if expression.args.get("only") else "" 2029 partition = self.sql(expression, "partition") 2030 partition = f" {partition}" if partition else "" 2031 version = self.sql(expression, "version") 2032 version = f" {version}" if version else "" 2033 alias = self.sql(expression, "alias") 2034 alias = f"{sep}{alias}" if alias else "" 2035 2036 sample = self.sql(expression, "sample") 2037 if self.dialect.ALIAS_POST_TABLESAMPLE: 2038 sample_pre_alias = sample 2039 sample_post_alias = "" 2040 else: 2041 sample_pre_alias = "" 2042 sample_post_alias = sample 2043 2044 hints = self.expressions(expression, key="hints", sep=" ") 2045 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2046 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2047 joins = self.indent( 2048 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2049 ) 2050 laterals = self.expressions(expression, key="laterals", sep="") 2051 2052 file_format = self.sql(expression, "format") 2053 if file_format: 2054 pattern = self.sql(expression, "pattern") 2055 pattern = f", PATTERN => {pattern}" if pattern else "" 2056 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2057 2058 ordinality = expression.args.get("ordinality") or "" 2059 if ordinality: 2060 ordinality = f" WITH ORDINALITY{alias}" 2061 alias = "" 2062 2063 when = self.sql(expression, "when") 2064 if when: 2065 table = f"{table} {when}" 2066 2067 changes = self.sql(expression, "changes") 2068 changes = f" {changes}" if changes else "" 2069 2070 rows_from = self.expressions(expression, key="rows_from") 2071 if rows_from: 2072 table = f"ROWS FROM {self.wrap(rows_from)}" 2073 2074 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2076 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2077 table = self.func("TABLE", expression.this) 2078 alias = self.sql(expression, "alias") 2079 alias = f" AS {alias}" if alias else "" 2080 sample = self.sql(expression, "sample") 2081 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2082 joins = self.indent( 2083 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2084 ) 2085 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2087 def tablesample_sql( 2088 self, 2089 expression: exp.TableSample, 2090 tablesample_keyword: t.Optional[str] = None, 2091 ) -> str: 2092 method = self.sql(expression, "method") 2093 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2094 numerator = self.sql(expression, "bucket_numerator") 2095 denominator = self.sql(expression, "bucket_denominator") 2096 field = self.sql(expression, "bucket_field") 2097 field = f" ON {field}" if field else "" 2098 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2099 seed = self.sql(expression, "seed") 2100 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2101 2102 size = self.sql(expression, "size") 2103 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2104 size = f"{size} ROWS" 2105 2106 percent = self.sql(expression, "percent") 2107 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2108 percent = f"{percent} PERCENT" 2109 2110 expr = f"{bucket}{percent}{size}" 2111 if self.TABLESAMPLE_REQUIRES_PARENS: 2112 expr = f"({expr})" 2113 2114 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2116 def pivot_sql(self, expression: exp.Pivot) -> str: 2117 expressions = self.expressions(expression, flat=True) 2118 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2119 2120 group = self.sql(expression, "group") 2121 2122 if expression.this: 2123 this = self.sql(expression, "this") 2124 if not expressions: 2125 return f"UNPIVOT {this}" 2126 2127 on = f"{self.seg('ON')} {expressions}" 2128 into = self.sql(expression, "into") 2129 into = f"{self.seg('INTO')} {into}" if into else "" 2130 using = self.expressions(expression, key="using", flat=True) 2131 using = f"{self.seg('USING')} {using}" if using else "" 2132 return f"{direction} {this}{on}{into}{using}{group}" 2133 2134 alias = self.sql(expression, "alias") 2135 alias = f" AS {alias}" if alias else "" 2136 2137 fields = self.expressions( 2138 expression, 2139 "fields", 2140 sep=" ", 2141 dynamic=True, 2142 new_line=True, 2143 skip_first=True, 2144 skip_last=True, 2145 ) 2146 2147 include_nulls = expression.args.get("include_nulls") 2148 if include_nulls is not None: 2149 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2150 else: 2151 nulls = "" 2152 2153 default_on_null = self.sql(expression, "default_on_null") 2154 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2155 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2166 def update_sql(self, expression: exp.Update) -> str: 2167 this = self.sql(expression, "this") 2168 set_sql = self.expressions(expression, flat=True) 2169 from_sql = self.sql(expression, "from") 2170 where_sql = self.sql(expression, "where") 2171 returning = self.sql(expression, "returning") 2172 order = self.sql(expression, "order") 2173 limit = self.sql(expression, "limit") 2174 if self.RETURNING_END: 2175 expression_sql = f"{from_sql}{where_sql}{returning}" 2176 else: 2177 expression_sql = f"{returning}{from_sql}{where_sql}" 2178 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2179 return self.prepend_ctes(expression, sql)
2181 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2182 values_as_table = values_as_table and self.VALUES_AS_TABLE 2183 2184 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2185 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2186 args = self.expressions(expression) 2187 alias = self.sql(expression, "alias") 2188 values = f"VALUES{self.seg('')}{args}" 2189 values = ( 2190 f"({values})" 2191 if self.WRAP_DERIVED_VALUES 2192 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2193 else values 2194 ) 2195 return f"{values} AS {alias}" if alias else values 2196 2197 # Converts `VALUES...` expression into a series of select unions. 2198 alias_node = expression.args.get("alias") 2199 column_names = alias_node and alias_node.columns 2200 2201 selects: t.List[exp.Query] = [] 2202 2203 for i, tup in enumerate(expression.expressions): 2204 row = tup.expressions 2205 2206 if i == 0 and column_names: 2207 row = [ 2208 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2209 ] 2210 2211 selects.append(exp.Select(expressions=row)) 2212 2213 if self.pretty: 2214 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2215 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2216 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2217 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2218 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2219 2220 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2221 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2222 return f"({unions}){alias}"
2227 @unsupported_args("expressions") 2228 def into_sql(self, expression: exp.Into) -> str: 2229 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2230 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2231 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2248 def group_sql(self, expression: exp.Group) -> str: 2249 group_by_all = expression.args.get("all") 2250 if group_by_all is True: 2251 modifier = " ALL" 2252 elif group_by_all is False: 2253 modifier = " DISTINCT" 2254 else: 2255 modifier = "" 2256 2257 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2258 2259 grouping_sets = self.expressions(expression, key="grouping_sets") 2260 cube = self.expressions(expression, key="cube") 2261 rollup = self.expressions(expression, key="rollup") 2262 2263 groupings = csv( 2264 self.seg(grouping_sets) if grouping_sets else "", 2265 self.seg(cube) if cube else "", 2266 self.seg(rollup) if rollup else "", 2267 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2268 sep=self.GROUPINGS_SEP, 2269 ) 2270 2271 if ( 2272 expression.expressions 2273 and groupings 2274 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2275 ): 2276 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2277 2278 return f"{group_by}{groupings}"
2284 def connect_sql(self, expression: exp.Connect) -> str: 2285 start = self.sql(expression, "start") 2286 start = self.seg(f"START WITH {start}") if start else "" 2287 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2288 connect = self.sql(expression, "connect") 2289 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2290 return start + connect
2295 def join_sql(self, expression: exp.Join) -> str: 2296 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2297 side = None 2298 else: 2299 side = expression.side 2300 2301 op_sql = " ".join( 2302 op 2303 for op in ( 2304 expression.method, 2305 "GLOBAL" if expression.args.get("global") else None, 2306 side, 2307 expression.kind, 2308 expression.hint if self.JOIN_HINTS else None, 2309 ) 2310 if op 2311 ) 2312 match_cond = self.sql(expression, "match_condition") 2313 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2314 on_sql = self.sql(expression, "on") 2315 using = expression.args.get("using") 2316 2317 if not on_sql and using: 2318 on_sql = csv(*(self.sql(column) for column in using)) 2319 2320 this = expression.this 2321 this_sql = self.sql(this) 2322 2323 exprs = self.expressions(expression) 2324 if exprs: 2325 this_sql = f"{this_sql},{self.seg(exprs)}" 2326 2327 if on_sql: 2328 on_sql = self.indent(on_sql, skip_first=True) 2329 space = self.seg(" " * self.pad) if self.pretty else " " 2330 if using: 2331 on_sql = f"{space}USING ({on_sql})" 2332 else: 2333 on_sql = f"{space}ON {on_sql}" 2334 elif not op_sql: 2335 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2336 return f" {this_sql}" 2337 2338 return f", {this_sql}" 2339 2340 if op_sql != "STRAIGHT_JOIN": 2341 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2342 2343 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2344 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:
2351 def lateral_op(self, expression: exp.Lateral) -> str: 2352 cross_apply = expression.args.get("cross_apply") 2353 2354 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2355 if cross_apply is True: 2356 op = "INNER JOIN " 2357 elif cross_apply is False: 2358 op = "LEFT JOIN " 2359 else: 2360 op = "" 2361 2362 return f"{op}LATERAL"
2364 def lateral_sql(self, expression: exp.Lateral) -> str: 2365 this = self.sql(expression, "this") 2366 2367 if expression.args.get("view"): 2368 alias = expression.args["alias"] 2369 columns = self.expressions(alias, key="columns", flat=True) 2370 table = f" {alias.name}" if alias.name else "" 2371 columns = f" AS {columns}" if columns else "" 2372 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2373 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2374 2375 alias = self.sql(expression, "alias") 2376 alias = f" AS {alias}" if alias else "" 2377 2378 ordinality = expression.args.get("ordinality") or "" 2379 if ordinality: 2380 ordinality = f" WITH ORDINALITY{alias}" 2381 alias = "" 2382 2383 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2385 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2386 this = self.sql(expression, "this") 2387 2388 args = [ 2389 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2390 for e in (expression.args.get(k) for k in ("offset", "expression")) 2391 if e 2392 ] 2393 2394 args_sql = ", ".join(self.sql(e) for e in args) 2395 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2396 expressions = self.expressions(expression, flat=True) 2397 limit_options = self.sql(expression, "limit_options") 2398 expressions = f" BY {expressions}" if expressions else "" 2399 2400 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2402 def offset_sql(self, expression: exp.Offset) -> str: 2403 this = self.sql(expression, "this") 2404 value = expression.expression 2405 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2406 expressions = self.expressions(expression, flat=True) 2407 expressions = f" BY {expressions}" if expressions else "" 2408 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2410 def setitem_sql(self, expression: exp.SetItem) -> str: 2411 kind = self.sql(expression, "kind") 2412 kind = f"{kind} " if kind else "" 2413 this = self.sql(expression, "this") 2414 expressions = self.expressions(expression) 2415 collate = self.sql(expression, "collate") 2416 collate = f" COLLATE {collate}" if collate else "" 2417 global_ = "GLOBAL " if expression.args.get("global") else "" 2418 return f"{global_}{kind}{this}{expressions}{collate}"
2425 def queryband_sql(self, expression: exp.QueryBand) -> str: 2426 this = self.sql(expression, "this") 2427 update = " UPDATE" if expression.args.get("update") else "" 2428 scope = self.sql(expression, "scope") 2429 scope = f" FOR {scope}" if scope else "" 2430 2431 return f"QUERY_BAND = {this}{update}{scope}"
2436 def lock_sql(self, expression: exp.Lock) -> str: 2437 if not self.LOCKING_READS_SUPPORTED: 2438 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2439 return "" 2440 2441 update = expression.args["update"] 2442 key = expression.args.get("key") 2443 if update: 2444 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2445 else: 2446 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2447 expressions = self.expressions(expression, flat=True) 2448 expressions = f" OF {expressions}" if expressions else "" 2449 wait = expression.args.get("wait") 2450 2451 if wait is not None: 2452 if isinstance(wait, exp.Literal): 2453 wait = f" WAIT {self.sql(wait)}" 2454 else: 2455 wait = " NOWAIT" if wait else " SKIP LOCKED" 2456 2457 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2465 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2466 if self.dialect.ESCAPED_SEQUENCES: 2467 to_escaped = self.dialect.ESCAPED_SEQUENCES 2468 text = "".join( 2469 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2470 ) 2471 2472 return self._replace_line_breaks(text).replace( 2473 self.dialect.QUOTE_END, self._escaped_quote_end 2474 )
2476 def loaddata_sql(self, expression: exp.LoadData) -> str: 2477 local = " LOCAL" if expression.args.get("local") else "" 2478 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2479 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2480 this = f" INTO TABLE {self.sql(expression, 'this')}" 2481 partition = self.sql(expression, "partition") 2482 partition = f" {partition}" if partition else "" 2483 input_format = self.sql(expression, "input_format") 2484 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2485 serde = self.sql(expression, "serde") 2486 serde = f" SERDE {serde}" if serde else "" 2487 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2495 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2496 this = self.sql(expression, "this") 2497 this = f"{this} " if this else this 2498 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2499 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2501 def withfill_sql(self, expression: exp.WithFill) -> str: 2502 from_sql = self.sql(expression, "from") 2503 from_sql = f" FROM {from_sql}" if from_sql else "" 2504 to_sql = self.sql(expression, "to") 2505 to_sql = f" TO {to_sql}" if to_sql else "" 2506 step_sql = self.sql(expression, "step") 2507 step_sql = f" STEP {step_sql}" if step_sql else "" 2508 interpolated_values = [ 2509 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2510 if isinstance(e, exp.Alias) 2511 else self.sql(e, "this") 2512 for e in expression.args.get("interpolate") or [] 2513 ] 2514 interpolate = ( 2515 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2516 ) 2517 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2528 def ordered_sql(self, expression: exp.Ordered) -> str: 2529 desc = expression.args.get("desc") 2530 asc = not desc 2531 2532 nulls_first = expression.args.get("nulls_first") 2533 nulls_last = not nulls_first 2534 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2535 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2536 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2537 2538 this = self.sql(expression, "this") 2539 2540 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2541 nulls_sort_change = "" 2542 if nulls_first and ( 2543 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2544 ): 2545 nulls_sort_change = " NULLS FIRST" 2546 elif ( 2547 nulls_last 2548 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2549 and not nulls_are_last 2550 ): 2551 nulls_sort_change = " NULLS LAST" 2552 2553 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2554 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2555 window = expression.find_ancestor(exp.Window, exp.Select) 2556 if isinstance(window, exp.Window) and window.args.get("spec"): 2557 self.unsupported( 2558 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2559 ) 2560 nulls_sort_change = "" 2561 elif self.NULL_ORDERING_SUPPORTED is False and ( 2562 (asc and nulls_sort_change == " NULLS LAST") 2563 or (desc and nulls_sort_change == " NULLS FIRST") 2564 ): 2565 # BigQuery does not allow these ordering/nulls combinations when used under 2566 # an aggregation func or under a window containing one 2567 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2568 2569 if isinstance(ancestor, exp.Window): 2570 ancestor = ancestor.this 2571 if isinstance(ancestor, exp.AggFunc): 2572 self.unsupported( 2573 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2574 ) 2575 nulls_sort_change = "" 2576 elif self.NULL_ORDERING_SUPPORTED is None: 2577 if expression.this.is_int: 2578 self.unsupported( 2579 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2580 ) 2581 elif not isinstance(expression.this, exp.Rand): 2582 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2583 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2584 nulls_sort_change = "" 2585 2586 with_fill = self.sql(expression, "with_fill") 2587 with_fill = f" {with_fill}" if with_fill else "" 2588 2589 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2599 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2600 partition = self.partition_by_sql(expression) 2601 order = self.sql(expression, "order") 2602 measures = self.expressions(expression, key="measures") 2603 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2604 rows = self.sql(expression, "rows") 2605 rows = self.seg(rows) if rows else "" 2606 after = self.sql(expression, "after") 2607 after = self.seg(after) if after else "" 2608 pattern = self.sql(expression, "pattern") 2609 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2610 definition_sqls = [ 2611 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2612 for definition in expression.args.get("define", []) 2613 ] 2614 definitions = self.expressions(sqls=definition_sqls) 2615 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2616 body = "".join( 2617 ( 2618 partition, 2619 order, 2620 measures, 2621 rows, 2622 after, 2623 pattern, 2624 define, 2625 ) 2626 ) 2627 alias = self.sql(expression, "alias") 2628 alias = f" {alias}" if alias else "" 2629 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2631 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2632 limit = expression.args.get("limit") 2633 2634 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2635 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2636 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2637 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2638 2639 return csv( 2640 *sqls, 2641 *[self.sql(join) for join in expression.args.get("joins") or []], 2642 self.sql(expression, "match"), 2643 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2644 self.sql(expression, "prewhere"), 2645 self.sql(expression, "where"), 2646 self.sql(expression, "connect"), 2647 self.sql(expression, "group"), 2648 self.sql(expression, "having"), 2649 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2650 self.sql(expression, "order"), 2651 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2652 *self.after_limit_modifiers(expression), 2653 self.options_modifier(expression), 2654 self.for_modifiers(expression), 2655 sep="", 2656 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2670 def offset_limit_modifiers( 2671 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2672 ) -> t.List[str]: 2673 return [ 2674 self.sql(expression, "offset") if fetch else self.sql(limit), 2675 self.sql(limit) if fetch else self.sql(expression, "offset"), 2676 ]
2683 def select_sql(self, expression: exp.Select) -> str: 2684 into = expression.args.get("into") 2685 if not self.SUPPORTS_SELECT_INTO and into: 2686 into.pop() 2687 2688 hint = self.sql(expression, "hint") 2689 distinct = self.sql(expression, "distinct") 2690 distinct = f" {distinct}" if distinct else "" 2691 kind = self.sql(expression, "kind") 2692 2693 limit = expression.args.get("limit") 2694 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2695 top = self.limit_sql(limit, top=True) 2696 limit.pop() 2697 else: 2698 top = "" 2699 2700 expressions = self.expressions(expression) 2701 2702 if kind: 2703 if kind in self.SELECT_KINDS: 2704 kind = f" AS {kind}" 2705 else: 2706 if kind == "STRUCT": 2707 expressions = self.expressions( 2708 sqls=[ 2709 self.sql( 2710 exp.Struct( 2711 expressions=[ 2712 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2713 if isinstance(e, exp.Alias) 2714 else e 2715 for e in expression.expressions 2716 ] 2717 ) 2718 ) 2719 ] 2720 ) 2721 kind = "" 2722 2723 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2724 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2725 2726 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2727 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2728 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2729 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2730 sql = self.query_modifiers( 2731 expression, 2732 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2733 self.sql(expression, "into", comment=False), 2734 self.sql(expression, "from", comment=False), 2735 ) 2736 2737 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2738 if expression.args.get("with"): 2739 sql = self.maybe_comment(sql, expression) 2740 expression.pop_comments() 2741 2742 sql = self.prepend_ctes(expression, sql) 2743 2744 if not self.SUPPORTS_SELECT_INTO and into: 2745 if into.args.get("temporary"): 2746 table_kind = " TEMPORARY" 2747 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2748 table_kind = " UNLOGGED" 2749 else: 2750 table_kind = "" 2751 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2752 2753 return sql
2765 def star_sql(self, expression: exp.Star) -> str: 2766 except_ = self.expressions(expression, key="except", flat=True) 2767 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2768 replace = self.expressions(expression, key="replace", flat=True) 2769 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2770 rename = self.expressions(expression, key="rename", flat=True) 2771 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2772 return f"*{except_}{replace}{rename}"
2788 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2789 alias = self.sql(expression, "alias") 2790 alias = f"{sep}{alias}" if alias else "" 2791 sample = self.sql(expression, "sample") 2792 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2793 alias = f"{sample}{alias}" 2794 2795 # Set to None so it's not generated again by self.query_modifiers() 2796 expression.set("sample", None) 2797 2798 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2799 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2800 return self.prepend_ctes(expression, sql)
2806 def unnest_sql(self, expression: exp.Unnest) -> str: 2807 args = self.expressions(expression, flat=True) 2808 2809 alias = expression.args.get("alias") 2810 offset = expression.args.get("offset") 2811 2812 if self.UNNEST_WITH_ORDINALITY: 2813 if alias and isinstance(offset, exp.Expression): 2814 alias.append("columns", offset) 2815 2816 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2817 columns = alias.columns 2818 alias = self.sql(columns[0]) if columns else "" 2819 else: 2820 alias = self.sql(alias) 2821 2822 alias = f" AS {alias}" if alias else alias 2823 if self.UNNEST_WITH_ORDINALITY: 2824 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2825 else: 2826 if isinstance(offset, exp.Expression): 2827 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2828 elif offset: 2829 suffix = f"{alias} WITH OFFSET" 2830 else: 2831 suffix = alias 2832 2833 return f"UNNEST({args}){suffix}"
2842 def window_sql(self, expression: exp.Window) -> str: 2843 this = self.sql(expression, "this") 2844 partition = self.partition_by_sql(expression) 2845 order = expression.args.get("order") 2846 order = self.order_sql(order, flat=True) if order else "" 2847 spec = self.sql(expression, "spec") 2848 alias = self.sql(expression, "alias") 2849 over = self.sql(expression, "over") or "OVER" 2850 2851 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2852 2853 first = expression.args.get("first") 2854 if first is None: 2855 first = "" 2856 else: 2857 first = "FIRST" if first else "LAST" 2858 2859 if not partition and not order and not spec and alias: 2860 return f"{this} {alias}" 2861 2862 args = self.format_args( 2863 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2864 ) 2865 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2871 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2872 kind = self.sql(expression, "kind") 2873 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2874 end = ( 2875 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2876 or "CURRENT ROW" 2877 ) 2878 2879 window_spec = f"{kind} BETWEEN {start} AND {end}" 2880 2881 exclude = self.sql(expression, "exclude") 2882 if exclude: 2883 if self.SUPPORTS_WINDOW_EXCLUDE: 2884 window_spec += f" EXCLUDE {exclude}" 2885 else: 2886 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2887 2888 return window_spec
2895 def between_sql(self, expression: exp.Between) -> str: 2896 this = self.sql(expression, "this") 2897 low = self.sql(expression, "low") 2898 high = self.sql(expression, "high") 2899 symmetric = expression.args.get("symmetric") 2900 2901 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2902 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2903 2904 flag = ( 2905 " SYMMETRIC" 2906 if symmetric 2907 else " ASYMMETRIC" 2908 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2909 else "" # silently drop ASYMMETRIC – semantics identical 2910 ) 2911 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]:
2913 def bracket_offset_expressions( 2914 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2915 ) -> t.List[exp.Expression]: 2916 return apply_index_offset( 2917 expression.this, 2918 expression.expressions, 2919 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2920 dialect=self.dialect, 2921 )
2934 def any_sql(self, expression: exp.Any) -> str: 2935 this = self.sql(expression, "this") 2936 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2937 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2938 this = self.wrap(this) 2939 return f"ANY{this}" 2940 return f"ANY {this}"
2945 def case_sql(self, expression: exp.Case) -> str: 2946 this = self.sql(expression, "this") 2947 statements = [f"CASE {this}" if this else "CASE"] 2948 2949 for e in expression.args["ifs"]: 2950 statements.append(f"WHEN {self.sql(e, 'this')}") 2951 statements.append(f"THEN {self.sql(e, 'true')}") 2952 2953 default = self.sql(expression, "default") 2954 2955 if default: 2956 statements.append(f"ELSE {default}") 2957 2958 statements.append("END") 2959 2960 if self.pretty and self.too_wide(statements): 2961 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2962 2963 return " ".join(statements)
2975 def extract_sql(self, expression: exp.Extract) -> str: 2976 from sqlglot.dialects.dialect import map_date_part 2977 2978 this = ( 2979 map_date_part(expression.this, self.dialect) 2980 if self.NORMALIZE_EXTRACT_DATE_PARTS 2981 else expression.this 2982 ) 2983 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2984 expression_sql = self.sql(expression, "expression") 2985 2986 return f"EXTRACT({this_sql} FROM {expression_sql})"
2988 def trim_sql(self, expression: exp.Trim) -> str: 2989 trim_type = self.sql(expression, "position") 2990 2991 if trim_type == "LEADING": 2992 func_name = "LTRIM" 2993 elif trim_type == "TRAILING": 2994 func_name = "RTRIM" 2995 else: 2996 func_name = "TRIM" 2997 2998 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]:
3000 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 3001 args = expression.expressions 3002 if isinstance(expression, exp.ConcatWs): 3003 args = args[1:] # Skip the delimiter 3004 3005 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3006 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 3007 3008 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 3009 3010 def _wrap_with_coalesce(e: exp.Expression) -> exp.Expression: 3011 if not e.type: 3012 from sqlglot.optimizer.annotate_types import annotate_types 3013 3014 e = annotate_types(e, dialect=self.dialect) 3015 3016 if e.is_string or e.is_type(exp.DataType.Type.ARRAY): 3017 return e 3018 3019 return exp.func("coalesce", e, exp.Literal.string("")) 3020 3021 args = [_wrap_with_coalesce(e) for e in args] 3022 3023 return args
3025 def concat_sql(self, expression: exp.Concat) -> str: 3026 expressions = self.convert_concat_args(expression) 3027 3028 # Some dialects don't allow a single-argument CONCAT call 3029 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 3030 return self.sql(expressions[0]) 3031 3032 return self.func("CONCAT", *expressions)
3043 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3044 expressions = self.expressions(expression, flat=True) 3045 expressions = f" ({expressions})" if expressions else "" 3046 reference = self.sql(expression, "reference") 3047 reference = f" {reference}" if reference else "" 3048 delete = self.sql(expression, "delete") 3049 delete = f" ON DELETE {delete}" if delete else "" 3050 update = self.sql(expression, "update") 3051 update = f" ON UPDATE {update}" if update else "" 3052 options = self.expressions(expression, key="options", flat=True, sep=" ") 3053 options = f" {options}" if options else "" 3054 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3056 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3057 expressions = self.expressions(expression, flat=True) 3058 include = self.sql(expression, "include") 3059 options = self.expressions(expression, key="options", flat=True, sep=" ") 3060 options = f" {options}" if options else "" 3061 return f"PRIMARY KEY ({expressions}){include}{options}"
3074 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3075 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3076 3077 if expression.args.get("escape"): 3078 path = self.escape_str(path) 3079 3080 if self.QUOTE_JSON_PATH: 3081 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3082 3083 return path
3085 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3086 if isinstance(expression, exp.JSONPathPart): 3087 transform = self.TRANSFORMS.get(expression.__class__) 3088 if not callable(transform): 3089 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3090 return "" 3091 3092 return transform(self, expression) 3093 3094 if isinstance(expression, int): 3095 return str(expression) 3096 3097 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3098 escaped = expression.replace("'", "\\'") 3099 escaped = f"\\'{expression}\\'" 3100 else: 3101 escaped = expression.replace('"', '\\"') 3102 escaped = f'"{escaped}"' 3103 3104 return escaped
3109 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3110 # Output the Teradata column FORMAT override. 3111 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3112 this = self.sql(expression, "this") 3113 fmt = self.sql(expression, "format") 3114 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3116 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3117 null_handling = expression.args.get("null_handling") 3118 null_handling = f" {null_handling}" if null_handling else "" 3119 3120 unique_keys = expression.args.get("unique_keys") 3121 if unique_keys is not None: 3122 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3123 else: 3124 unique_keys = "" 3125 3126 return_type = self.sql(expression, "return_type") 3127 return_type = f" RETURNING {return_type}" if return_type else "" 3128 encoding = self.sql(expression, "encoding") 3129 encoding = f" ENCODING {encoding}" if encoding else "" 3130 3131 return self.func( 3132 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3133 *expression.expressions, 3134 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3135 )
3140 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3141 null_handling = expression.args.get("null_handling") 3142 null_handling = f" {null_handling}" if null_handling else "" 3143 return_type = self.sql(expression, "return_type") 3144 return_type = f" RETURNING {return_type}" if return_type else "" 3145 strict = " STRICT" if expression.args.get("strict") else "" 3146 return self.func( 3147 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3148 )
3150 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3151 this = self.sql(expression, "this") 3152 order = self.sql(expression, "order") 3153 null_handling = expression.args.get("null_handling") 3154 null_handling = f" {null_handling}" if null_handling else "" 3155 return_type = self.sql(expression, "return_type") 3156 return_type = f" RETURNING {return_type}" if return_type else "" 3157 strict = " STRICT" if expression.args.get("strict") else "" 3158 return self.func( 3159 "JSON_ARRAYAGG", 3160 this, 3161 suffix=f"{order}{null_handling}{return_type}{strict})", 3162 )
3164 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3165 path = self.sql(expression, "path") 3166 path = f" PATH {path}" if path else "" 3167 nested_schema = self.sql(expression, "nested_schema") 3168 3169 if nested_schema: 3170 return f"NESTED{path} {nested_schema}" 3171 3172 this = self.sql(expression, "this") 3173 kind = self.sql(expression, "kind") 3174 kind = f" {kind}" if kind else "" 3175 return f"{this}{kind}{path}"
3180 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3181 this = self.sql(expression, "this") 3182 path = self.sql(expression, "path") 3183 path = f", {path}" if path else "" 3184 error_handling = expression.args.get("error_handling") 3185 error_handling = f" {error_handling}" if error_handling else "" 3186 empty_handling = expression.args.get("empty_handling") 3187 empty_handling = f" {empty_handling}" if empty_handling else "" 3188 schema = self.sql(expression, "schema") 3189 return self.func( 3190 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3191 )
3193 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3194 this = self.sql(expression, "this") 3195 kind = self.sql(expression, "kind") 3196 path = self.sql(expression, "path") 3197 path = f" {path}" if path else "" 3198 as_json = " AS JSON" if expression.args.get("as_json") else "" 3199 return f"{this} {kind}{path}{as_json}"
3201 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3202 this = self.sql(expression, "this") 3203 path = self.sql(expression, "path") 3204 path = f", {path}" if path else "" 3205 expressions = self.expressions(expression) 3206 with_ = ( 3207 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3208 if expressions 3209 else "" 3210 ) 3211 return f"OPENJSON({this}{path}){with_}"
3213 def in_sql(self, expression: exp.In) -> str: 3214 query = expression.args.get("query") 3215 unnest = expression.args.get("unnest") 3216 field = expression.args.get("field") 3217 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3218 3219 if query: 3220 in_sql = self.sql(query) 3221 elif unnest: 3222 in_sql = self.in_unnest_op(unnest) 3223 elif field: 3224 in_sql = self.sql(field) 3225 else: 3226 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3227 3228 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3233 def interval_sql(self, expression: exp.Interval) -> str: 3234 unit = self.sql(expression, "unit") 3235 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3236 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3237 unit = f" {unit}" if unit else "" 3238 3239 if self.SINGLE_STRING_INTERVAL: 3240 this = expression.this.name if expression.this else "" 3241 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3242 3243 this = self.sql(expression, "this") 3244 if this: 3245 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3246 this = f" {this}" if unwrapped else f" ({this})" 3247 3248 return f"INTERVAL{this}{unit}"
3253 def reference_sql(self, expression: exp.Reference) -> str: 3254 this = self.sql(expression, "this") 3255 expressions = self.expressions(expression, flat=True) 3256 expressions = f"({expressions})" if expressions else "" 3257 options = self.expressions(expression, key="options", flat=True, sep=" ") 3258 options = f" {options}" if options else "" 3259 return f"REFERENCES {this}{expressions}{options}"
3261 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3262 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3263 parent = expression.parent 3264 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3265 return self.func( 3266 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3267 )
3287 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3288 alias = expression.args["alias"] 3289 3290 parent = expression.parent 3291 pivot = parent and parent.parent 3292 3293 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3294 identifier_alias = isinstance(alias, exp.Identifier) 3295 literal_alias = isinstance(alias, exp.Literal) 3296 3297 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3298 alias.replace(exp.Literal.string(alias.output_name)) 3299 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3300 alias.replace(exp.to_identifier(alias.output_name)) 3301 3302 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:
3340 def connector_sql( 3341 self, 3342 expression: exp.Connector, 3343 op: str, 3344 stack: t.Optional[t.List[str | exp.Expression]] = None, 3345 ) -> str: 3346 if stack is not None: 3347 if expression.expressions: 3348 stack.append(self.expressions(expression, sep=f" {op} ")) 3349 else: 3350 stack.append(expression.right) 3351 if expression.comments and self.comments: 3352 for comment in expression.comments: 3353 if comment: 3354 op += f" /*{self.sanitize_comment(comment)}*/" 3355 stack.extend((op, expression.left)) 3356 return op 3357 3358 stack = [expression] 3359 sqls: t.List[str] = [] 3360 ops = set() 3361 3362 while stack: 3363 node = stack.pop() 3364 if isinstance(node, exp.Connector): 3365 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3366 else: 3367 sql = self.sql(node) 3368 if sqls and sqls[-1] in ops: 3369 sqls[-1] += f" {sql}" 3370 else: 3371 sqls.append(sql) 3372 3373 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3374 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3394 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3395 format_sql = self.sql(expression, "format") 3396 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3397 to_sql = self.sql(expression, "to") 3398 to_sql = f" {to_sql}" if to_sql else "" 3399 action = self.sql(expression, "action") 3400 action = f" {action}" if action else "" 3401 default = self.sql(expression, "default") 3402 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3403 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3417 def comment_sql(self, expression: exp.Comment) -> str: 3418 this = self.sql(expression, "this") 3419 kind = expression.args["kind"] 3420 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3421 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3422 expression_sql = self.sql(expression, "expression") 3423 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3425 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3426 this = self.sql(expression, "this") 3427 delete = " DELETE" if expression.args.get("delete") else "" 3428 recompress = self.sql(expression, "recompress") 3429 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3430 to_disk = self.sql(expression, "to_disk") 3431 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3432 to_volume = self.sql(expression, "to_volume") 3433 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3434 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3436 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3437 where = self.sql(expression, "where") 3438 group = self.sql(expression, "group") 3439 aggregates = self.expressions(expression, key="aggregates") 3440 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3441 3442 if not (where or group or aggregates) and len(expression.expressions) == 1: 3443 return f"TTL {self.expressions(expression, flat=True)}" 3444 3445 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3462 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3463 this = self.sql(expression, "this") 3464 3465 dtype = self.sql(expression, "dtype") 3466 if dtype: 3467 collate = self.sql(expression, "collate") 3468 collate = f" COLLATE {collate}" if collate else "" 3469 using = self.sql(expression, "using") 3470 using = f" USING {using}" if using else "" 3471 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3472 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3473 3474 default = self.sql(expression, "default") 3475 if default: 3476 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3477 3478 comment = self.sql(expression, "comment") 3479 if comment: 3480 return f"ALTER COLUMN {this} COMMENT {comment}" 3481 3482 visible = expression.args.get("visible") 3483 if visible: 3484 return f"ALTER COLUMN {this} SET {visible}" 3485 3486 allow_null = expression.args.get("allow_null") 3487 drop = expression.args.get("drop") 3488 3489 if not drop and not allow_null: 3490 self.unsupported("Unsupported ALTER COLUMN syntax") 3491 3492 if allow_null is not None: 3493 keyword = "DROP" if drop else "SET" 3494 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3495 3496 return f"ALTER COLUMN {this} DROP DEFAULT"
3512 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3513 compound = " COMPOUND" if expression.args.get("compound") else "" 3514 this = self.sql(expression, "this") 3515 expressions = self.expressions(expression, flat=True) 3516 expressions = f"({expressions})" if expressions else "" 3517 return f"ALTER{compound} SORTKEY {this or expressions}"
def
alterrename_sql( self, expression: sqlglot.expressions.AlterRename, include_to: bool = True) -> str:
3519 def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str: 3520 if not self.RENAME_TABLE_WITH_DB: 3521 # Remove db from tables 3522 expression = expression.transform( 3523 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3524 ).assert_is(exp.AlterRename) 3525 this = self.sql(expression, "this") 3526 to_kw = " TO" if include_to else "" 3527 return f"RENAME{to_kw} {this}"
3542 def alter_sql(self, expression: exp.Alter) -> str: 3543 actions = expression.args["actions"] 3544 3545 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3546 actions[0], exp.ColumnDef 3547 ): 3548 actions_sql = self.expressions(expression, key="actions", flat=True) 3549 actions_sql = f"ADD {actions_sql}" 3550 else: 3551 actions_list = [] 3552 for action in actions: 3553 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3554 action_sql = self.add_column_sql(action) 3555 else: 3556 action_sql = self.sql(action) 3557 if isinstance(action, exp.Query): 3558 action_sql = f"AS {action_sql}" 3559 3560 actions_list.append(action_sql) 3561 3562 actions_sql = self.format_args(*actions_list).lstrip("\n") 3563 3564 exists = " IF EXISTS" if expression.args.get("exists") else "" 3565 on_cluster = self.sql(expression, "cluster") 3566 on_cluster = f" {on_cluster}" if on_cluster else "" 3567 only = " ONLY" if expression.args.get("only") else "" 3568 options = self.expressions(expression, key="options") 3569 options = f", {options}" if options else "" 3570 kind = self.sql(expression, "kind") 3571 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3572 check = " WITH CHECK" if expression.args.get("check") else "" 3573 3574 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}"
3576 def add_column_sql(self, expression: exp.Expression) -> str: 3577 sql = self.sql(expression) 3578 if isinstance(expression, exp.Schema): 3579 column_text = " COLUMNS" 3580 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3581 column_text = " COLUMN" 3582 else: 3583 column_text = "" 3584 3585 return f"ADD{column_text} {sql}"
3595 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3596 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3597 location = self.sql(expression, "location") 3598 location = f" {location}" if location else "" 3599 return f"ADD {exists}{self.sql(expression.this)}{location}"
3601 def distinct_sql(self, expression: exp.Distinct) -> str: 3602 this = self.expressions(expression, flat=True) 3603 3604 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3605 case = exp.case() 3606 for arg in expression.expressions: 3607 case = case.when(arg.is_(exp.null()), exp.null()) 3608 this = self.sql(case.else_(f"({this})")) 3609 3610 this = f" {this}" if this else "" 3611 3612 on = self.sql(expression, "on") 3613 on = f" ON {on}" if on else "" 3614 return f"DISTINCT{this}{on}"
3643 def div_sql(self, expression: exp.Div) -> str: 3644 l, r = expression.left, expression.right 3645 3646 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3647 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3648 3649 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3650 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3651 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3652 3653 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3654 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3655 return self.sql( 3656 exp.cast( 3657 l / r, 3658 to=exp.DataType.Type.BIGINT, 3659 ) 3660 ) 3661 3662 return self.binary(expression, "/")
3779 def log_sql(self, expression: exp.Log) -> str: 3780 this = expression.this 3781 expr = expression.expression 3782 3783 if self.dialect.LOG_BASE_FIRST is False: 3784 this, expr = expr, this 3785 elif self.dialect.LOG_BASE_FIRST is None and expr: 3786 if this.name in ("2", "10"): 3787 return self.func(f"LOG{this.name}", expr) 3788 3789 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3790 3791 return self.func("LOG", this, expr)
3800 def binary(self, expression: exp.Binary, op: str) -> str: 3801 sqls: t.List[str] = [] 3802 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3803 binary_type = type(expression) 3804 3805 while stack: 3806 node = stack.pop() 3807 3808 if type(node) is binary_type: 3809 op_func = node.args.get("operator") 3810 if op_func: 3811 op = f"OPERATOR({self.sql(op_func)})" 3812 3813 stack.append(node.right) 3814 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3815 stack.append(node.left) 3816 else: 3817 sqls.append(self.sql(node)) 3818 3819 return "".join(sqls)
3828 def function_fallback_sql(self, expression: exp.Func) -> str: 3829 args = [] 3830 3831 for key in expression.arg_types: 3832 arg_value = expression.args.get(key) 3833 3834 if isinstance(arg_value, list): 3835 for value in arg_value: 3836 args.append(value) 3837 elif arg_value is not None: 3838 args.append(arg_value) 3839 3840 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3841 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3842 else: 3843 name = expression.sql_name() 3844 3845 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:
3847 def func( 3848 self, 3849 name: str, 3850 *args: t.Optional[exp.Expression | str], 3851 prefix: str = "(", 3852 suffix: str = ")", 3853 normalize: bool = True, 3854 ) -> str: 3855 name = self.normalize_func(name) if normalize else name 3856 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3858 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3859 arg_sqls = tuple( 3860 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3861 ) 3862 if self.pretty and self.too_wide(arg_sqls): 3863 return self.indent( 3864 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3865 ) 3866 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]:
3871 def format_time( 3872 self, 3873 expression: exp.Expression, 3874 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3875 inverse_time_trie: t.Optional[t.Dict] = None, 3876 ) -> t.Optional[str]: 3877 return format_time( 3878 self.sql(expression, "format"), 3879 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3880 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3881 )
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:
3883 def expressions( 3884 self, 3885 expression: t.Optional[exp.Expression] = None, 3886 key: t.Optional[str] = None, 3887 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3888 flat: bool = False, 3889 indent: bool = True, 3890 skip_first: bool = False, 3891 skip_last: bool = False, 3892 sep: str = ", ", 3893 prefix: str = "", 3894 dynamic: bool = False, 3895 new_line: bool = False, 3896 ) -> str: 3897 expressions = expression.args.get(key or "expressions") if expression else sqls 3898 3899 if not expressions: 3900 return "" 3901 3902 if flat: 3903 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3904 3905 num_sqls = len(expressions) 3906 result_sqls = [] 3907 3908 for i, e in enumerate(expressions): 3909 sql = self.sql(e, comment=False) 3910 if not sql: 3911 continue 3912 3913 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3914 3915 if self.pretty: 3916 if self.leading_comma: 3917 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3918 else: 3919 result_sqls.append( 3920 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3921 ) 3922 else: 3923 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3924 3925 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3926 if new_line: 3927 result_sqls.insert(0, "") 3928 result_sqls.append("") 3929 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3930 else: 3931 result_sql = "".join(result_sqls) 3932 3933 return ( 3934 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3935 if indent 3936 else result_sql 3937 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3939 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3940 flat = flat or isinstance(expression.parent, exp.Properties) 3941 expressions_sql = self.expressions(expression, flat=flat) 3942 if flat: 3943 return f"{op} {expressions_sql}" 3944 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3946 def naked_property(self, expression: exp.Property) -> str: 3947 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3948 if not property_name: 3949 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3950 return f"{property_name} {self.sql(expression, 'this')}"
3958 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3959 this = self.sql(expression, "this") 3960 expressions = self.no_identify(self.expressions, expression) 3961 expressions = ( 3962 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3963 ) 3964 return f"{this}{expressions}" if expressions.strip() != "" else this
3974 def when_sql(self, expression: exp.When) -> str: 3975 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3976 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3977 condition = self.sql(expression, "condition") 3978 condition = f" AND {condition}" if condition else "" 3979 3980 then_expression = expression.args.get("then") 3981 if isinstance(then_expression, exp.Insert): 3982 this = self.sql(then_expression, "this") 3983 this = f"INSERT {this}" if this else "INSERT" 3984 then = self.sql(then_expression, "expression") 3985 then = f"{this} VALUES {then}" if then else this 3986 elif isinstance(then_expression, exp.Update): 3987 if isinstance(then_expression.args.get("expressions"), exp.Star): 3988 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3989 else: 3990 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3991 else: 3992 then = self.sql(then_expression) 3993 return f"WHEN {matched}{source}{condition} THEN {then}"
3998 def merge_sql(self, expression: exp.Merge) -> str: 3999 table = expression.this 4000 table_alias = "" 4001 4002 hints = table.args.get("hints") 4003 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 4004 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 4005 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 4006 4007 this = self.sql(table) 4008 using = f"USING {self.sql(expression, 'using')}" 4009 on = f"ON {self.sql(expression, 'on')}" 4010 whens = self.sql(expression, "whens") 4011 4012 returning = self.sql(expression, "returning") 4013 if returning: 4014 whens = f"{whens}{returning}" 4015 4016 sep = self.sep() 4017 4018 return self.prepend_ctes( 4019 expression, 4020 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 4021 )
4027 def tonumber_sql(self, expression: exp.ToNumber) -> str: 4028 if not self.SUPPORTS_TO_NUMBER: 4029 self.unsupported("Unsupported TO_NUMBER function") 4030 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4031 4032 fmt = expression.args.get("format") 4033 if not fmt: 4034 self.unsupported("Conversion format is required for TO_NUMBER") 4035 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4036 4037 return self.func("TO_NUMBER", expression.this, fmt)
4039 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 4040 this = self.sql(expression, "this") 4041 kind = self.sql(expression, "kind") 4042 settings_sql = self.expressions(expression, key="settings", sep=" ") 4043 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 4044 return f"{this}({kind}{args})"
def
uniquekeyproperty_sql( self, expression: sqlglot.expressions.UniqueKeyProperty, prefix: str = 'UNIQUE KEY') -> str:
4065 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 4066 expressions = self.expressions(expression, flat=True) 4067 expressions = f" {self.wrap(expressions)}" if expressions else "" 4068 buckets = self.sql(expression, "buckets") 4069 kind = self.sql(expression, "kind") 4070 buckets = f" BUCKETS {buckets}" if buckets else "" 4071 order = self.sql(expression, "order") 4072 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4077 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4078 expressions = self.expressions(expression, key="expressions", flat=True) 4079 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4080 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4081 buckets = self.sql(expression, "buckets") 4082 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4084 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4085 this = self.sql(expression, "this") 4086 having = self.sql(expression, "having") 4087 4088 if having: 4089 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4090 4091 return self.func("ANY_VALUE", this)
4093 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4094 transform = self.func("TRANSFORM", *expression.expressions) 4095 row_format_before = self.sql(expression, "row_format_before") 4096 row_format_before = f" {row_format_before}" if row_format_before else "" 4097 record_writer = self.sql(expression, "record_writer") 4098 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4099 using = f" USING {self.sql(expression, 'command_script')}" 4100 schema = self.sql(expression, "schema") 4101 schema = f" AS {schema}" if schema else "" 4102 row_format_after = self.sql(expression, "row_format_after") 4103 row_format_after = f" {row_format_after}" if row_format_after else "" 4104 record_reader = self.sql(expression, "record_reader") 4105 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4106 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4108 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4109 key_block_size = self.sql(expression, "key_block_size") 4110 if key_block_size: 4111 return f"KEY_BLOCK_SIZE = {key_block_size}" 4112 4113 using = self.sql(expression, "using") 4114 if using: 4115 return f"USING {using}" 4116 4117 parser = self.sql(expression, "parser") 4118 if parser: 4119 return f"WITH PARSER {parser}" 4120 4121 comment = self.sql(expression, "comment") 4122 if comment: 4123 return f"COMMENT {comment}" 4124 4125 visible = expression.args.get("visible") 4126 if visible is not None: 4127 return "VISIBLE" if visible else "INVISIBLE" 4128 4129 engine_attr = self.sql(expression, "engine_attr") 4130 if engine_attr: 4131 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4132 4133 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4134 if secondary_engine_attr: 4135 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4136 4137 self.unsupported("Unsupported index constraint option.") 4138 return ""
4144 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4145 kind = self.sql(expression, "kind") 4146 kind = f"{kind} INDEX" if kind else "INDEX" 4147 this = self.sql(expression, "this") 4148 this = f" {this}" if this else "" 4149 index_type = self.sql(expression, "index_type") 4150 index_type = f" USING {index_type}" if index_type else "" 4151 expressions = self.expressions(expression, flat=True) 4152 expressions = f" ({expressions})" if expressions else "" 4153 options = self.expressions(expression, key="options", sep=" ") 4154 options = f" {options}" if options else "" 4155 return f"{kind}{this}{index_type}{expressions}{options}"
4157 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4158 if self.NVL2_SUPPORTED: 4159 return self.function_fallback_sql(expression) 4160 4161 case = exp.Case().when( 4162 expression.this.is_(exp.null()).not_(copy=False), 4163 expression.args["true"], 4164 copy=False, 4165 ) 4166 else_cond = expression.args.get("false") 4167 if else_cond: 4168 case.else_(else_cond, copy=False) 4169 4170 return self.sql(case)
4172 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4173 this = self.sql(expression, "this") 4174 expr = self.sql(expression, "expression") 4175 iterator = self.sql(expression, "iterator") 4176 condition = self.sql(expression, "condition") 4177 condition = f" IF {condition}" if condition else "" 4178 return f"{this} FOR {expr} IN {iterator}{condition}"
4186 def predict_sql(self, expression: exp.Predict) -> str: 4187 model = self.sql(expression, "this") 4188 model = f"MODEL {model}" 4189 table = self.sql(expression, "expression") 4190 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4191 parameters = self.sql(expression, "params_struct") 4192 return self.func("PREDICT", model, table, parameters or None)
4194 def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str: 4195 model = self.sql(expression, "this") 4196 model = f"MODEL {model}" 4197 table = self.sql(expression, "expression") 4198 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4199 parameters = self.sql(expression, "params_struct") 4200 return self.func("GENERATE_EMBEDDING", model, table, parameters or None)
4202 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4203 this_sql = self.sql(expression, "this") 4204 if isinstance(expression.this, exp.Table): 4205 this_sql = f"TABLE {this_sql}" 4206 4207 return self.func( 4208 "FEATURES_AT_TIME", 4209 this_sql, 4210 expression.args.get("time"), 4211 expression.args.get("num_rows"), 4212 expression.args.get("ignore_feature_nulls"), 4213 )
4215 def vectorsearch_sql(self, expression: exp.VectorSearch) -> str: 4216 this_sql = self.sql(expression, "this") 4217 if isinstance(expression.this, exp.Table): 4218 this_sql = f"TABLE {this_sql}" 4219 4220 query_table = self.sql(expression, "query_table") 4221 if isinstance(expression.args["query_table"], exp.Table): 4222 query_table = f"TABLE {query_table}" 4223 4224 return self.func( 4225 "VECTOR_SEARCH", 4226 this_sql, 4227 expression.args.get("column_to_search"), 4228 query_table, 4229 expression.args.get("query_column_to_search"), 4230 expression.args.get("top_k"), 4231 expression.args.get("distance_type"), 4232 expression.args.get("options"), 4233 )
4245 def toarray_sql(self, expression: exp.ToArray) -> str: 4246 arg = expression.this 4247 if not arg.type: 4248 from sqlglot.optimizer.annotate_types import annotate_types 4249 4250 arg = annotate_types(arg, dialect=self.dialect) 4251 4252 if arg.is_type(exp.DataType.Type.ARRAY): 4253 return self.sql(arg) 4254 4255 cond_for_null = arg.is_(exp.null()) 4256 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4258 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4259 this = expression.this 4260 time_format = self.format_time(expression) 4261 4262 if time_format: 4263 return self.sql( 4264 exp.cast( 4265 exp.StrToTime(this=this, format=expression.args["format"]), 4266 exp.DataType.Type.TIME, 4267 ) 4268 ) 4269 4270 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4271 return self.sql(this) 4272 4273 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4275 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4276 this = expression.this 4277 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4278 return self.sql(this) 4279 4280 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4282 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4283 this = expression.this 4284 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4285 return self.sql(this) 4286 4287 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4289 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4290 this = expression.this 4291 time_format = self.format_time(expression) 4292 4293 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4294 return self.sql( 4295 exp.cast( 4296 exp.StrToTime(this=this, format=expression.args["format"]), 4297 exp.DataType.Type.DATE, 4298 ) 4299 ) 4300 4301 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4302 return self.sql(this) 4303 4304 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4316 def lastday_sql(self, expression: exp.LastDay) -> str: 4317 if self.LAST_DAY_SUPPORTS_DATE_PART: 4318 return self.function_fallback_sql(expression) 4319 4320 unit = expression.text("unit") 4321 if unit and unit != "MONTH": 4322 self.unsupported("Date parts are not supported in LAST_DAY.") 4323 4324 return self.func("LAST_DAY", expression.this)
4333 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4334 if self.CAN_IMPLEMENT_ARRAY_ANY: 4335 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4336 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4337 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4338 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4339 4340 from sqlglot.dialects import Dialect 4341 4342 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4343 if self.dialect.__class__ != Dialect: 4344 self.unsupported("ARRAY_ANY is unsupported") 4345 4346 return self.function_fallback_sql(expression)
4348 def struct_sql(self, expression: exp.Struct) -> str: 4349 expression.set( 4350 "expressions", 4351 [ 4352 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4353 if isinstance(e, exp.PropertyEQ) 4354 else e 4355 for e in expression.expressions 4356 ], 4357 ) 4358 4359 return self.function_fallback_sql(expression)
4367 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4368 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4369 tables = f" {self.expressions(expression)}" 4370 4371 exists = " IF EXISTS" if expression.args.get("exists") else "" 4372 4373 on_cluster = self.sql(expression, "cluster") 4374 on_cluster = f" {on_cluster}" if on_cluster else "" 4375 4376 identity = self.sql(expression, "identity") 4377 identity = f" {identity} IDENTITY" if identity else "" 4378 4379 option = self.sql(expression, "option") 4380 option = f" {option}" if option else "" 4381 4382 partition = self.sql(expression, "partition") 4383 partition = f" {partition}" if partition else "" 4384 4385 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4389 def convert_sql(self, expression: exp.Convert) -> str: 4390 to = expression.this 4391 value = expression.expression 4392 style = expression.args.get("style") 4393 safe = expression.args.get("safe") 4394 strict = expression.args.get("strict") 4395 4396 if not to or not value: 4397 return "" 4398 4399 # Retrieve length of datatype and override to default if not specified 4400 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4401 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4402 4403 transformed: t.Optional[exp.Expression] = None 4404 cast = exp.Cast if strict else exp.TryCast 4405 4406 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4407 if isinstance(style, exp.Literal) and style.is_int: 4408 from sqlglot.dialects.tsql import TSQL 4409 4410 style_value = style.name 4411 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4412 if not converted_style: 4413 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4414 4415 fmt = exp.Literal.string(converted_style) 4416 4417 if to.this == exp.DataType.Type.DATE: 4418 transformed = exp.StrToDate(this=value, format=fmt) 4419 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4420 transformed = exp.StrToTime(this=value, format=fmt) 4421 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4422 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4423 elif to.this == exp.DataType.Type.TEXT: 4424 transformed = exp.TimeToStr(this=value, format=fmt) 4425 4426 if not transformed: 4427 transformed = cast(this=value, to=to, safe=safe) 4428 4429 return self.sql(transformed)
4497 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4498 option = self.sql(expression, "this") 4499 4500 if expression.expressions: 4501 upper = option.upper() 4502 4503 # Snowflake FILE_FORMAT options are separated by whitespace 4504 sep = " " if upper == "FILE_FORMAT" else ", " 4505 4506 # Databricks copy/format options do not set their list of values with EQ 4507 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4508 values = self.expressions(expression, flat=True, sep=sep) 4509 return f"{option}{op}({values})" 4510 4511 value = self.sql(expression, "expression") 4512 4513 if not value: 4514 return option 4515 4516 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4517 4518 return f"{option}{op}{value}"
4520 def credentials_sql(self, expression: exp.Credentials) -> str: 4521 cred_expr = expression.args.get("credentials") 4522 if isinstance(cred_expr, exp.Literal): 4523 # Redshift case: CREDENTIALS <string> 4524 credentials = self.sql(expression, "credentials") 4525 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4526 else: 4527 # Snowflake case: CREDENTIALS = (...) 4528 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4529 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4530 4531 storage = self.sql(expression, "storage") 4532 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4533 4534 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4535 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4536 4537 iam_role = self.sql(expression, "iam_role") 4538 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4539 4540 region = self.sql(expression, "region") 4541 region = f" REGION {region}" if region else "" 4542 4543 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4545 def copy_sql(self, expression: exp.Copy) -> str: 4546 this = self.sql(expression, "this") 4547 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4548 4549 credentials = self.sql(expression, "credentials") 4550 credentials = self.seg(credentials) if credentials else "" 4551 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4552 files = self.expressions(expression, key="files", flat=True) 4553 4554 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4555 params = self.expressions( 4556 expression, 4557 key="params", 4558 sep=sep, 4559 new_line=True, 4560 skip_last=True, 4561 skip_first=True, 4562 indent=self.COPY_PARAMS_ARE_WRAPPED, 4563 ) 4564 4565 if params: 4566 if self.COPY_PARAMS_ARE_WRAPPED: 4567 params = f" WITH ({params})" 4568 elif not self.pretty: 4569 params = f" {params}" 4570 4571 return f"COPY{this}{kind} {files}{credentials}{params}"
4576 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4577 on_sql = "ON" if expression.args.get("on") else "OFF" 4578 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4579 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4580 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4581 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4582 4583 if filter_col or retention_period: 4584 on_sql = self.func("ON", filter_col, retention_period) 4585 4586 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4588 def maskingpolicycolumnconstraint_sql( 4589 self, expression: exp.MaskingPolicyColumnConstraint 4590 ) -> str: 4591 this = self.sql(expression, "this") 4592 expressions = self.expressions(expression, flat=True) 4593 expressions = f" USING ({expressions})" if expressions else "" 4594 return f"MASKING POLICY {this}{expressions}"
4604 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4605 this = self.sql(expression, "this") 4606 expr = expression.expression 4607 4608 if isinstance(expr, exp.Func): 4609 # T-SQL's CLR functions are case sensitive 4610 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4611 else: 4612 expr = self.sql(expression, "expression") 4613 4614 return self.scope_resolution(expr, this)
4622 def rand_sql(self, expression: exp.Rand) -> str: 4623 lower = self.sql(expression, "lower") 4624 upper = self.sql(expression, "upper") 4625 4626 if lower and upper: 4627 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4628 return self.func("RAND", expression.this)
4630 def changes_sql(self, expression: exp.Changes) -> str: 4631 information = self.sql(expression, "information") 4632 information = f"INFORMATION => {information}" 4633 at_before = self.sql(expression, "at_before") 4634 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4635 end = self.sql(expression, "end") 4636 end = f"{self.seg('')}{end}" if end else "" 4637 4638 return f"CHANGES ({information}){at_before}{end}"
4640 def pad_sql(self, expression: exp.Pad) -> str: 4641 prefix = "L" if expression.args.get("is_left") else "R" 4642 4643 fill_pattern = self.sql(expression, "fill_pattern") or None 4644 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4645 fill_pattern = "' '" 4646 4647 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4653 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4654 generate_series = exp.GenerateSeries(**expression.args) 4655 4656 parent = expression.parent 4657 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4658 parent = parent.parent 4659 4660 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4661 return self.sql(exp.Unnest(expressions=[generate_series])) 4662 4663 if isinstance(parent, exp.Select): 4664 self.unsupported("GenerateSeries projection unnesting is not supported.") 4665 4666 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4668 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4669 exprs = expression.expressions 4670 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4671 if len(exprs) == 0: 4672 rhs: t.Union[str, exp.Expression] = exp.Array(expressions=[]) 4673 else: 4674 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4675 else: 4676 rhs = self.expressions(expression) # type: ignore 4677 4678 return self.func(name, expression.this, rhs or None)
4680 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4681 if self.SUPPORTS_CONVERT_TIMEZONE: 4682 return self.function_fallback_sql(expression) 4683 4684 source_tz = expression.args.get("source_tz") 4685 target_tz = expression.args.get("target_tz") 4686 timestamp = expression.args.get("timestamp") 4687 4688 if source_tz and timestamp: 4689 timestamp = exp.AtTimeZone( 4690 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4691 ) 4692 4693 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4694 4695 return self.sql(expr)
4697 def json_sql(self, expression: exp.JSON) -> str: 4698 this = self.sql(expression, "this") 4699 this = f" {this}" if this else "" 4700 4701 _with = expression.args.get("with") 4702 4703 if _with is None: 4704 with_sql = "" 4705 elif not _with: 4706 with_sql = " WITHOUT" 4707 else: 4708 with_sql = " WITH" 4709 4710 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4711 4712 return f"JSON{this}{with_sql}{unique_sql}"
4714 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4715 def _generate_on_options(arg: t.Any) -> str: 4716 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4717 4718 path = self.sql(expression, "path") 4719 returning = self.sql(expression, "returning") 4720 returning = f" RETURNING {returning}" if returning else "" 4721 4722 on_condition = self.sql(expression, "on_condition") 4723 on_condition = f" {on_condition}" if on_condition else "" 4724 4725 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4727 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4728 else_ = "ELSE " if expression.args.get("else_") else "" 4729 condition = self.sql(expression, "expression") 4730 condition = f"WHEN {condition} THEN " if condition else else_ 4731 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4732 return f"{condition}{insert}"
4740 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4741 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4742 empty = expression.args.get("empty") 4743 empty = ( 4744 f"DEFAULT {empty} ON EMPTY" 4745 if isinstance(empty, exp.Expression) 4746 else self.sql(expression, "empty") 4747 ) 4748 4749 error = expression.args.get("error") 4750 error = ( 4751 f"DEFAULT {error} ON ERROR" 4752 if isinstance(error, exp.Expression) 4753 else self.sql(expression, "error") 4754 ) 4755 4756 if error and empty: 4757 error = ( 4758 f"{empty} {error}" 4759 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4760 else f"{error} {empty}" 4761 ) 4762 empty = "" 4763 4764 null = self.sql(expression, "null") 4765 4766 return f"{empty}{error}{null}"
4772 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4773 this = self.sql(expression, "this") 4774 path = self.sql(expression, "path") 4775 4776 passing = self.expressions(expression, "passing") 4777 passing = f" PASSING {passing}" if passing else "" 4778 4779 on_condition = self.sql(expression, "on_condition") 4780 on_condition = f" {on_condition}" if on_condition else "" 4781 4782 path = f"{path}{passing}{on_condition}" 4783 4784 return self.func("JSON_EXISTS", this, path)
4786 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4787 array_agg = self.function_fallback_sql(expression) 4788 4789 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4790 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4791 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4792 parent = expression.parent 4793 if isinstance(parent, exp.Filter): 4794 parent_cond = parent.expression.this 4795 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4796 else: 4797 this = expression.this 4798 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4799 if this.find(exp.Column): 4800 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4801 this_sql = ( 4802 self.expressions(this) 4803 if isinstance(this, exp.Distinct) 4804 else self.sql(expression, "this") 4805 ) 4806 4807 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4808 4809 return array_agg
4882 def overlay_sql(self, expression: exp.Overlay): 4883 this = self.sql(expression, "this") 4884 expr = self.sql(expression, "expression") 4885 from_sql = self.sql(expression, "from") 4886 for_sql = self.sql(expression, "for") 4887 for_sql = f" FOR {for_sql}" if for_sql else "" 4888 4889 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4895 def string_sql(self, expression: exp.String) -> str: 4896 this = expression.this 4897 zone = expression.args.get("zone") 4898 4899 if zone: 4900 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4901 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4902 # set for source_tz to transpile the time conversion before the STRING cast 4903 this = exp.ConvertTimezone( 4904 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4905 ) 4906 4907 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4917 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4918 filler = self.sql(expression, "this") 4919 filler = f" {filler}" if filler else "" 4920 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4921 return f"TRUNCATE{filler} {with_count}"
4923 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4924 if self.SUPPORTS_UNIX_SECONDS: 4925 return self.function_fallback_sql(expression) 4926 4927 start_ts = exp.cast( 4928 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4929 ) 4930 4931 return self.sql( 4932 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4933 )
4935 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4936 dim = expression.expression 4937 4938 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4939 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4940 if not (dim.is_int and dim.name == "1"): 4941 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4942 dim = None 4943 4944 # If dimension is required but not specified, default initialize it 4945 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4946 dim = exp.Literal.number(1) 4947 4948 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4950 def attach_sql(self, expression: exp.Attach) -> str: 4951 this = self.sql(expression, "this") 4952 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4953 expressions = self.expressions(expression) 4954 expressions = f" ({expressions})" if expressions else "" 4955 4956 return f"ATTACH{exists_sql} {this}{expressions}"
4958 def detach_sql(self, expression: exp.Detach) -> str: 4959 this = self.sql(expression, "this") 4960 # the DATABASE keyword is required if IF EXISTS is set 4961 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4962 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4963 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4964 4965 return f"DETACH{exists_sql} {this}"
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4978 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4979 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4980 encode = f"{encode} {self.sql(expression, 'this')}" 4981 4982 properties = expression.args.get("properties") 4983 if properties: 4984 encode = f"{encode} {self.properties(properties)}" 4985 4986 return encode
4988 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4989 this = self.sql(expression, "this") 4990 include = f"INCLUDE {this}" 4991 4992 column_def = self.sql(expression, "column_def") 4993 if column_def: 4994 include = f"{include} {column_def}" 4995 4996 alias = self.sql(expression, "alias") 4997 if alias: 4998 include = f"{include} AS {alias}" 4999 5000 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
5012 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 5013 partitions = self.expressions(expression, "partition_expressions") 5014 create = self.expressions(expression, "create_expressions") 5015 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
5017 def partitionbyrangepropertydynamic_sql( 5018 self, expression: exp.PartitionByRangePropertyDynamic 5019 ) -> str: 5020 start = self.sql(expression, "start") 5021 end = self.sql(expression, "end") 5022 5023 every = expression.args["every"] 5024 if isinstance(every, exp.Interval) and every.this.is_string: 5025 every.this.replace(exp.Literal.number(every.name)) 5026 5027 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
5040 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 5041 kind = self.sql(expression, "kind") 5042 option = self.sql(expression, "option") 5043 option = f" {option}" if option else "" 5044 this = self.sql(expression, "this") 5045 this = f" {this}" if this else "" 5046 columns = self.expressions(expression) 5047 columns = f" {columns}" if columns else "" 5048 return f"{kind}{option} STATISTICS{this}{columns}"
5050 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 5051 this = self.sql(expression, "this") 5052 columns = self.expressions(expression) 5053 inner_expression = self.sql(expression, "expression") 5054 inner_expression = f" {inner_expression}" if inner_expression else "" 5055 update_options = self.sql(expression, "update_options") 5056 update_options = f" {update_options} UPDATE" if update_options else "" 5057 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
5068 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 5069 kind = self.sql(expression, "kind") 5070 this = self.sql(expression, "this") 5071 this = f" {this}" if this else "" 5072 inner_expression = self.sql(expression, "expression") 5073 return f"VALIDATE {kind}{this}{inner_expression}"
5075 def analyze_sql(self, expression: exp.Analyze) -> str: 5076 options = self.expressions(expression, key="options", sep=" ") 5077 options = f" {options}" if options else "" 5078 kind = self.sql(expression, "kind") 5079 kind = f" {kind}" if kind else "" 5080 this = self.sql(expression, "this") 5081 this = f" {this}" if this else "" 5082 mode = self.sql(expression, "mode") 5083 mode = f" {mode}" if mode else "" 5084 properties = self.sql(expression, "properties") 5085 properties = f" {properties}" if properties else "" 5086 partition = self.sql(expression, "partition") 5087 partition = f" {partition}" if partition else "" 5088 inner_expression = self.sql(expression, "expression") 5089 inner_expression = f" {inner_expression}" if inner_expression else "" 5090 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
5092 def xmltable_sql(self, expression: exp.XMLTable) -> str: 5093 this = self.sql(expression, "this") 5094 namespaces = self.expressions(expression, key="namespaces") 5095 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 5096 passing = self.expressions(expression, key="passing") 5097 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 5098 columns = self.expressions(expression, key="columns") 5099 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 5100 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 5101 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
5107 def export_sql(self, expression: exp.Export) -> str: 5108 this = self.sql(expression, "this") 5109 connection = self.sql(expression, "connection") 5110 connection = f"WITH CONNECTION {connection} " if connection else "" 5111 options = self.sql(expression, "options") 5112 return f"EXPORT DATA {connection}{options} AS {this}"
5117 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 5118 variable = self.sql(expression, "this") 5119 default = self.sql(expression, "default") 5120 default = f" = {default}" if default else "" 5121 5122 kind = self.sql(expression, "kind") 5123 if isinstance(expression.args.get("kind"), exp.Schema): 5124 kind = f"TABLE {kind}" 5125 5126 return f"{variable} AS {kind}{default}"
5128 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5129 kind = self.sql(expression, "kind") 5130 this = self.sql(expression, "this") 5131 set = self.sql(expression, "expression") 5132 using = self.sql(expression, "using") 5133 using = f" USING {using}" if using else "" 5134 5135 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5136 5137 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5156 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5157 # Snowflake GET/PUT statements: 5158 # PUT <file> <internalStage> <properties> 5159 # GET <internalStage> <file> <properties> 5160 props = expression.args.get("properties") 5161 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5162 this = self.sql(expression, "this") 5163 target = self.sql(expression, "target") 5164 5165 if isinstance(expression, exp.Put): 5166 return f"PUT {this} {target}{props_sql}" 5167 else: 5168 return f"GET {target} {this}{props_sql}"
5176 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5177 if self.SUPPORTS_DECODE_CASE: 5178 return self.func("DECODE", *expression.expressions) 5179 5180 expression, *expressions = expression.expressions 5181 5182 ifs = [] 5183 for search, result in zip(expressions[::2], expressions[1::2]): 5184 if isinstance(search, exp.Literal): 5185 ifs.append(exp.If(this=expression.eq(search), true=result)) 5186 elif isinstance(search, exp.Null): 5187 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5188 else: 5189 if isinstance(search, exp.Binary): 5190 search = exp.paren(search) 5191 5192 cond = exp.or_( 5193 expression.eq(search), 5194 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5195 copy=False, 5196 ) 5197 ifs.append(exp.If(this=cond, true=result)) 5198 5199 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5200 return self.sql(case)
5202 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5203 this = self.sql(expression, "this") 5204 this = self.seg(this, sep="") 5205 dimensions = self.expressions( 5206 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5207 ) 5208 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5209 metrics = self.expressions( 5210 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5211 ) 5212 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5213 where = self.sql(expression, "where") 5214 where = self.seg(f"WHERE {where}") if where else "" 5215 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"
5217 def getextract_sql(self, expression: exp.GetExtract) -> str: 5218 this = expression.this 5219 expr = expression.expression 5220 5221 if not this.type or not expression.type: 5222 from sqlglot.optimizer.annotate_types import annotate_types 5223 5224 this = annotate_types(this, dialect=self.dialect) 5225 5226 if this.is_type(*(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)): 5227 return self.sql(exp.Bracket(this=this, expressions=[expr])) 5228 5229 return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))
def
refreshtriggerproperty_sql(self, expression: sqlglot.expressions.RefreshTriggerProperty) -> str:
5246 def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str: 5247 method = self.sql(expression, "method") 5248 kind = expression.args.get("kind") 5249 if not kind: 5250 return f"REFRESH {method}" 5251 5252 every = self.sql(expression, "every") 5253 unit = self.sql(expression, "unit") 5254 every = f" EVERY {every} {unit}" if every else "" 5255 starts = self.sql(expression, "starts") 5256 starts = f" STARTS {starts}" if starts else "" 5257 5258 return f"REFRESH {method} ON {kind}{every}{starts}"