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.ProjectionPolicyColumnConstraint: lambda self, 182 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 183 exp.Put: lambda self, e: self.get_put_sql(e), 184 exp.RemoteWithConnectionModelProperty: lambda self, 185 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 186 exp.ReturnsProperty: lambda self, e: ( 187 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 188 ), 189 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 190 exp.SecureProperty: lambda *_: "SECURE", 191 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 192 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 193 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 194 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 195 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 196 exp.SqlReadWriteProperty: lambda _, e: e.name, 197 exp.SqlSecurityProperty: lambda _, 198 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 199 exp.StabilityProperty: lambda _, e: e.name, 200 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 201 exp.StreamingTableProperty: lambda *_: "STREAMING", 202 exp.StrictProperty: lambda *_: "STRICT", 203 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 204 exp.TableColumn: lambda self, e: self.sql(e.this), 205 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 206 exp.TemporaryProperty: lambda *_: "TEMPORARY", 207 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 208 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 209 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 210 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 211 exp.TransientProperty: lambda *_: "TRANSIENT", 212 exp.Union: lambda self, e: self.set_operations(e), 213 exp.UnloggedProperty: lambda *_: "UNLOGGED", 214 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 215 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 216 exp.Uuid: lambda *_: "UUID()", 217 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 218 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 219 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 220 exp.VolatileProperty: lambda *_: "VOLATILE", 221 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 222 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 223 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 224 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 225 exp.ForceProperty: lambda *_: "FORCE", 226 } 227 228 # Whether null ordering is supported in order by 229 # True: Full Support, None: No support, False: No support for certain cases 230 # such as window specifications, aggregate functions etc 231 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 232 233 # Whether ignore nulls is inside the agg or outside. 234 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 235 IGNORE_NULLS_IN_FUNC = False 236 237 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 238 LOCKING_READS_SUPPORTED = False 239 240 # Whether the EXCEPT and INTERSECT operations can return duplicates 241 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 242 243 # Wrap derived values in parens, usually standard but spark doesn't support it 244 WRAP_DERIVED_VALUES = True 245 246 # Whether create function uses an AS before the RETURN 247 CREATE_FUNCTION_RETURN_AS = True 248 249 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 250 MATCHED_BY_SOURCE = True 251 252 # Whether the INTERVAL expression works only with values like '1 day' 253 SINGLE_STRING_INTERVAL = False 254 255 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 256 INTERVAL_ALLOWS_PLURAL_FORM = True 257 258 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 259 LIMIT_FETCH = "ALL" 260 261 # Whether limit and fetch allows expresions or just limits 262 LIMIT_ONLY_LITERALS = False 263 264 # Whether a table is allowed to be renamed with a db 265 RENAME_TABLE_WITH_DB = True 266 267 # The separator for grouping sets and rollups 268 GROUPINGS_SEP = "," 269 270 # The string used for creating an index on a table 271 INDEX_ON = "ON" 272 273 # Whether join hints should be generated 274 JOIN_HINTS = True 275 276 # Whether table hints should be generated 277 TABLE_HINTS = True 278 279 # Whether query hints should be generated 280 QUERY_HINTS = True 281 282 # What kind of separator to use for query hints 283 QUERY_HINT_SEP = ", " 284 285 # Whether comparing against booleans (e.g. x IS TRUE) is supported 286 IS_BOOL_ALLOWED = True 287 288 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 289 DUPLICATE_KEY_UPDATE_WITH_SET = True 290 291 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 292 LIMIT_IS_TOP = False 293 294 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 295 RETURNING_END = True 296 297 # Whether to generate an unquoted value for EXTRACT's date part argument 298 EXTRACT_ALLOWS_QUOTES = True 299 300 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 301 TZ_TO_WITH_TIME_ZONE = False 302 303 # Whether the NVL2 function is supported 304 NVL2_SUPPORTED = True 305 306 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 307 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 308 309 # Whether VALUES statements can be used as derived tables. 310 # MySQL 5 and Redshift do not allow this, so when False, it will convert 311 # SELECT * VALUES into SELECT UNION 312 VALUES_AS_TABLE = True 313 314 # Whether the word COLUMN is included when adding a column with ALTER TABLE 315 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 316 317 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 318 UNNEST_WITH_ORDINALITY = True 319 320 # Whether FILTER (WHERE cond) can be used for conditional aggregation 321 AGGREGATE_FILTER_SUPPORTED = True 322 323 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 324 SEMI_ANTI_JOIN_WITH_SIDE = True 325 326 # Whether to include the type of a computed column in the CREATE DDL 327 COMPUTED_COLUMN_WITH_TYPE = True 328 329 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 330 SUPPORTS_TABLE_COPY = True 331 332 # Whether parentheses are required around the table sample's expression 333 TABLESAMPLE_REQUIRES_PARENS = True 334 335 # Whether a table sample clause's size needs to be followed by the ROWS keyword 336 TABLESAMPLE_SIZE_IS_ROWS = True 337 338 # The keyword(s) to use when generating a sample clause 339 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 340 341 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 342 TABLESAMPLE_WITH_METHOD = True 343 344 # The keyword to use when specifying the seed of a sample clause 345 TABLESAMPLE_SEED_KEYWORD = "SEED" 346 347 # Whether COLLATE is a function instead of a binary operator 348 COLLATE_IS_FUNC = False 349 350 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 351 DATA_TYPE_SPECIFIERS_ALLOWED = False 352 353 # Whether conditions require booleans WHERE x = 0 vs WHERE x 354 ENSURE_BOOLS = False 355 356 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 357 CTE_RECURSIVE_KEYWORD_REQUIRED = True 358 359 # Whether CONCAT requires >1 arguments 360 SUPPORTS_SINGLE_ARG_CONCAT = True 361 362 # Whether LAST_DAY function supports a date part argument 363 LAST_DAY_SUPPORTS_DATE_PART = True 364 365 # Whether named columns are allowed in table aliases 366 SUPPORTS_TABLE_ALIAS_COLUMNS = True 367 368 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 369 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 370 371 # What delimiter to use for separating JSON key/value pairs 372 JSON_KEY_VALUE_PAIR_SEP = ":" 373 374 # INSERT OVERWRITE TABLE x override 375 INSERT_OVERWRITE = " OVERWRITE TABLE" 376 377 # Whether the SELECT .. INTO syntax is used instead of CTAS 378 SUPPORTS_SELECT_INTO = False 379 380 # Whether UNLOGGED tables can be created 381 SUPPORTS_UNLOGGED_TABLES = False 382 383 # Whether the CREATE TABLE LIKE statement is supported 384 SUPPORTS_CREATE_TABLE_LIKE = True 385 386 # Whether the LikeProperty needs to be specified inside of the schema clause 387 LIKE_PROPERTY_INSIDE_SCHEMA = False 388 389 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 390 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 391 MULTI_ARG_DISTINCT = True 392 393 # Whether the JSON extraction operators expect a value of type JSON 394 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 395 396 # Whether bracketed keys like ["foo"] are supported in JSON paths 397 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 398 399 # Whether to escape keys using single quotes in JSON paths 400 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 401 402 # The JSONPathPart expressions supported by this dialect 403 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 404 405 # Whether any(f(x) for x in array) can be implemented by this dialect 406 CAN_IMPLEMENT_ARRAY_ANY = False 407 408 # Whether the function TO_NUMBER is supported 409 SUPPORTS_TO_NUMBER = True 410 411 # Whether EXCLUDE in window specification is supported 412 SUPPORTS_WINDOW_EXCLUDE = False 413 414 # Whether or not set op modifiers apply to the outer set op or select. 415 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 416 # True means limit 1 happens after the set op, False means it it happens on y. 417 SET_OP_MODIFIERS = True 418 419 # Whether parameters from COPY statement are wrapped in parentheses 420 COPY_PARAMS_ARE_WRAPPED = True 421 422 # Whether values of params are set with "=" token or empty space 423 COPY_PARAMS_EQ_REQUIRED = False 424 425 # Whether COPY statement has INTO keyword 426 COPY_HAS_INTO_KEYWORD = True 427 428 # Whether the conditional TRY(expression) function is supported 429 TRY_SUPPORTED = True 430 431 # Whether the UESCAPE syntax in unicode strings is supported 432 SUPPORTS_UESCAPE = True 433 434 # The keyword to use when generating a star projection with excluded columns 435 STAR_EXCEPT = "EXCEPT" 436 437 # The HEX function name 438 HEX_FUNC = "HEX" 439 440 # The keywords to use when prefixing & separating WITH based properties 441 WITH_PROPERTIES_PREFIX = "WITH" 442 443 # Whether to quote the generated expression of exp.JsonPath 444 QUOTE_JSON_PATH = True 445 446 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 447 PAD_FILL_PATTERN_IS_REQUIRED = False 448 449 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 450 SUPPORTS_EXPLODING_PROJECTIONS = True 451 452 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 453 ARRAY_CONCAT_IS_VAR_LEN = True 454 455 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 456 SUPPORTS_CONVERT_TIMEZONE = False 457 458 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 459 SUPPORTS_MEDIAN = True 460 461 # Whether UNIX_SECONDS(timestamp) is supported 462 SUPPORTS_UNIX_SECONDS = False 463 464 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 465 ALTER_SET_WRAPPED = False 466 467 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 468 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 469 # TODO: The normalization should be done by default once we've tested it across all dialects. 470 NORMALIZE_EXTRACT_DATE_PARTS = False 471 472 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 473 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 474 475 # The function name of the exp.ArraySize expression 476 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 477 478 # The syntax to use when altering the type of a column 479 ALTER_SET_TYPE = "SET DATA TYPE" 480 481 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 482 # None -> Doesn't support it at all 483 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 484 # True (Postgres) -> Explicitly requires it 485 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 486 487 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 488 SUPPORTS_DECODE_CASE = True 489 490 TYPE_MAPPING = { 491 exp.DataType.Type.DATETIME2: "TIMESTAMP", 492 exp.DataType.Type.NCHAR: "CHAR", 493 exp.DataType.Type.NVARCHAR: "VARCHAR", 494 exp.DataType.Type.MEDIUMTEXT: "TEXT", 495 exp.DataType.Type.LONGTEXT: "TEXT", 496 exp.DataType.Type.TINYTEXT: "TEXT", 497 exp.DataType.Type.BLOB: "VARBINARY", 498 exp.DataType.Type.MEDIUMBLOB: "BLOB", 499 exp.DataType.Type.LONGBLOB: "BLOB", 500 exp.DataType.Type.TINYBLOB: "BLOB", 501 exp.DataType.Type.INET: "INET", 502 exp.DataType.Type.ROWVERSION: "VARBINARY", 503 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 504 } 505 506 TIME_PART_SINGULARS = { 507 "MICROSECONDS": "MICROSECOND", 508 "SECONDS": "SECOND", 509 "MINUTES": "MINUTE", 510 "HOURS": "HOUR", 511 "DAYS": "DAY", 512 "WEEKS": "WEEK", 513 "MONTHS": "MONTH", 514 "QUARTERS": "QUARTER", 515 "YEARS": "YEAR", 516 } 517 518 AFTER_HAVING_MODIFIER_TRANSFORMS = { 519 "cluster": lambda self, e: self.sql(e, "cluster"), 520 "distribute": lambda self, e: self.sql(e, "distribute"), 521 "sort": lambda self, e: self.sql(e, "sort"), 522 "windows": lambda self, e: ( 523 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 524 if e.args.get("windows") 525 else "" 526 ), 527 "qualify": lambda self, e: self.sql(e, "qualify"), 528 } 529 530 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 531 532 STRUCT_DELIMITER = ("<", ">") 533 534 PARAMETER_TOKEN = "@" 535 NAMED_PLACEHOLDER_TOKEN = ":" 536 537 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 538 539 PROPERTIES_LOCATION = { 540 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 541 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 542 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 543 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 544 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 545 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 546 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 547 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 548 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 549 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 550 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 551 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 552 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 555 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 556 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 557 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 558 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 559 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 560 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 561 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 562 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 564 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 568 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 569 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 570 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 571 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 572 exp.HeapProperty: exp.Properties.Location.POST_WITH, 573 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 574 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 575 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 576 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 578 exp.JournalProperty: exp.Properties.Location.POST_NAME, 579 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 584 exp.LogProperty: exp.Properties.Location.POST_NAME, 585 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 586 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 587 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 588 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 590 exp.Order: exp.Properties.Location.POST_SCHEMA, 591 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 593 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 594 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 595 exp.Property: exp.Properties.Location.POST_WITH, 596 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 600 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 604 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 606 exp.Set: exp.Properties.Location.POST_SCHEMA, 607 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.SetProperty: exp.Properties.Location.POST_CREATE, 609 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 611 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 612 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 615 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 618 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.Tags: exp.Properties.Location.POST_WITH, 620 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 621 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 623 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 625 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 626 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 629 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 630 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 631 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 632 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 635 } 636 637 # Keywords that can't be used as unquoted identifier names 638 RESERVED_KEYWORDS: t.Set[str] = set() 639 640 # Expressions whose comments are separated from them for better formatting 641 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 642 exp.Command, 643 exp.Create, 644 exp.Describe, 645 exp.Delete, 646 exp.Drop, 647 exp.From, 648 exp.Insert, 649 exp.Join, 650 exp.MultitableInserts, 651 exp.Order, 652 exp.Group, 653 exp.Having, 654 exp.Select, 655 exp.SetOperation, 656 exp.Update, 657 exp.Where, 658 exp.With, 659 ) 660 661 # Expressions that should not have their comments generated in maybe_comment 662 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 663 exp.Binary, 664 exp.SetOperation, 665 ) 666 667 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 668 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 669 exp.Column, 670 exp.Literal, 671 exp.Neg, 672 exp.Paren, 673 ) 674 675 PARAMETERIZABLE_TEXT_TYPES = { 676 exp.DataType.Type.NVARCHAR, 677 exp.DataType.Type.VARCHAR, 678 exp.DataType.Type.CHAR, 679 exp.DataType.Type.NCHAR, 680 } 681 682 # Expressions that need to have all CTEs under them bubbled up to them 683 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 684 685 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 686 687 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 688 689 __slots__ = ( 690 "pretty", 691 "identify", 692 "normalize", 693 "pad", 694 "_indent", 695 "normalize_functions", 696 "unsupported_level", 697 "max_unsupported", 698 "leading_comma", 699 "max_text_width", 700 "comments", 701 "dialect", 702 "unsupported_messages", 703 "_escaped_quote_end", 704 "_escaped_identifier_end", 705 "_next_name", 706 "_identifier_start", 707 "_identifier_end", 708 "_quote_json_path_key_using_brackets", 709 ) 710 711 def __init__( 712 self, 713 pretty: t.Optional[bool] = None, 714 identify: str | bool = False, 715 normalize: bool = False, 716 pad: int = 2, 717 indent: int = 2, 718 normalize_functions: t.Optional[str | bool] = None, 719 unsupported_level: ErrorLevel = ErrorLevel.WARN, 720 max_unsupported: int = 3, 721 leading_comma: bool = False, 722 max_text_width: int = 80, 723 comments: bool = True, 724 dialect: DialectType = None, 725 ): 726 import sqlglot 727 from sqlglot.dialects import Dialect 728 729 self.pretty = pretty if pretty is not None else sqlglot.pretty 730 self.identify = identify 731 self.normalize = normalize 732 self.pad = pad 733 self._indent = indent 734 self.unsupported_level = unsupported_level 735 self.max_unsupported = max_unsupported 736 self.leading_comma = leading_comma 737 self.max_text_width = max_text_width 738 self.comments = comments 739 self.dialect = Dialect.get_or_raise(dialect) 740 741 # This is both a Dialect property and a Generator argument, so we prioritize the latter 742 self.normalize_functions = ( 743 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 744 ) 745 746 self.unsupported_messages: t.List[str] = [] 747 self._escaped_quote_end: str = ( 748 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 749 ) 750 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 751 752 self._next_name = name_sequence("_t") 753 754 self._identifier_start = self.dialect.IDENTIFIER_START 755 self._identifier_end = self.dialect.IDENTIFIER_END 756 757 self._quote_json_path_key_using_brackets = True 758 759 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 760 """ 761 Generates the SQL string corresponding to the given syntax tree. 762 763 Args: 764 expression: The syntax tree. 765 copy: Whether to copy the expression. The generator performs mutations so 766 it is safer to copy. 767 768 Returns: 769 The SQL string corresponding to `expression`. 770 """ 771 if copy: 772 expression = expression.copy() 773 774 expression = self.preprocess(expression) 775 776 self.unsupported_messages = [] 777 sql = self.sql(expression).strip() 778 779 if self.pretty: 780 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 781 782 if self.unsupported_level == ErrorLevel.IGNORE: 783 return sql 784 785 if self.unsupported_level == ErrorLevel.WARN: 786 for msg in self.unsupported_messages: 787 logger.warning(msg) 788 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 789 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 790 791 return sql 792 793 def preprocess(self, expression: exp.Expression) -> exp.Expression: 794 """Apply generic preprocessing transformations to a given expression.""" 795 expression = self._move_ctes_to_top_level(expression) 796 797 if self.ENSURE_BOOLS: 798 from sqlglot.transforms import ensure_bools 799 800 expression = ensure_bools(expression) 801 802 return expression 803 804 def _move_ctes_to_top_level(self, expression: E) -> E: 805 if ( 806 not expression.parent 807 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 808 and any(node.parent is not expression for node in expression.find_all(exp.With)) 809 ): 810 from sqlglot.transforms import move_ctes_to_top_level 811 812 expression = move_ctes_to_top_level(expression) 813 return expression 814 815 def unsupported(self, message: str) -> None: 816 if self.unsupported_level == ErrorLevel.IMMEDIATE: 817 raise UnsupportedError(message) 818 self.unsupported_messages.append(message) 819 820 def sep(self, sep: str = " ") -> str: 821 return f"{sep.strip()}\n" if self.pretty else sep 822 823 def seg(self, sql: str, sep: str = " ") -> str: 824 return f"{self.sep(sep)}{sql}" 825 826 def sanitize_comment(self, comment: str) -> str: 827 comment = " " + comment if comment[0].strip() else comment 828 comment = comment + " " if comment[-1].strip() else comment 829 830 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 831 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 832 comment = comment.replace("*/", "* /") 833 834 return comment 835 836 def maybe_comment( 837 self, 838 sql: str, 839 expression: t.Optional[exp.Expression] = None, 840 comments: t.Optional[t.List[str]] = None, 841 separated: bool = False, 842 ) -> str: 843 comments = ( 844 ((expression and expression.comments) if comments is None else comments) # type: ignore 845 if self.comments 846 else None 847 ) 848 849 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 850 return sql 851 852 comments_sql = " ".join( 853 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 854 ) 855 856 if not comments_sql: 857 return sql 858 859 comments_sql = self._replace_line_breaks(comments_sql) 860 861 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 862 return ( 863 f"{self.sep()}{comments_sql}{sql}" 864 if not sql or sql[0].isspace() 865 else f"{comments_sql}{self.sep()}{sql}" 866 ) 867 868 return f"{sql} {comments_sql}" 869 870 def wrap(self, expression: exp.Expression | str) -> str: 871 this_sql = ( 872 self.sql(expression) 873 if isinstance(expression, exp.UNWRAPPED_QUERIES) 874 else self.sql(expression, "this") 875 ) 876 if not this_sql: 877 return "()" 878 879 this_sql = self.indent(this_sql, level=1, pad=0) 880 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 881 882 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 883 original = self.identify 884 self.identify = False 885 result = func(*args, **kwargs) 886 self.identify = original 887 return result 888 889 def normalize_func(self, name: str) -> str: 890 if self.normalize_functions == "upper" or self.normalize_functions is True: 891 return name.upper() 892 if self.normalize_functions == "lower": 893 return name.lower() 894 return name 895 896 def indent( 897 self, 898 sql: str, 899 level: int = 0, 900 pad: t.Optional[int] = None, 901 skip_first: bool = False, 902 skip_last: bool = False, 903 ) -> str: 904 if not self.pretty or not sql: 905 return sql 906 907 pad = self.pad if pad is None else pad 908 lines = sql.split("\n") 909 910 return "\n".join( 911 ( 912 line 913 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 914 else f"{' ' * (level * self._indent + pad)}{line}" 915 ) 916 for i, line in enumerate(lines) 917 ) 918 919 def sql( 920 self, 921 expression: t.Optional[str | exp.Expression], 922 key: t.Optional[str] = None, 923 comment: bool = True, 924 ) -> str: 925 if not expression: 926 return "" 927 928 if isinstance(expression, str): 929 return expression 930 931 if key: 932 value = expression.args.get(key) 933 if value: 934 return self.sql(value) 935 return "" 936 937 transform = self.TRANSFORMS.get(expression.__class__) 938 939 if callable(transform): 940 sql = transform(self, expression) 941 elif isinstance(expression, exp.Expression): 942 exp_handler_name = f"{expression.key}_sql" 943 944 if hasattr(self, exp_handler_name): 945 sql = getattr(self, exp_handler_name)(expression) 946 elif isinstance(expression, exp.Func): 947 sql = self.function_fallback_sql(expression) 948 elif isinstance(expression, exp.Property): 949 sql = self.property_sql(expression) 950 else: 951 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 952 else: 953 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 954 955 return self.maybe_comment(sql, expression) if self.comments and comment else sql 956 957 def uncache_sql(self, expression: exp.Uncache) -> str: 958 table = self.sql(expression, "this") 959 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 960 return f"UNCACHE TABLE{exists_sql} {table}" 961 962 def cache_sql(self, expression: exp.Cache) -> str: 963 lazy = " LAZY" if expression.args.get("lazy") else "" 964 table = self.sql(expression, "this") 965 options = expression.args.get("options") 966 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 967 sql = self.sql(expression, "expression") 968 sql = f" AS{self.sep()}{sql}" if sql else "" 969 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 970 return self.prepend_ctes(expression, sql) 971 972 def characterset_sql(self, expression: exp.CharacterSet) -> str: 973 if isinstance(expression.parent, exp.Cast): 974 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 975 default = "DEFAULT " if expression.args.get("default") else "" 976 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 977 978 def column_parts(self, expression: exp.Column) -> str: 979 return ".".join( 980 self.sql(part) 981 for part in ( 982 expression.args.get("catalog"), 983 expression.args.get("db"), 984 expression.args.get("table"), 985 expression.args.get("this"), 986 ) 987 if part 988 ) 989 990 def column_sql(self, expression: exp.Column) -> str: 991 join_mark = " (+)" if expression.args.get("join_mark") else "" 992 993 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 994 join_mark = "" 995 self.unsupported("Outer join syntax using the (+) operator is not supported.") 996 997 return f"{self.column_parts(expression)}{join_mark}" 998 999 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1000 this = self.sql(expression, "this") 1001 this = f" {this}" if this else "" 1002 position = self.sql(expression, "position") 1003 return f"{position}{this}" 1004 1005 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1006 column = self.sql(expression, "this") 1007 kind = self.sql(expression, "kind") 1008 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1009 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1010 kind = f"{sep}{kind}" if kind else "" 1011 constraints = f" {constraints}" if constraints else "" 1012 position = self.sql(expression, "position") 1013 position = f" {position}" if position else "" 1014 1015 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1016 kind = "" 1017 1018 return f"{exists}{column}{kind}{constraints}{position}" 1019 1020 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1021 this = self.sql(expression, "this") 1022 kind_sql = self.sql(expression, "kind").strip() 1023 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1024 1025 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1026 this = self.sql(expression, "this") 1027 if expression.args.get("not_null"): 1028 persisted = " PERSISTED NOT NULL" 1029 elif expression.args.get("persisted"): 1030 persisted = " PERSISTED" 1031 else: 1032 persisted = "" 1033 1034 return f"AS {this}{persisted}" 1035 1036 def autoincrementcolumnconstraint_sql(self, _) -> str: 1037 return self.token_sql(TokenType.AUTO_INCREMENT) 1038 1039 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1040 if isinstance(expression.this, list): 1041 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1042 else: 1043 this = self.sql(expression, "this") 1044 1045 return f"COMPRESS {this}" 1046 1047 def generatedasidentitycolumnconstraint_sql( 1048 self, expression: exp.GeneratedAsIdentityColumnConstraint 1049 ) -> str: 1050 this = "" 1051 if expression.this is not None: 1052 on_null = " ON NULL" if expression.args.get("on_null") else "" 1053 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1054 1055 start = expression.args.get("start") 1056 start = f"START WITH {start}" if start else "" 1057 increment = expression.args.get("increment") 1058 increment = f" INCREMENT BY {increment}" if increment else "" 1059 minvalue = expression.args.get("minvalue") 1060 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1061 maxvalue = expression.args.get("maxvalue") 1062 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1063 cycle = expression.args.get("cycle") 1064 cycle_sql = "" 1065 1066 if cycle is not None: 1067 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1068 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1069 1070 sequence_opts = "" 1071 if start or increment or cycle_sql: 1072 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1073 sequence_opts = f" ({sequence_opts.strip()})" 1074 1075 expr = self.sql(expression, "expression") 1076 expr = f"({expr})" if expr else "IDENTITY" 1077 1078 return f"GENERATED{this} AS {expr}{sequence_opts}" 1079 1080 def generatedasrowcolumnconstraint_sql( 1081 self, expression: exp.GeneratedAsRowColumnConstraint 1082 ) -> str: 1083 start = "START" if expression.args.get("start") else "END" 1084 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1085 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1086 1087 def periodforsystemtimeconstraint_sql( 1088 self, expression: exp.PeriodForSystemTimeConstraint 1089 ) -> str: 1090 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1091 1092 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1093 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1094 1095 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1096 desc = expression.args.get("desc") 1097 if desc is not None: 1098 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1099 options = self.expressions(expression, key="options", flat=True, sep=" ") 1100 options = f" {options}" if options else "" 1101 return f"PRIMARY KEY{options}" 1102 1103 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1104 this = self.sql(expression, "this") 1105 this = f" {this}" if this else "" 1106 index_type = expression.args.get("index_type") 1107 index_type = f" USING {index_type}" if index_type else "" 1108 on_conflict = self.sql(expression, "on_conflict") 1109 on_conflict = f" {on_conflict}" if on_conflict else "" 1110 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1111 options = self.expressions(expression, key="options", flat=True, sep=" ") 1112 options = f" {options}" if options else "" 1113 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1114 1115 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1116 return self.sql(expression, "this") 1117 1118 def create_sql(self, expression: exp.Create) -> str: 1119 kind = self.sql(expression, "kind") 1120 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1121 properties = expression.args.get("properties") 1122 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1123 1124 this = self.createable_sql(expression, properties_locs) 1125 1126 properties_sql = "" 1127 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1128 exp.Properties.Location.POST_WITH 1129 ): 1130 properties_sql = self.sql( 1131 exp.Properties( 1132 expressions=[ 1133 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1134 *properties_locs[exp.Properties.Location.POST_WITH], 1135 ] 1136 ) 1137 ) 1138 1139 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1140 properties_sql = self.sep() + properties_sql 1141 elif not self.pretty: 1142 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1143 properties_sql = f" {properties_sql}" 1144 1145 begin = " BEGIN" if expression.args.get("begin") else "" 1146 end = " END" if expression.args.get("end") else "" 1147 1148 expression_sql = self.sql(expression, "expression") 1149 if expression_sql: 1150 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1151 1152 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1153 postalias_props_sql = "" 1154 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1155 postalias_props_sql = self.properties( 1156 exp.Properties( 1157 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1158 ), 1159 wrapped=False, 1160 ) 1161 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1162 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1163 1164 postindex_props_sql = "" 1165 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1166 postindex_props_sql = self.properties( 1167 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1168 wrapped=False, 1169 prefix=" ", 1170 ) 1171 1172 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1173 indexes = f" {indexes}" if indexes else "" 1174 index_sql = indexes + postindex_props_sql 1175 1176 replace = " OR REPLACE" if expression.args.get("replace") else "" 1177 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1178 unique = " UNIQUE" if expression.args.get("unique") else "" 1179 1180 clustered = expression.args.get("clustered") 1181 if clustered is None: 1182 clustered_sql = "" 1183 elif clustered: 1184 clustered_sql = " CLUSTERED COLUMNSTORE" 1185 else: 1186 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1187 1188 postcreate_props_sql = "" 1189 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1190 postcreate_props_sql = self.properties( 1191 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1192 sep=" ", 1193 prefix=" ", 1194 wrapped=False, 1195 ) 1196 1197 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1198 1199 postexpression_props_sql = "" 1200 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1201 postexpression_props_sql = self.properties( 1202 exp.Properties( 1203 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1204 ), 1205 sep=" ", 1206 prefix=" ", 1207 wrapped=False, 1208 ) 1209 1210 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1211 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1212 no_schema_binding = ( 1213 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1214 ) 1215 1216 clone = self.sql(expression, "clone") 1217 clone = f" {clone}" if clone else "" 1218 1219 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1220 properties_expression = f"{expression_sql}{properties_sql}" 1221 else: 1222 properties_expression = f"{properties_sql}{expression_sql}" 1223 1224 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1225 return self.prepend_ctes(expression, expression_sql) 1226 1227 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1228 start = self.sql(expression, "start") 1229 start = f"START WITH {start}" if start else "" 1230 increment = self.sql(expression, "increment") 1231 increment = f" INCREMENT BY {increment}" if increment else "" 1232 minvalue = self.sql(expression, "minvalue") 1233 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1234 maxvalue = self.sql(expression, "maxvalue") 1235 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1236 owned = self.sql(expression, "owned") 1237 owned = f" OWNED BY {owned}" if owned else "" 1238 1239 cache = expression.args.get("cache") 1240 if cache is None: 1241 cache_str = "" 1242 elif cache is True: 1243 cache_str = " CACHE" 1244 else: 1245 cache_str = f" CACHE {cache}" 1246 1247 options = self.expressions(expression, key="options", flat=True, sep=" ") 1248 options = f" {options}" if options else "" 1249 1250 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1251 1252 def clone_sql(self, expression: exp.Clone) -> str: 1253 this = self.sql(expression, "this") 1254 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1255 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1256 return f"{shallow}{keyword} {this}" 1257 1258 def describe_sql(self, expression: exp.Describe) -> str: 1259 style = expression.args.get("style") 1260 style = f" {style}" if style else "" 1261 partition = self.sql(expression, "partition") 1262 partition = f" {partition}" if partition else "" 1263 format = self.sql(expression, "format") 1264 format = f" {format}" if format else "" 1265 1266 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1267 1268 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1269 tag = self.sql(expression, "tag") 1270 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1271 1272 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1273 with_ = self.sql(expression, "with") 1274 if with_: 1275 sql = f"{with_}{self.sep()}{sql}" 1276 return sql 1277 1278 def with_sql(self, expression: exp.With) -> str: 1279 sql = self.expressions(expression, flat=True) 1280 recursive = ( 1281 "RECURSIVE " 1282 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1283 else "" 1284 ) 1285 search = self.sql(expression, "search") 1286 search = f" {search}" if search else "" 1287 1288 return f"WITH {recursive}{sql}{search}" 1289 1290 def cte_sql(self, expression: exp.CTE) -> str: 1291 alias = expression.args.get("alias") 1292 if alias: 1293 alias.add_comments(expression.pop_comments()) 1294 1295 alias_sql = self.sql(expression, "alias") 1296 1297 materialized = expression.args.get("materialized") 1298 if materialized is False: 1299 materialized = "NOT MATERIALIZED " 1300 elif materialized: 1301 materialized = "MATERIALIZED " 1302 1303 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1304 1305 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1306 alias = self.sql(expression, "this") 1307 columns = self.expressions(expression, key="columns", flat=True) 1308 columns = f"({columns})" if columns else "" 1309 1310 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1311 columns = "" 1312 self.unsupported("Named columns are not supported in table alias.") 1313 1314 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1315 alias = self._next_name() 1316 1317 return f"{alias}{columns}" 1318 1319 def bitstring_sql(self, expression: exp.BitString) -> str: 1320 this = self.sql(expression, "this") 1321 if self.dialect.BIT_START: 1322 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1323 return f"{int(this, 2)}" 1324 1325 def hexstring_sql( 1326 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1327 ) -> str: 1328 this = self.sql(expression, "this") 1329 is_integer_type = expression.args.get("is_integer") 1330 1331 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1332 not self.dialect.HEX_START and not binary_function_repr 1333 ): 1334 # Integer representation will be returned if: 1335 # - The read dialect treats the hex value as integer literal but not the write 1336 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1337 return f"{int(this, 16)}" 1338 1339 if not is_integer_type: 1340 # Read dialect treats the hex value as BINARY/BLOB 1341 if binary_function_repr: 1342 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1343 return self.func(binary_function_repr, exp.Literal.string(this)) 1344 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1345 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1346 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1347 1348 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1349 1350 def bytestring_sql(self, expression: exp.ByteString) -> str: 1351 this = self.sql(expression, "this") 1352 if self.dialect.BYTE_START: 1353 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1354 return this 1355 1356 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1357 this = self.sql(expression, "this") 1358 escape = expression.args.get("escape") 1359 1360 if self.dialect.UNICODE_START: 1361 escape_substitute = r"\\\1" 1362 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1363 else: 1364 escape_substitute = r"\\u\1" 1365 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1366 1367 if escape: 1368 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1369 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1370 else: 1371 escape_pattern = ESCAPED_UNICODE_RE 1372 escape_sql = "" 1373 1374 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1375 this = escape_pattern.sub(escape_substitute, this) 1376 1377 return f"{left_quote}{this}{right_quote}{escape_sql}" 1378 1379 def rawstring_sql(self, expression: exp.RawString) -> str: 1380 string = expression.this 1381 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1382 string = string.replace("\\", "\\\\") 1383 1384 string = self.escape_str(string, escape_backslash=False) 1385 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1386 1387 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1388 this = self.sql(expression, "this") 1389 specifier = self.sql(expression, "expression") 1390 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1391 return f"{this}{specifier}" 1392 1393 def datatype_sql(self, expression: exp.DataType) -> str: 1394 nested = "" 1395 values = "" 1396 interior = self.expressions(expression, flat=True) 1397 1398 type_value = expression.this 1399 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1400 type_sql = self.sql(expression, "kind") 1401 else: 1402 type_sql = ( 1403 self.TYPE_MAPPING.get(type_value, type_value.value) 1404 if isinstance(type_value, exp.DataType.Type) 1405 else type_value 1406 ) 1407 1408 if interior: 1409 if expression.args.get("nested"): 1410 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1411 if expression.args.get("values") is not None: 1412 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1413 values = self.expressions(expression, key="values", flat=True) 1414 values = f"{delimiters[0]}{values}{delimiters[1]}" 1415 elif type_value == exp.DataType.Type.INTERVAL: 1416 nested = f" {interior}" 1417 else: 1418 nested = f"({interior})" 1419 1420 type_sql = f"{type_sql}{nested}{values}" 1421 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1422 exp.DataType.Type.TIMETZ, 1423 exp.DataType.Type.TIMESTAMPTZ, 1424 ): 1425 type_sql = f"{type_sql} WITH TIME ZONE" 1426 1427 return type_sql 1428 1429 def directory_sql(self, expression: exp.Directory) -> str: 1430 local = "LOCAL " if expression.args.get("local") else "" 1431 row_format = self.sql(expression, "row_format") 1432 row_format = f" {row_format}" if row_format else "" 1433 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1434 1435 def delete_sql(self, expression: exp.Delete) -> str: 1436 this = self.sql(expression, "this") 1437 this = f" FROM {this}" if this else "" 1438 using = self.sql(expression, "using") 1439 using = f" USING {using}" if using else "" 1440 cluster = self.sql(expression, "cluster") 1441 cluster = f" {cluster}" if cluster else "" 1442 where = self.sql(expression, "where") 1443 returning = self.sql(expression, "returning") 1444 limit = self.sql(expression, "limit") 1445 tables = self.expressions(expression, key="tables") 1446 tables = f" {tables}" if tables else "" 1447 if self.RETURNING_END: 1448 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1449 else: 1450 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1451 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1452 1453 def drop_sql(self, expression: exp.Drop) -> str: 1454 this = self.sql(expression, "this") 1455 expressions = self.expressions(expression, flat=True) 1456 expressions = f" ({expressions})" if expressions else "" 1457 kind = expression.args["kind"] 1458 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1459 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1460 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1461 on_cluster = self.sql(expression, "cluster") 1462 on_cluster = f" {on_cluster}" if on_cluster else "" 1463 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1464 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1465 cascade = " CASCADE" if expression.args.get("cascade") else "" 1466 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1467 purge = " PURGE" if expression.args.get("purge") else "" 1468 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1469 1470 def set_operation(self, expression: exp.SetOperation) -> str: 1471 op_type = type(expression) 1472 op_name = op_type.key.upper() 1473 1474 distinct = expression.args.get("distinct") 1475 if ( 1476 distinct is False 1477 and op_type in (exp.Except, exp.Intersect) 1478 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1479 ): 1480 self.unsupported(f"{op_name} ALL is not supported") 1481 1482 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1483 1484 if distinct is None: 1485 distinct = default_distinct 1486 if distinct is None: 1487 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1488 1489 if distinct is default_distinct: 1490 distinct_or_all = "" 1491 else: 1492 distinct_or_all = " DISTINCT" if distinct else " ALL" 1493 1494 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1495 side_kind = f"{side_kind} " if side_kind else "" 1496 1497 by_name = " BY NAME" if expression.args.get("by_name") else "" 1498 on = self.expressions(expression, key="on", flat=True) 1499 on = f" ON ({on})" if on else "" 1500 1501 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1502 1503 def set_operations(self, expression: exp.SetOperation) -> str: 1504 if not self.SET_OP_MODIFIERS: 1505 limit = expression.args.get("limit") 1506 order = expression.args.get("order") 1507 1508 if limit or order: 1509 select = self._move_ctes_to_top_level( 1510 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1511 ) 1512 1513 if limit: 1514 select = select.limit(limit.pop(), copy=False) 1515 if order: 1516 select = select.order_by(order.pop(), copy=False) 1517 return self.sql(select) 1518 1519 sqls: t.List[str] = [] 1520 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1521 1522 while stack: 1523 node = stack.pop() 1524 1525 if isinstance(node, exp.SetOperation): 1526 stack.append(node.expression) 1527 stack.append( 1528 self.maybe_comment( 1529 self.set_operation(node), comments=node.comments, separated=True 1530 ) 1531 ) 1532 stack.append(node.this) 1533 else: 1534 sqls.append(self.sql(node)) 1535 1536 this = self.sep().join(sqls) 1537 this = self.query_modifiers(expression, this) 1538 return self.prepend_ctes(expression, this) 1539 1540 def fetch_sql(self, expression: exp.Fetch) -> str: 1541 direction = expression.args.get("direction") 1542 direction = f" {direction}" if direction else "" 1543 count = self.sql(expression, "count") 1544 count = f" {count}" if count else "" 1545 limit_options = self.sql(expression, "limit_options") 1546 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1547 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1548 1549 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1550 percent = " PERCENT" if expression.args.get("percent") else "" 1551 rows = " ROWS" if expression.args.get("rows") else "" 1552 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1553 if not with_ties and rows: 1554 with_ties = " ONLY" 1555 return f"{percent}{rows}{with_ties}" 1556 1557 def filter_sql(self, expression: exp.Filter) -> str: 1558 if self.AGGREGATE_FILTER_SUPPORTED: 1559 this = self.sql(expression, "this") 1560 where = self.sql(expression, "expression").strip() 1561 return f"{this} FILTER({where})" 1562 1563 agg = expression.this 1564 agg_arg = agg.this 1565 cond = expression.expression.this 1566 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1567 return self.sql(agg) 1568 1569 def hint_sql(self, expression: exp.Hint) -> str: 1570 if not self.QUERY_HINTS: 1571 self.unsupported("Hints are not supported") 1572 return "" 1573 1574 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1575 1576 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1577 using = self.sql(expression, "using") 1578 using = f" USING {using}" if using else "" 1579 columns = self.expressions(expression, key="columns", flat=True) 1580 columns = f"({columns})" if columns else "" 1581 partition_by = self.expressions(expression, key="partition_by", flat=True) 1582 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1583 where = self.sql(expression, "where") 1584 include = self.expressions(expression, key="include", flat=True) 1585 if include: 1586 include = f" INCLUDE ({include})" 1587 with_storage = self.expressions(expression, key="with_storage", flat=True) 1588 with_storage = f" WITH ({with_storage})" if with_storage else "" 1589 tablespace = self.sql(expression, "tablespace") 1590 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1591 on = self.sql(expression, "on") 1592 on = f" ON {on}" if on else "" 1593 1594 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1595 1596 def index_sql(self, expression: exp.Index) -> str: 1597 unique = "UNIQUE " if expression.args.get("unique") else "" 1598 primary = "PRIMARY " if expression.args.get("primary") else "" 1599 amp = "AMP " if expression.args.get("amp") else "" 1600 name = self.sql(expression, "this") 1601 name = f"{name} " if name else "" 1602 table = self.sql(expression, "table") 1603 table = f"{self.INDEX_ON} {table}" if table else "" 1604 1605 index = "INDEX " if not table else "" 1606 1607 params = self.sql(expression, "params") 1608 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1609 1610 def identifier_sql(self, expression: exp.Identifier) -> str: 1611 text = expression.name 1612 lower = text.lower() 1613 text = lower if self.normalize and not expression.quoted else text 1614 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1615 if ( 1616 expression.quoted 1617 or self.dialect.can_identify(text, self.identify) 1618 or lower in self.RESERVED_KEYWORDS 1619 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1620 ): 1621 text = f"{self._identifier_start}{text}{self._identifier_end}" 1622 return text 1623 1624 def hex_sql(self, expression: exp.Hex) -> str: 1625 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1626 if self.dialect.HEX_LOWERCASE: 1627 text = self.func("LOWER", text) 1628 1629 return text 1630 1631 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1632 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1633 if not self.dialect.HEX_LOWERCASE: 1634 text = self.func("LOWER", text) 1635 return text 1636 1637 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1638 input_format = self.sql(expression, "input_format") 1639 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1640 output_format = self.sql(expression, "output_format") 1641 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1642 return self.sep().join((input_format, output_format)) 1643 1644 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1645 string = self.sql(exp.Literal.string(expression.name)) 1646 return f"{prefix}{string}" 1647 1648 def partition_sql(self, expression: exp.Partition) -> str: 1649 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1650 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1651 1652 def properties_sql(self, expression: exp.Properties) -> str: 1653 root_properties = [] 1654 with_properties = [] 1655 1656 for p in expression.expressions: 1657 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1658 if p_loc == exp.Properties.Location.POST_WITH: 1659 with_properties.append(p) 1660 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1661 root_properties.append(p) 1662 1663 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1664 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1665 1666 if root_props and with_props and not self.pretty: 1667 with_props = " " + with_props 1668 1669 return root_props + with_props 1670 1671 def root_properties(self, properties: exp.Properties) -> str: 1672 if properties.expressions: 1673 return self.expressions(properties, indent=False, sep=" ") 1674 return "" 1675 1676 def properties( 1677 self, 1678 properties: exp.Properties, 1679 prefix: str = "", 1680 sep: str = ", ", 1681 suffix: str = "", 1682 wrapped: bool = True, 1683 ) -> str: 1684 if properties.expressions: 1685 expressions = self.expressions(properties, sep=sep, indent=False) 1686 if expressions: 1687 expressions = self.wrap(expressions) if wrapped else expressions 1688 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1689 return "" 1690 1691 def with_properties(self, properties: exp.Properties) -> str: 1692 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1693 1694 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1695 properties_locs = defaultdict(list) 1696 for p in properties.expressions: 1697 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1698 if p_loc != exp.Properties.Location.UNSUPPORTED: 1699 properties_locs[p_loc].append(p) 1700 else: 1701 self.unsupported(f"Unsupported property {p.key}") 1702 1703 return properties_locs 1704 1705 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1706 if isinstance(expression.this, exp.Dot): 1707 return self.sql(expression, "this") 1708 return f"'{expression.name}'" if string_key else expression.name 1709 1710 def property_sql(self, expression: exp.Property) -> str: 1711 property_cls = expression.__class__ 1712 if property_cls == exp.Property: 1713 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1714 1715 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1716 if not property_name: 1717 self.unsupported(f"Unsupported property {expression.key}") 1718 1719 return f"{property_name}={self.sql(expression, 'this')}" 1720 1721 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1722 if self.SUPPORTS_CREATE_TABLE_LIKE: 1723 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1724 options = f" {options}" if options else "" 1725 1726 like = f"LIKE {self.sql(expression, 'this')}{options}" 1727 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1728 like = f"({like})" 1729 1730 return like 1731 1732 if expression.expressions: 1733 self.unsupported("Transpilation of LIKE property options is unsupported") 1734 1735 select = exp.select("*").from_(expression.this).limit(0) 1736 return f"AS {self.sql(select)}" 1737 1738 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1739 no = "NO " if expression.args.get("no") else "" 1740 protection = " PROTECTION" if expression.args.get("protection") else "" 1741 return f"{no}FALLBACK{protection}" 1742 1743 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1744 no = "NO " if expression.args.get("no") else "" 1745 local = expression.args.get("local") 1746 local = f"{local} " if local else "" 1747 dual = "DUAL " if expression.args.get("dual") else "" 1748 before = "BEFORE " if expression.args.get("before") else "" 1749 after = "AFTER " if expression.args.get("after") else "" 1750 return f"{no}{local}{dual}{before}{after}JOURNAL" 1751 1752 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1753 freespace = self.sql(expression, "this") 1754 percent = " PERCENT" if expression.args.get("percent") else "" 1755 return f"FREESPACE={freespace}{percent}" 1756 1757 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1758 if expression.args.get("default"): 1759 property = "DEFAULT" 1760 elif expression.args.get("on"): 1761 property = "ON" 1762 else: 1763 property = "OFF" 1764 return f"CHECKSUM={property}" 1765 1766 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1767 if expression.args.get("no"): 1768 return "NO MERGEBLOCKRATIO" 1769 if expression.args.get("default"): 1770 return "DEFAULT MERGEBLOCKRATIO" 1771 1772 percent = " PERCENT" if expression.args.get("percent") else "" 1773 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1774 1775 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1776 default = expression.args.get("default") 1777 minimum = expression.args.get("minimum") 1778 maximum = expression.args.get("maximum") 1779 if default or minimum or maximum: 1780 if default: 1781 prop = "DEFAULT" 1782 elif minimum: 1783 prop = "MINIMUM" 1784 else: 1785 prop = "MAXIMUM" 1786 return f"{prop} DATABLOCKSIZE" 1787 units = expression.args.get("units") 1788 units = f" {units}" if units else "" 1789 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1790 1791 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1792 autotemp = expression.args.get("autotemp") 1793 always = expression.args.get("always") 1794 default = expression.args.get("default") 1795 manual = expression.args.get("manual") 1796 never = expression.args.get("never") 1797 1798 if autotemp is not None: 1799 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1800 elif always: 1801 prop = "ALWAYS" 1802 elif default: 1803 prop = "DEFAULT" 1804 elif manual: 1805 prop = "MANUAL" 1806 elif never: 1807 prop = "NEVER" 1808 return f"BLOCKCOMPRESSION={prop}" 1809 1810 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1811 no = expression.args.get("no") 1812 no = " NO" if no else "" 1813 concurrent = expression.args.get("concurrent") 1814 concurrent = " CONCURRENT" if concurrent else "" 1815 target = self.sql(expression, "target") 1816 target = f" {target}" if target else "" 1817 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1818 1819 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1820 if isinstance(expression.this, list): 1821 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1822 if expression.this: 1823 modulus = self.sql(expression, "this") 1824 remainder = self.sql(expression, "expression") 1825 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1826 1827 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1828 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1829 return f"FROM ({from_expressions}) TO ({to_expressions})" 1830 1831 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1832 this = self.sql(expression, "this") 1833 1834 for_values_or_default = expression.expression 1835 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1836 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1837 else: 1838 for_values_or_default = " DEFAULT" 1839 1840 return f"PARTITION OF {this}{for_values_or_default}" 1841 1842 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1843 kind = expression.args.get("kind") 1844 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1845 for_or_in = expression.args.get("for_or_in") 1846 for_or_in = f" {for_or_in}" if for_or_in else "" 1847 lock_type = expression.args.get("lock_type") 1848 override = " OVERRIDE" if expression.args.get("override") else "" 1849 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1850 1851 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1852 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1853 statistics = expression.args.get("statistics") 1854 statistics_sql = "" 1855 if statistics is not None: 1856 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1857 return f"{data_sql}{statistics_sql}" 1858 1859 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1860 this = self.sql(expression, "this") 1861 this = f"HISTORY_TABLE={this}" if this else "" 1862 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1863 data_consistency = ( 1864 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1865 ) 1866 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1867 retention_period = ( 1868 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1869 ) 1870 1871 if this: 1872 on_sql = self.func("ON", this, data_consistency, retention_period) 1873 else: 1874 on_sql = "ON" if expression.args.get("on") else "OFF" 1875 1876 sql = f"SYSTEM_VERSIONING={on_sql}" 1877 1878 return f"WITH({sql})" if expression.args.get("with") else sql 1879 1880 def insert_sql(self, expression: exp.Insert) -> str: 1881 hint = self.sql(expression, "hint") 1882 overwrite = expression.args.get("overwrite") 1883 1884 if isinstance(expression.this, exp.Directory): 1885 this = " OVERWRITE" if overwrite else " INTO" 1886 else: 1887 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1888 1889 stored = self.sql(expression, "stored") 1890 stored = f" {stored}" if stored else "" 1891 alternative = expression.args.get("alternative") 1892 alternative = f" OR {alternative}" if alternative else "" 1893 ignore = " IGNORE" if expression.args.get("ignore") else "" 1894 is_function = expression.args.get("is_function") 1895 if is_function: 1896 this = f"{this} FUNCTION" 1897 this = f"{this} {self.sql(expression, 'this')}" 1898 1899 exists = " IF EXISTS" if expression.args.get("exists") else "" 1900 where = self.sql(expression, "where") 1901 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1902 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1903 on_conflict = self.sql(expression, "conflict") 1904 on_conflict = f" {on_conflict}" if on_conflict else "" 1905 by_name = " BY NAME" if expression.args.get("by_name") else "" 1906 returning = self.sql(expression, "returning") 1907 1908 if self.RETURNING_END: 1909 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1910 else: 1911 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1912 1913 partition_by = self.sql(expression, "partition") 1914 partition_by = f" {partition_by}" if partition_by else "" 1915 settings = self.sql(expression, "settings") 1916 settings = f" {settings}" if settings else "" 1917 1918 source = self.sql(expression, "source") 1919 source = f"TABLE {source}" if source else "" 1920 1921 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1922 return self.prepend_ctes(expression, sql) 1923 1924 def introducer_sql(self, expression: exp.Introducer) -> str: 1925 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1926 1927 def kill_sql(self, expression: exp.Kill) -> str: 1928 kind = self.sql(expression, "kind") 1929 kind = f" {kind}" if kind else "" 1930 this = self.sql(expression, "this") 1931 this = f" {this}" if this else "" 1932 return f"KILL{kind}{this}" 1933 1934 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1935 return expression.name 1936 1937 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1938 return expression.name 1939 1940 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1941 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1942 1943 constraint = self.sql(expression, "constraint") 1944 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1945 1946 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1947 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1948 action = self.sql(expression, "action") 1949 1950 expressions = self.expressions(expression, flat=True) 1951 if expressions: 1952 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1953 expressions = f" {set_keyword}{expressions}" 1954 1955 where = self.sql(expression, "where") 1956 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1957 1958 def returning_sql(self, expression: exp.Returning) -> str: 1959 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1960 1961 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1962 fields = self.sql(expression, "fields") 1963 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1964 escaped = self.sql(expression, "escaped") 1965 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1966 items = self.sql(expression, "collection_items") 1967 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1968 keys = self.sql(expression, "map_keys") 1969 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1970 lines = self.sql(expression, "lines") 1971 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1972 null = self.sql(expression, "null") 1973 null = f" NULL DEFINED AS {null}" if null else "" 1974 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1975 1976 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1977 return f"WITH ({self.expressions(expression, flat=True)})" 1978 1979 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1980 this = f"{self.sql(expression, 'this')} INDEX" 1981 target = self.sql(expression, "target") 1982 target = f" FOR {target}" if target else "" 1983 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1984 1985 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1986 this = self.sql(expression, "this") 1987 kind = self.sql(expression, "kind") 1988 expr = self.sql(expression, "expression") 1989 return f"{this} ({kind} => {expr})" 1990 1991 def table_parts(self, expression: exp.Table) -> str: 1992 return ".".join( 1993 self.sql(part) 1994 for part in ( 1995 expression.args.get("catalog"), 1996 expression.args.get("db"), 1997 expression.args.get("this"), 1998 ) 1999 if part is not None 2000 ) 2001 2002 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2003 table = self.table_parts(expression) 2004 only = "ONLY " if expression.args.get("only") else "" 2005 partition = self.sql(expression, "partition") 2006 partition = f" {partition}" if partition else "" 2007 version = self.sql(expression, "version") 2008 version = f" {version}" if version else "" 2009 alias = self.sql(expression, "alias") 2010 alias = f"{sep}{alias}" if alias else "" 2011 2012 sample = self.sql(expression, "sample") 2013 if self.dialect.ALIAS_POST_TABLESAMPLE: 2014 sample_pre_alias = sample 2015 sample_post_alias = "" 2016 else: 2017 sample_pre_alias = "" 2018 sample_post_alias = sample 2019 2020 hints = self.expressions(expression, key="hints", sep=" ") 2021 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2022 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2023 joins = self.indent( 2024 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2025 ) 2026 laterals = self.expressions(expression, key="laterals", sep="") 2027 2028 file_format = self.sql(expression, "format") 2029 if file_format: 2030 pattern = self.sql(expression, "pattern") 2031 pattern = f", PATTERN => {pattern}" if pattern else "" 2032 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2033 2034 ordinality = expression.args.get("ordinality") or "" 2035 if ordinality: 2036 ordinality = f" WITH ORDINALITY{alias}" 2037 alias = "" 2038 2039 when = self.sql(expression, "when") 2040 if when: 2041 table = f"{table} {when}" 2042 2043 changes = self.sql(expression, "changes") 2044 changes = f" {changes}" if changes else "" 2045 2046 rows_from = self.expressions(expression, key="rows_from") 2047 if rows_from: 2048 table = f"ROWS FROM {self.wrap(rows_from)}" 2049 2050 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2051 2052 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2053 table = self.func("TABLE", expression.this) 2054 alias = self.sql(expression, "alias") 2055 alias = f" AS {alias}" if alias else "" 2056 sample = self.sql(expression, "sample") 2057 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2058 joins = self.indent( 2059 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2060 ) 2061 return f"{table}{alias}{pivots}{sample}{joins}" 2062 2063 def tablesample_sql( 2064 self, 2065 expression: exp.TableSample, 2066 tablesample_keyword: t.Optional[str] = None, 2067 ) -> str: 2068 method = self.sql(expression, "method") 2069 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2070 numerator = self.sql(expression, "bucket_numerator") 2071 denominator = self.sql(expression, "bucket_denominator") 2072 field = self.sql(expression, "bucket_field") 2073 field = f" ON {field}" if field else "" 2074 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2075 seed = self.sql(expression, "seed") 2076 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2077 2078 size = self.sql(expression, "size") 2079 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2080 size = f"{size} ROWS" 2081 2082 percent = self.sql(expression, "percent") 2083 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2084 percent = f"{percent} PERCENT" 2085 2086 expr = f"{bucket}{percent}{size}" 2087 if self.TABLESAMPLE_REQUIRES_PARENS: 2088 expr = f"({expr})" 2089 2090 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2091 2092 def pivot_sql(self, expression: exp.Pivot) -> str: 2093 expressions = self.expressions(expression, flat=True) 2094 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2095 2096 group = self.sql(expression, "group") 2097 2098 if expression.this: 2099 this = self.sql(expression, "this") 2100 if not expressions: 2101 return f"UNPIVOT {this}" 2102 2103 on = f"{self.seg('ON')} {expressions}" 2104 into = self.sql(expression, "into") 2105 into = f"{self.seg('INTO')} {into}" if into else "" 2106 using = self.expressions(expression, key="using", flat=True) 2107 using = f"{self.seg('USING')} {using}" if using else "" 2108 return f"{direction} {this}{on}{into}{using}{group}" 2109 2110 alias = self.sql(expression, "alias") 2111 alias = f" AS {alias}" if alias else "" 2112 2113 fields = self.expressions( 2114 expression, 2115 "fields", 2116 sep=" ", 2117 dynamic=True, 2118 new_line=True, 2119 skip_first=True, 2120 skip_last=True, 2121 ) 2122 2123 include_nulls = expression.args.get("include_nulls") 2124 if include_nulls is not None: 2125 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2126 else: 2127 nulls = "" 2128 2129 default_on_null = self.sql(expression, "default_on_null") 2130 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2131 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2132 2133 def version_sql(self, expression: exp.Version) -> str: 2134 this = f"FOR {expression.name}" 2135 kind = expression.text("kind") 2136 expr = self.sql(expression, "expression") 2137 return f"{this} {kind} {expr}" 2138 2139 def tuple_sql(self, expression: exp.Tuple) -> str: 2140 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2141 2142 def update_sql(self, expression: exp.Update) -> str: 2143 this = self.sql(expression, "this") 2144 set_sql = self.expressions(expression, flat=True) 2145 from_sql = self.sql(expression, "from") 2146 where_sql = self.sql(expression, "where") 2147 returning = self.sql(expression, "returning") 2148 order = self.sql(expression, "order") 2149 limit = self.sql(expression, "limit") 2150 if self.RETURNING_END: 2151 expression_sql = f"{from_sql}{where_sql}{returning}" 2152 else: 2153 expression_sql = f"{returning}{from_sql}{where_sql}" 2154 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2155 return self.prepend_ctes(expression, sql) 2156 2157 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2158 values_as_table = values_as_table and self.VALUES_AS_TABLE 2159 2160 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2161 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2162 args = self.expressions(expression) 2163 alias = self.sql(expression, "alias") 2164 values = f"VALUES{self.seg('')}{args}" 2165 values = ( 2166 f"({values})" 2167 if self.WRAP_DERIVED_VALUES 2168 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2169 else values 2170 ) 2171 return f"{values} AS {alias}" if alias else values 2172 2173 # Converts `VALUES...` expression into a series of select unions. 2174 alias_node = expression.args.get("alias") 2175 column_names = alias_node and alias_node.columns 2176 2177 selects: t.List[exp.Query] = [] 2178 2179 for i, tup in enumerate(expression.expressions): 2180 row = tup.expressions 2181 2182 if i == 0 and column_names: 2183 row = [ 2184 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2185 ] 2186 2187 selects.append(exp.Select(expressions=row)) 2188 2189 if self.pretty: 2190 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2191 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2192 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2193 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2194 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2195 2196 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2197 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2198 return f"({unions}){alias}" 2199 2200 def var_sql(self, expression: exp.Var) -> str: 2201 return self.sql(expression, "this") 2202 2203 @unsupported_args("expressions") 2204 def into_sql(self, expression: exp.Into) -> str: 2205 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2206 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2207 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2208 2209 def from_sql(self, expression: exp.From) -> str: 2210 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2211 2212 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2213 grouping_sets = self.expressions(expression, indent=False) 2214 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2215 2216 def rollup_sql(self, expression: exp.Rollup) -> str: 2217 expressions = self.expressions(expression, indent=False) 2218 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2219 2220 def cube_sql(self, expression: exp.Cube) -> str: 2221 expressions = self.expressions(expression, indent=False) 2222 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2223 2224 def group_sql(self, expression: exp.Group) -> str: 2225 group_by_all = expression.args.get("all") 2226 if group_by_all is True: 2227 modifier = " ALL" 2228 elif group_by_all is False: 2229 modifier = " DISTINCT" 2230 else: 2231 modifier = "" 2232 2233 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2234 2235 grouping_sets = self.expressions(expression, key="grouping_sets") 2236 cube = self.expressions(expression, key="cube") 2237 rollup = self.expressions(expression, key="rollup") 2238 2239 groupings = csv( 2240 self.seg(grouping_sets) if grouping_sets else "", 2241 self.seg(cube) if cube else "", 2242 self.seg(rollup) if rollup else "", 2243 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2244 sep=self.GROUPINGS_SEP, 2245 ) 2246 2247 if ( 2248 expression.expressions 2249 and groupings 2250 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2251 ): 2252 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2253 2254 return f"{group_by}{groupings}" 2255 2256 def having_sql(self, expression: exp.Having) -> str: 2257 this = self.indent(self.sql(expression, "this")) 2258 return f"{self.seg('HAVING')}{self.sep()}{this}" 2259 2260 def connect_sql(self, expression: exp.Connect) -> str: 2261 start = self.sql(expression, "start") 2262 start = self.seg(f"START WITH {start}") if start else "" 2263 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2264 connect = self.sql(expression, "connect") 2265 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2266 return start + connect 2267 2268 def prior_sql(self, expression: exp.Prior) -> str: 2269 return f"PRIOR {self.sql(expression, 'this')}" 2270 2271 def join_sql(self, expression: exp.Join) -> str: 2272 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2273 side = None 2274 else: 2275 side = expression.side 2276 2277 op_sql = " ".join( 2278 op 2279 for op in ( 2280 expression.method, 2281 "GLOBAL" if expression.args.get("global") else None, 2282 side, 2283 expression.kind, 2284 expression.hint if self.JOIN_HINTS else None, 2285 ) 2286 if op 2287 ) 2288 match_cond = self.sql(expression, "match_condition") 2289 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2290 on_sql = self.sql(expression, "on") 2291 using = expression.args.get("using") 2292 2293 if not on_sql and using: 2294 on_sql = csv(*(self.sql(column) for column in using)) 2295 2296 this = expression.this 2297 this_sql = self.sql(this) 2298 2299 exprs = self.expressions(expression) 2300 if exprs: 2301 this_sql = f"{this_sql},{self.seg(exprs)}" 2302 2303 if on_sql: 2304 on_sql = self.indent(on_sql, skip_first=True) 2305 space = self.seg(" " * self.pad) if self.pretty else " " 2306 if using: 2307 on_sql = f"{space}USING ({on_sql})" 2308 else: 2309 on_sql = f"{space}ON {on_sql}" 2310 elif not op_sql: 2311 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2312 return f" {this_sql}" 2313 2314 return f", {this_sql}" 2315 2316 if op_sql != "STRAIGHT_JOIN": 2317 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2318 2319 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2320 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2321 2322 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2323 args = self.expressions(expression, flat=True) 2324 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2325 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2326 2327 def lateral_op(self, expression: exp.Lateral) -> str: 2328 cross_apply = expression.args.get("cross_apply") 2329 2330 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2331 if cross_apply is True: 2332 op = "INNER JOIN " 2333 elif cross_apply is False: 2334 op = "LEFT JOIN " 2335 else: 2336 op = "" 2337 2338 return f"{op}LATERAL" 2339 2340 def lateral_sql(self, expression: exp.Lateral) -> str: 2341 this = self.sql(expression, "this") 2342 2343 if expression.args.get("view"): 2344 alias = expression.args["alias"] 2345 columns = self.expressions(alias, key="columns", flat=True) 2346 table = f" {alias.name}" if alias.name else "" 2347 columns = f" AS {columns}" if columns else "" 2348 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2349 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2350 2351 alias = self.sql(expression, "alias") 2352 alias = f" AS {alias}" if alias else "" 2353 2354 ordinality = expression.args.get("ordinality") or "" 2355 if ordinality: 2356 ordinality = f" WITH ORDINALITY{alias}" 2357 alias = "" 2358 2359 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2360 2361 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2362 this = self.sql(expression, "this") 2363 2364 args = [ 2365 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2366 for e in (expression.args.get(k) for k in ("offset", "expression")) 2367 if e 2368 ] 2369 2370 args_sql = ", ".join(self.sql(e) for e in args) 2371 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2372 expressions = self.expressions(expression, flat=True) 2373 limit_options = self.sql(expression, "limit_options") 2374 expressions = f" BY {expressions}" if expressions else "" 2375 2376 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2377 2378 def offset_sql(self, expression: exp.Offset) -> str: 2379 this = self.sql(expression, "this") 2380 value = expression.expression 2381 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2382 expressions = self.expressions(expression, flat=True) 2383 expressions = f" BY {expressions}" if expressions else "" 2384 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2385 2386 def setitem_sql(self, expression: exp.SetItem) -> str: 2387 kind = self.sql(expression, "kind") 2388 kind = f"{kind} " if kind else "" 2389 this = self.sql(expression, "this") 2390 expressions = self.expressions(expression) 2391 collate = self.sql(expression, "collate") 2392 collate = f" COLLATE {collate}" if collate else "" 2393 global_ = "GLOBAL " if expression.args.get("global") else "" 2394 return f"{global_}{kind}{this}{expressions}{collate}" 2395 2396 def set_sql(self, expression: exp.Set) -> str: 2397 expressions = f" {self.expressions(expression, flat=True)}" 2398 tag = " TAG" if expression.args.get("tag") else "" 2399 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2400 2401 def pragma_sql(self, expression: exp.Pragma) -> str: 2402 return f"PRAGMA {self.sql(expression, 'this')}" 2403 2404 def lock_sql(self, expression: exp.Lock) -> str: 2405 if not self.LOCKING_READS_SUPPORTED: 2406 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2407 return "" 2408 2409 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2410 expressions = self.expressions(expression, flat=True) 2411 expressions = f" OF {expressions}" if expressions else "" 2412 wait = expression.args.get("wait") 2413 2414 if wait is not None: 2415 if isinstance(wait, exp.Literal): 2416 wait = f" WAIT {self.sql(wait)}" 2417 else: 2418 wait = " NOWAIT" if wait else " SKIP LOCKED" 2419 2420 return f"{lock_type}{expressions}{wait or ''}" 2421 2422 def literal_sql(self, expression: exp.Literal) -> str: 2423 text = expression.this or "" 2424 if expression.is_string: 2425 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2426 return text 2427 2428 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2429 if self.dialect.ESCAPED_SEQUENCES: 2430 to_escaped = self.dialect.ESCAPED_SEQUENCES 2431 text = "".join( 2432 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2433 ) 2434 2435 return self._replace_line_breaks(text).replace( 2436 self.dialect.QUOTE_END, self._escaped_quote_end 2437 ) 2438 2439 def loaddata_sql(self, expression: exp.LoadData) -> str: 2440 local = " LOCAL" if expression.args.get("local") else "" 2441 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2442 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2443 this = f" INTO TABLE {self.sql(expression, 'this')}" 2444 partition = self.sql(expression, "partition") 2445 partition = f" {partition}" if partition else "" 2446 input_format = self.sql(expression, "input_format") 2447 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2448 serde = self.sql(expression, "serde") 2449 serde = f" SERDE {serde}" if serde else "" 2450 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2451 2452 def null_sql(self, *_) -> str: 2453 return "NULL" 2454 2455 def boolean_sql(self, expression: exp.Boolean) -> str: 2456 return "TRUE" if expression.this else "FALSE" 2457 2458 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2459 this = self.sql(expression, "this") 2460 this = f"{this} " if this else this 2461 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2462 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2463 2464 def withfill_sql(self, expression: exp.WithFill) -> str: 2465 from_sql = self.sql(expression, "from") 2466 from_sql = f" FROM {from_sql}" if from_sql else "" 2467 to_sql = self.sql(expression, "to") 2468 to_sql = f" TO {to_sql}" if to_sql else "" 2469 step_sql = self.sql(expression, "step") 2470 step_sql = f" STEP {step_sql}" if step_sql else "" 2471 interpolated_values = [ 2472 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2473 if isinstance(e, exp.Alias) 2474 else self.sql(e, "this") 2475 for e in expression.args.get("interpolate") or [] 2476 ] 2477 interpolate = ( 2478 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2479 ) 2480 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2481 2482 def cluster_sql(self, expression: exp.Cluster) -> str: 2483 return self.op_expressions("CLUSTER BY", expression) 2484 2485 def distribute_sql(self, expression: exp.Distribute) -> str: 2486 return self.op_expressions("DISTRIBUTE BY", expression) 2487 2488 def sort_sql(self, expression: exp.Sort) -> str: 2489 return self.op_expressions("SORT BY", expression) 2490 2491 def ordered_sql(self, expression: exp.Ordered) -> str: 2492 desc = expression.args.get("desc") 2493 asc = not desc 2494 2495 nulls_first = expression.args.get("nulls_first") 2496 nulls_last = not nulls_first 2497 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2498 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2499 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2500 2501 this = self.sql(expression, "this") 2502 2503 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2504 nulls_sort_change = "" 2505 if nulls_first and ( 2506 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2507 ): 2508 nulls_sort_change = " NULLS FIRST" 2509 elif ( 2510 nulls_last 2511 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2512 and not nulls_are_last 2513 ): 2514 nulls_sort_change = " NULLS LAST" 2515 2516 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2517 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2518 window = expression.find_ancestor(exp.Window, exp.Select) 2519 if isinstance(window, exp.Window) and window.args.get("spec"): 2520 self.unsupported( 2521 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2522 ) 2523 nulls_sort_change = "" 2524 elif self.NULL_ORDERING_SUPPORTED is False and ( 2525 (asc and nulls_sort_change == " NULLS LAST") 2526 or (desc and nulls_sort_change == " NULLS FIRST") 2527 ): 2528 # BigQuery does not allow these ordering/nulls combinations when used under 2529 # an aggregation func or under a window containing one 2530 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2531 2532 if isinstance(ancestor, exp.Window): 2533 ancestor = ancestor.this 2534 if isinstance(ancestor, exp.AggFunc): 2535 self.unsupported( 2536 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2537 ) 2538 nulls_sort_change = "" 2539 elif self.NULL_ORDERING_SUPPORTED is None: 2540 if expression.this.is_int: 2541 self.unsupported( 2542 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2543 ) 2544 elif not isinstance(expression.this, exp.Rand): 2545 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2546 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2547 nulls_sort_change = "" 2548 2549 with_fill = self.sql(expression, "with_fill") 2550 with_fill = f" {with_fill}" if with_fill else "" 2551 2552 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2553 2554 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2555 window_frame = self.sql(expression, "window_frame") 2556 window_frame = f"{window_frame} " if window_frame else "" 2557 2558 this = self.sql(expression, "this") 2559 2560 return f"{window_frame}{this}" 2561 2562 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2563 partition = self.partition_by_sql(expression) 2564 order = self.sql(expression, "order") 2565 measures = self.expressions(expression, key="measures") 2566 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2567 rows = self.sql(expression, "rows") 2568 rows = self.seg(rows) if rows else "" 2569 after = self.sql(expression, "after") 2570 after = self.seg(after) if after else "" 2571 pattern = self.sql(expression, "pattern") 2572 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2573 definition_sqls = [ 2574 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2575 for definition in expression.args.get("define", []) 2576 ] 2577 definitions = self.expressions(sqls=definition_sqls) 2578 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2579 body = "".join( 2580 ( 2581 partition, 2582 order, 2583 measures, 2584 rows, 2585 after, 2586 pattern, 2587 define, 2588 ) 2589 ) 2590 alias = self.sql(expression, "alias") 2591 alias = f" {alias}" if alias else "" 2592 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2593 2594 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2595 limit = expression.args.get("limit") 2596 2597 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2598 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2599 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2600 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2601 2602 return csv( 2603 *sqls, 2604 *[self.sql(join) for join in expression.args.get("joins") or []], 2605 self.sql(expression, "match"), 2606 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2607 self.sql(expression, "prewhere"), 2608 self.sql(expression, "where"), 2609 self.sql(expression, "connect"), 2610 self.sql(expression, "group"), 2611 self.sql(expression, "having"), 2612 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2613 self.sql(expression, "order"), 2614 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2615 *self.after_limit_modifiers(expression), 2616 self.options_modifier(expression), 2617 self.for_modifiers(expression), 2618 sep="", 2619 ) 2620 2621 def options_modifier(self, expression: exp.Expression) -> str: 2622 options = self.expressions(expression, key="options") 2623 return f" {options}" if options else "" 2624 2625 def for_modifiers(self, expression: exp.Expression) -> str: 2626 for_modifiers = self.expressions(expression, key="for") 2627 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2628 2629 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2630 self.unsupported("Unsupported query option.") 2631 return "" 2632 2633 def offset_limit_modifiers( 2634 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2635 ) -> t.List[str]: 2636 return [ 2637 self.sql(expression, "offset") if fetch else self.sql(limit), 2638 self.sql(limit) if fetch else self.sql(expression, "offset"), 2639 ] 2640 2641 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2642 locks = self.expressions(expression, key="locks", sep=" ") 2643 locks = f" {locks}" if locks else "" 2644 return [locks, self.sql(expression, "sample")] 2645 2646 def select_sql(self, expression: exp.Select) -> str: 2647 into = expression.args.get("into") 2648 if not self.SUPPORTS_SELECT_INTO and into: 2649 into.pop() 2650 2651 hint = self.sql(expression, "hint") 2652 distinct = self.sql(expression, "distinct") 2653 distinct = f" {distinct}" if distinct else "" 2654 kind = self.sql(expression, "kind") 2655 2656 limit = expression.args.get("limit") 2657 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2658 top = self.limit_sql(limit, top=True) 2659 limit.pop() 2660 else: 2661 top = "" 2662 2663 expressions = self.expressions(expression) 2664 2665 if kind: 2666 if kind in self.SELECT_KINDS: 2667 kind = f" AS {kind}" 2668 else: 2669 if kind == "STRUCT": 2670 expressions = self.expressions( 2671 sqls=[ 2672 self.sql( 2673 exp.Struct( 2674 expressions=[ 2675 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2676 if isinstance(e, exp.Alias) 2677 else e 2678 for e in expression.expressions 2679 ] 2680 ) 2681 ) 2682 ] 2683 ) 2684 kind = "" 2685 2686 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2687 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2688 2689 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2690 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2691 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2692 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2693 sql = self.query_modifiers( 2694 expression, 2695 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2696 self.sql(expression, "into", comment=False), 2697 self.sql(expression, "from", comment=False), 2698 ) 2699 2700 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2701 if expression.args.get("with"): 2702 sql = self.maybe_comment(sql, expression) 2703 expression.pop_comments() 2704 2705 sql = self.prepend_ctes(expression, sql) 2706 2707 if not self.SUPPORTS_SELECT_INTO and into: 2708 if into.args.get("temporary"): 2709 table_kind = " TEMPORARY" 2710 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2711 table_kind = " UNLOGGED" 2712 else: 2713 table_kind = "" 2714 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2715 2716 return sql 2717 2718 def schema_sql(self, expression: exp.Schema) -> str: 2719 this = self.sql(expression, "this") 2720 sql = self.schema_columns_sql(expression) 2721 return f"{this} {sql}" if this and sql else this or sql 2722 2723 def schema_columns_sql(self, expression: exp.Schema) -> str: 2724 if expression.expressions: 2725 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2726 return "" 2727 2728 def star_sql(self, expression: exp.Star) -> str: 2729 except_ = self.expressions(expression, key="except", flat=True) 2730 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2731 replace = self.expressions(expression, key="replace", flat=True) 2732 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2733 rename = self.expressions(expression, key="rename", flat=True) 2734 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2735 return f"*{except_}{replace}{rename}" 2736 2737 def parameter_sql(self, expression: exp.Parameter) -> str: 2738 this = self.sql(expression, "this") 2739 return f"{self.PARAMETER_TOKEN}{this}" 2740 2741 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2742 this = self.sql(expression, "this") 2743 kind = expression.text("kind") 2744 if kind: 2745 kind = f"{kind}." 2746 return f"@@{kind}{this}" 2747 2748 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2749 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2750 2751 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2752 alias = self.sql(expression, "alias") 2753 alias = f"{sep}{alias}" if alias else "" 2754 sample = self.sql(expression, "sample") 2755 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2756 alias = f"{sample}{alias}" 2757 2758 # Set to None so it's not generated again by self.query_modifiers() 2759 expression.set("sample", None) 2760 2761 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2762 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2763 return self.prepend_ctes(expression, sql) 2764 2765 def qualify_sql(self, expression: exp.Qualify) -> str: 2766 this = self.indent(self.sql(expression, "this")) 2767 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2768 2769 def unnest_sql(self, expression: exp.Unnest) -> str: 2770 args = self.expressions(expression, flat=True) 2771 2772 alias = expression.args.get("alias") 2773 offset = expression.args.get("offset") 2774 2775 if self.UNNEST_WITH_ORDINALITY: 2776 if alias and isinstance(offset, exp.Expression): 2777 alias.append("columns", offset) 2778 2779 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2780 columns = alias.columns 2781 alias = self.sql(columns[0]) if columns else "" 2782 else: 2783 alias = self.sql(alias) 2784 2785 alias = f" AS {alias}" if alias else alias 2786 if self.UNNEST_WITH_ORDINALITY: 2787 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2788 else: 2789 if isinstance(offset, exp.Expression): 2790 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2791 elif offset: 2792 suffix = f"{alias} WITH OFFSET" 2793 else: 2794 suffix = alias 2795 2796 return f"UNNEST({args}){suffix}" 2797 2798 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2799 return "" 2800 2801 def where_sql(self, expression: exp.Where) -> str: 2802 this = self.indent(self.sql(expression, "this")) 2803 return f"{self.seg('WHERE')}{self.sep()}{this}" 2804 2805 def window_sql(self, expression: exp.Window) -> str: 2806 this = self.sql(expression, "this") 2807 partition = self.partition_by_sql(expression) 2808 order = expression.args.get("order") 2809 order = self.order_sql(order, flat=True) if order else "" 2810 spec = self.sql(expression, "spec") 2811 alias = self.sql(expression, "alias") 2812 over = self.sql(expression, "over") or "OVER" 2813 2814 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2815 2816 first = expression.args.get("first") 2817 if first is None: 2818 first = "" 2819 else: 2820 first = "FIRST" if first else "LAST" 2821 2822 if not partition and not order and not spec and alias: 2823 return f"{this} {alias}" 2824 2825 args = self.format_args( 2826 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2827 ) 2828 return f"{this} ({args})" 2829 2830 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2831 partition = self.expressions(expression, key="partition_by", flat=True) 2832 return f"PARTITION BY {partition}" if partition else "" 2833 2834 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2835 kind = self.sql(expression, "kind") 2836 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2837 end = ( 2838 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2839 or "CURRENT ROW" 2840 ) 2841 2842 window_spec = f"{kind} BETWEEN {start} AND {end}" 2843 2844 exclude = self.sql(expression, "exclude") 2845 if exclude: 2846 if self.SUPPORTS_WINDOW_EXCLUDE: 2847 window_spec += f" EXCLUDE {exclude}" 2848 else: 2849 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2850 2851 return window_spec 2852 2853 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2854 this = self.sql(expression, "this") 2855 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2856 return f"{this} WITHIN GROUP ({expression_sql})" 2857 2858 def between_sql(self, expression: exp.Between) -> str: 2859 this = self.sql(expression, "this") 2860 low = self.sql(expression, "low") 2861 high = self.sql(expression, "high") 2862 return f"{this} BETWEEN {low} AND {high}" 2863 2864 def bracket_offset_expressions( 2865 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2866 ) -> t.List[exp.Expression]: 2867 return apply_index_offset( 2868 expression.this, 2869 expression.expressions, 2870 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2871 dialect=self.dialect, 2872 ) 2873 2874 def bracket_sql(self, expression: exp.Bracket) -> str: 2875 expressions = self.bracket_offset_expressions(expression) 2876 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2877 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2878 2879 def all_sql(self, expression: exp.All) -> str: 2880 return f"ALL {self.wrap(expression)}" 2881 2882 def any_sql(self, expression: exp.Any) -> str: 2883 this = self.sql(expression, "this") 2884 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2885 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2886 this = self.wrap(this) 2887 return f"ANY{this}" 2888 return f"ANY {this}" 2889 2890 def exists_sql(self, expression: exp.Exists) -> str: 2891 return f"EXISTS{self.wrap(expression)}" 2892 2893 def case_sql(self, expression: exp.Case) -> str: 2894 this = self.sql(expression, "this") 2895 statements = [f"CASE {this}" if this else "CASE"] 2896 2897 for e in expression.args["ifs"]: 2898 statements.append(f"WHEN {self.sql(e, 'this')}") 2899 statements.append(f"THEN {self.sql(e, 'true')}") 2900 2901 default = self.sql(expression, "default") 2902 2903 if default: 2904 statements.append(f"ELSE {default}") 2905 2906 statements.append("END") 2907 2908 if self.pretty and self.too_wide(statements): 2909 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2910 2911 return " ".join(statements) 2912 2913 def constraint_sql(self, expression: exp.Constraint) -> str: 2914 this = self.sql(expression, "this") 2915 expressions = self.expressions(expression, flat=True) 2916 return f"CONSTRAINT {this} {expressions}" 2917 2918 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2919 order = expression.args.get("order") 2920 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2921 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2922 2923 def extract_sql(self, expression: exp.Extract) -> str: 2924 from sqlglot.dialects.dialect import map_date_part 2925 2926 this = ( 2927 map_date_part(expression.this, self.dialect) 2928 if self.NORMALIZE_EXTRACT_DATE_PARTS 2929 else expression.this 2930 ) 2931 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2932 expression_sql = self.sql(expression, "expression") 2933 2934 return f"EXTRACT({this_sql} FROM {expression_sql})" 2935 2936 def trim_sql(self, expression: exp.Trim) -> str: 2937 trim_type = self.sql(expression, "position") 2938 2939 if trim_type == "LEADING": 2940 func_name = "LTRIM" 2941 elif trim_type == "TRAILING": 2942 func_name = "RTRIM" 2943 else: 2944 func_name = "TRIM" 2945 2946 return self.func(func_name, expression.this, expression.expression) 2947 2948 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2949 args = expression.expressions 2950 if isinstance(expression, exp.ConcatWs): 2951 args = args[1:] # Skip the delimiter 2952 2953 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2954 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2955 2956 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2957 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2958 2959 return args 2960 2961 def concat_sql(self, expression: exp.Concat) -> str: 2962 expressions = self.convert_concat_args(expression) 2963 2964 # Some dialects don't allow a single-argument CONCAT call 2965 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2966 return self.sql(expressions[0]) 2967 2968 return self.func("CONCAT", *expressions) 2969 2970 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2971 return self.func( 2972 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2973 ) 2974 2975 def check_sql(self, expression: exp.Check) -> str: 2976 this = self.sql(expression, key="this") 2977 return f"CHECK ({this})" 2978 2979 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2980 expressions = self.expressions(expression, flat=True) 2981 expressions = f" ({expressions})" if expressions else "" 2982 reference = self.sql(expression, "reference") 2983 reference = f" {reference}" if reference else "" 2984 delete = self.sql(expression, "delete") 2985 delete = f" ON DELETE {delete}" if delete else "" 2986 update = self.sql(expression, "update") 2987 update = f" ON UPDATE {update}" if update else "" 2988 options = self.expressions(expression, key="options", flat=True, sep=" ") 2989 options = f" {options}" if options else "" 2990 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 2991 2992 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2993 expressions = self.expressions(expression, flat=True) 2994 options = self.expressions(expression, key="options", flat=True, sep=" ") 2995 options = f" {options}" if options else "" 2996 return f"PRIMARY KEY ({expressions}){options}" 2997 2998 def if_sql(self, expression: exp.If) -> str: 2999 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3000 3001 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3002 modifier = expression.args.get("modifier") 3003 modifier = f" {modifier}" if modifier else "" 3004 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3005 3006 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3007 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3008 3009 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3010 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3011 3012 if expression.args.get("escape"): 3013 path = self.escape_str(path) 3014 3015 if self.QUOTE_JSON_PATH: 3016 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3017 3018 return path 3019 3020 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3021 if isinstance(expression, exp.JSONPathPart): 3022 transform = self.TRANSFORMS.get(expression.__class__) 3023 if not callable(transform): 3024 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3025 return "" 3026 3027 return transform(self, expression) 3028 3029 if isinstance(expression, int): 3030 return str(expression) 3031 3032 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3033 escaped = expression.replace("'", "\\'") 3034 escaped = f"\\'{expression}\\'" 3035 else: 3036 escaped = expression.replace('"', '\\"') 3037 escaped = f'"{escaped}"' 3038 3039 return escaped 3040 3041 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3042 return f"{self.sql(expression, 'this')} FORMAT JSON" 3043 3044 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3045 # Output the Teradata column FORMAT override. 3046 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3047 this = self.sql(expression, "this") 3048 fmt = self.sql(expression, "format") 3049 return f"{this} (FORMAT {fmt})" 3050 3051 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3052 null_handling = expression.args.get("null_handling") 3053 null_handling = f" {null_handling}" if null_handling else "" 3054 3055 unique_keys = expression.args.get("unique_keys") 3056 if unique_keys is not None: 3057 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3058 else: 3059 unique_keys = "" 3060 3061 return_type = self.sql(expression, "return_type") 3062 return_type = f" RETURNING {return_type}" if return_type else "" 3063 encoding = self.sql(expression, "encoding") 3064 encoding = f" ENCODING {encoding}" if encoding else "" 3065 3066 return self.func( 3067 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3068 *expression.expressions, 3069 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3070 ) 3071 3072 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3073 return self.jsonobject_sql(expression) 3074 3075 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3076 null_handling = expression.args.get("null_handling") 3077 null_handling = f" {null_handling}" if null_handling else "" 3078 return_type = self.sql(expression, "return_type") 3079 return_type = f" RETURNING {return_type}" if return_type else "" 3080 strict = " STRICT" if expression.args.get("strict") else "" 3081 return self.func( 3082 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3083 ) 3084 3085 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3086 this = self.sql(expression, "this") 3087 order = self.sql(expression, "order") 3088 null_handling = expression.args.get("null_handling") 3089 null_handling = f" {null_handling}" if null_handling else "" 3090 return_type = self.sql(expression, "return_type") 3091 return_type = f" RETURNING {return_type}" if return_type else "" 3092 strict = " STRICT" if expression.args.get("strict") else "" 3093 return self.func( 3094 "JSON_ARRAYAGG", 3095 this, 3096 suffix=f"{order}{null_handling}{return_type}{strict})", 3097 ) 3098 3099 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3100 path = self.sql(expression, "path") 3101 path = f" PATH {path}" if path else "" 3102 nested_schema = self.sql(expression, "nested_schema") 3103 3104 if nested_schema: 3105 return f"NESTED{path} {nested_schema}" 3106 3107 this = self.sql(expression, "this") 3108 kind = self.sql(expression, "kind") 3109 kind = f" {kind}" if kind else "" 3110 return f"{this}{kind}{path}" 3111 3112 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3113 return self.func("COLUMNS", *expression.expressions) 3114 3115 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3116 this = self.sql(expression, "this") 3117 path = self.sql(expression, "path") 3118 path = f", {path}" if path else "" 3119 error_handling = expression.args.get("error_handling") 3120 error_handling = f" {error_handling}" if error_handling else "" 3121 empty_handling = expression.args.get("empty_handling") 3122 empty_handling = f" {empty_handling}" if empty_handling else "" 3123 schema = self.sql(expression, "schema") 3124 return self.func( 3125 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3126 ) 3127 3128 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3129 this = self.sql(expression, "this") 3130 kind = self.sql(expression, "kind") 3131 path = self.sql(expression, "path") 3132 path = f" {path}" if path else "" 3133 as_json = " AS JSON" if expression.args.get("as_json") else "" 3134 return f"{this} {kind}{path}{as_json}" 3135 3136 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3137 this = self.sql(expression, "this") 3138 path = self.sql(expression, "path") 3139 path = f", {path}" if path else "" 3140 expressions = self.expressions(expression) 3141 with_ = ( 3142 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3143 if expressions 3144 else "" 3145 ) 3146 return f"OPENJSON({this}{path}){with_}" 3147 3148 def in_sql(self, expression: exp.In) -> str: 3149 query = expression.args.get("query") 3150 unnest = expression.args.get("unnest") 3151 field = expression.args.get("field") 3152 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3153 3154 if query: 3155 in_sql = self.sql(query) 3156 elif unnest: 3157 in_sql = self.in_unnest_op(unnest) 3158 elif field: 3159 in_sql = self.sql(field) 3160 else: 3161 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3162 3163 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3164 3165 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3166 return f"(SELECT {self.sql(unnest)})" 3167 3168 def interval_sql(self, expression: exp.Interval) -> str: 3169 unit = self.sql(expression, "unit") 3170 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3171 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3172 unit = f" {unit}" if unit else "" 3173 3174 if self.SINGLE_STRING_INTERVAL: 3175 this = expression.this.name if expression.this else "" 3176 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3177 3178 this = self.sql(expression, "this") 3179 if this: 3180 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3181 this = f" {this}" if unwrapped else f" ({this})" 3182 3183 return f"INTERVAL{this}{unit}" 3184 3185 def return_sql(self, expression: exp.Return) -> str: 3186 return f"RETURN {self.sql(expression, 'this')}" 3187 3188 def reference_sql(self, expression: exp.Reference) -> str: 3189 this = self.sql(expression, "this") 3190 expressions = self.expressions(expression, flat=True) 3191 expressions = f"({expressions})" if expressions else "" 3192 options = self.expressions(expression, key="options", flat=True, sep=" ") 3193 options = f" {options}" if options else "" 3194 return f"REFERENCES {this}{expressions}{options}" 3195 3196 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3197 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3198 parent = expression.parent 3199 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3200 return self.func( 3201 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3202 ) 3203 3204 def paren_sql(self, expression: exp.Paren) -> str: 3205 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3206 return f"({sql}{self.seg(')', sep='')}" 3207 3208 def neg_sql(self, expression: exp.Neg) -> str: 3209 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3210 this_sql = self.sql(expression, "this") 3211 sep = " " if this_sql[0] == "-" else "" 3212 return f"-{sep}{this_sql}" 3213 3214 def not_sql(self, expression: exp.Not) -> str: 3215 return f"NOT {self.sql(expression, 'this')}" 3216 3217 def alias_sql(self, expression: exp.Alias) -> str: 3218 alias = self.sql(expression, "alias") 3219 alias = f" AS {alias}" if alias else "" 3220 return f"{self.sql(expression, 'this')}{alias}" 3221 3222 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3223 alias = expression.args["alias"] 3224 3225 parent = expression.parent 3226 pivot = parent and parent.parent 3227 3228 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3229 identifier_alias = isinstance(alias, exp.Identifier) 3230 literal_alias = isinstance(alias, exp.Literal) 3231 3232 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3233 alias.replace(exp.Literal.string(alias.output_name)) 3234 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3235 alias.replace(exp.to_identifier(alias.output_name)) 3236 3237 return self.alias_sql(expression) 3238 3239 def aliases_sql(self, expression: exp.Aliases) -> str: 3240 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3241 3242 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3243 this = self.sql(expression, "this") 3244 index = self.sql(expression, "expression") 3245 return f"{this} AT {index}" 3246 3247 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3248 this = self.sql(expression, "this") 3249 zone = self.sql(expression, "zone") 3250 return f"{this} AT TIME ZONE {zone}" 3251 3252 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3253 this = self.sql(expression, "this") 3254 zone = self.sql(expression, "zone") 3255 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3256 3257 def add_sql(self, expression: exp.Add) -> str: 3258 return self.binary(expression, "+") 3259 3260 def and_sql( 3261 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3262 ) -> str: 3263 return self.connector_sql(expression, "AND", stack) 3264 3265 def or_sql( 3266 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3267 ) -> str: 3268 return self.connector_sql(expression, "OR", stack) 3269 3270 def xor_sql( 3271 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3272 ) -> str: 3273 return self.connector_sql(expression, "XOR", stack) 3274 3275 def connector_sql( 3276 self, 3277 expression: exp.Connector, 3278 op: str, 3279 stack: t.Optional[t.List[str | exp.Expression]] = None, 3280 ) -> str: 3281 if stack is not None: 3282 if expression.expressions: 3283 stack.append(self.expressions(expression, sep=f" {op} ")) 3284 else: 3285 stack.append(expression.right) 3286 if expression.comments and self.comments: 3287 for comment in expression.comments: 3288 if comment: 3289 op += f" /*{self.sanitize_comment(comment)}*/" 3290 stack.extend((op, expression.left)) 3291 return op 3292 3293 stack = [expression] 3294 sqls: t.List[str] = [] 3295 ops = set() 3296 3297 while stack: 3298 node = stack.pop() 3299 if isinstance(node, exp.Connector): 3300 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3301 else: 3302 sql = self.sql(node) 3303 if sqls and sqls[-1] in ops: 3304 sqls[-1] += f" {sql}" 3305 else: 3306 sqls.append(sql) 3307 3308 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3309 return sep.join(sqls) 3310 3311 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3312 return self.binary(expression, "&") 3313 3314 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3315 return self.binary(expression, "<<") 3316 3317 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3318 return f"~{self.sql(expression, 'this')}" 3319 3320 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3321 return self.binary(expression, "|") 3322 3323 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3324 return self.binary(expression, ">>") 3325 3326 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3327 return self.binary(expression, "^") 3328 3329 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3330 format_sql = self.sql(expression, "format") 3331 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3332 to_sql = self.sql(expression, "to") 3333 to_sql = f" {to_sql}" if to_sql else "" 3334 action = self.sql(expression, "action") 3335 action = f" {action}" if action else "" 3336 default = self.sql(expression, "default") 3337 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3338 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3339 3340 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3341 zone = self.sql(expression, "this") 3342 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3343 3344 def collate_sql(self, expression: exp.Collate) -> str: 3345 if self.COLLATE_IS_FUNC: 3346 return self.function_fallback_sql(expression) 3347 return self.binary(expression, "COLLATE") 3348 3349 def command_sql(self, expression: exp.Command) -> str: 3350 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3351 3352 def comment_sql(self, expression: exp.Comment) -> str: 3353 this = self.sql(expression, "this") 3354 kind = expression.args["kind"] 3355 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3356 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3357 expression_sql = self.sql(expression, "expression") 3358 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3359 3360 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3361 this = self.sql(expression, "this") 3362 delete = " DELETE" if expression.args.get("delete") else "" 3363 recompress = self.sql(expression, "recompress") 3364 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3365 to_disk = self.sql(expression, "to_disk") 3366 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3367 to_volume = self.sql(expression, "to_volume") 3368 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3369 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3370 3371 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3372 where = self.sql(expression, "where") 3373 group = self.sql(expression, "group") 3374 aggregates = self.expressions(expression, key="aggregates") 3375 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3376 3377 if not (where or group or aggregates) and len(expression.expressions) == 1: 3378 return f"TTL {self.expressions(expression, flat=True)}" 3379 3380 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3381 3382 def transaction_sql(self, expression: exp.Transaction) -> str: 3383 return "BEGIN" 3384 3385 def commit_sql(self, expression: exp.Commit) -> str: 3386 chain = expression.args.get("chain") 3387 if chain is not None: 3388 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3389 3390 return f"COMMIT{chain or ''}" 3391 3392 def rollback_sql(self, expression: exp.Rollback) -> str: 3393 savepoint = expression.args.get("savepoint") 3394 savepoint = f" TO {savepoint}" if savepoint else "" 3395 return f"ROLLBACK{savepoint}" 3396 3397 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3398 this = self.sql(expression, "this") 3399 3400 dtype = self.sql(expression, "dtype") 3401 if dtype: 3402 collate = self.sql(expression, "collate") 3403 collate = f" COLLATE {collate}" if collate else "" 3404 using = self.sql(expression, "using") 3405 using = f" USING {using}" if using else "" 3406 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3407 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3408 3409 default = self.sql(expression, "default") 3410 if default: 3411 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3412 3413 comment = self.sql(expression, "comment") 3414 if comment: 3415 return f"ALTER COLUMN {this} COMMENT {comment}" 3416 3417 visible = expression.args.get("visible") 3418 if visible: 3419 return f"ALTER COLUMN {this} SET {visible}" 3420 3421 allow_null = expression.args.get("allow_null") 3422 drop = expression.args.get("drop") 3423 3424 if not drop and not allow_null: 3425 self.unsupported("Unsupported ALTER COLUMN syntax") 3426 3427 if allow_null is not None: 3428 keyword = "DROP" if drop else "SET" 3429 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3430 3431 return f"ALTER COLUMN {this} DROP DEFAULT" 3432 3433 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3434 this = self.sql(expression, "this") 3435 3436 visible = expression.args.get("visible") 3437 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3438 3439 return f"ALTER INDEX {this} {visible_sql}" 3440 3441 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3442 this = self.sql(expression, "this") 3443 if not isinstance(expression.this, exp.Var): 3444 this = f"KEY DISTKEY {this}" 3445 return f"ALTER DISTSTYLE {this}" 3446 3447 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3448 compound = " COMPOUND" if expression.args.get("compound") else "" 3449 this = self.sql(expression, "this") 3450 expressions = self.expressions(expression, flat=True) 3451 expressions = f"({expressions})" if expressions else "" 3452 return f"ALTER{compound} SORTKEY {this or expressions}" 3453 3454 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3455 if not self.RENAME_TABLE_WITH_DB: 3456 # Remove db from tables 3457 expression = expression.transform( 3458 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3459 ).assert_is(exp.AlterRename) 3460 this = self.sql(expression, "this") 3461 return f"RENAME TO {this}" 3462 3463 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3464 exists = " IF EXISTS" if expression.args.get("exists") else "" 3465 old_column = self.sql(expression, "this") 3466 new_column = self.sql(expression, "to") 3467 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3468 3469 def alterset_sql(self, expression: exp.AlterSet) -> str: 3470 exprs = self.expressions(expression, flat=True) 3471 if self.ALTER_SET_WRAPPED: 3472 exprs = f"({exprs})" 3473 3474 return f"SET {exprs}" 3475 3476 def alter_sql(self, expression: exp.Alter) -> str: 3477 actions = expression.args["actions"] 3478 3479 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3480 actions[0], exp.ColumnDef 3481 ): 3482 actions_sql = self.expressions(expression, key="actions", flat=True) 3483 actions_sql = f"ADD {actions_sql}" 3484 else: 3485 actions_list = [] 3486 for action in actions: 3487 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3488 action_sql = self.add_column_sql(action) 3489 else: 3490 action_sql = self.sql(action) 3491 if isinstance(action, exp.Query): 3492 action_sql = f"AS {action_sql}" 3493 3494 actions_list.append(action_sql) 3495 3496 actions_sql = self.format_args(*actions_list).lstrip("\n") 3497 3498 exists = " IF EXISTS" if expression.args.get("exists") else "" 3499 on_cluster = self.sql(expression, "cluster") 3500 on_cluster = f" {on_cluster}" if on_cluster else "" 3501 only = " ONLY" if expression.args.get("only") else "" 3502 options = self.expressions(expression, key="options") 3503 options = f", {options}" if options else "" 3504 kind = self.sql(expression, "kind") 3505 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3506 3507 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}" 3508 3509 def add_column_sql(self, expression: exp.Expression) -> str: 3510 sql = self.sql(expression) 3511 if isinstance(expression, exp.Schema): 3512 column_text = " COLUMNS" 3513 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3514 column_text = " COLUMN" 3515 else: 3516 column_text = "" 3517 3518 return f"ADD{column_text} {sql}" 3519 3520 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3521 expressions = self.expressions(expression) 3522 exists = " IF EXISTS " if expression.args.get("exists") else " " 3523 return f"DROP{exists}{expressions}" 3524 3525 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3526 return f"ADD {self.expressions(expression, indent=False)}" 3527 3528 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3529 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3530 return f"ADD {exists}{self.sql(expression.this)}" 3531 3532 def distinct_sql(self, expression: exp.Distinct) -> str: 3533 this = self.expressions(expression, flat=True) 3534 3535 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3536 case = exp.case() 3537 for arg in expression.expressions: 3538 case = case.when(arg.is_(exp.null()), exp.null()) 3539 this = self.sql(case.else_(f"({this})")) 3540 3541 this = f" {this}" if this else "" 3542 3543 on = self.sql(expression, "on") 3544 on = f" ON {on}" if on else "" 3545 return f"DISTINCT{this}{on}" 3546 3547 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3548 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3549 3550 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3551 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3552 3553 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3554 this_sql = self.sql(expression, "this") 3555 expression_sql = self.sql(expression, "expression") 3556 kind = "MAX" if expression.args.get("max") else "MIN" 3557 return f"{this_sql} HAVING {kind} {expression_sql}" 3558 3559 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3560 return self.sql( 3561 exp.Cast( 3562 this=exp.Div(this=expression.this, expression=expression.expression), 3563 to=exp.DataType(this=exp.DataType.Type.INT), 3564 ) 3565 ) 3566 3567 def dpipe_sql(self, expression: exp.DPipe) -> str: 3568 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3569 return self.func( 3570 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3571 ) 3572 return self.binary(expression, "||") 3573 3574 def div_sql(self, expression: exp.Div) -> str: 3575 l, r = expression.left, expression.right 3576 3577 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3578 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3579 3580 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3581 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3582 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3583 3584 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3585 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3586 return self.sql( 3587 exp.cast( 3588 l / r, 3589 to=exp.DataType.Type.BIGINT, 3590 ) 3591 ) 3592 3593 return self.binary(expression, "/") 3594 3595 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3596 n = exp._wrap(expression.this, exp.Binary) 3597 d = exp._wrap(expression.expression, exp.Binary) 3598 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3599 3600 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3601 return self.binary(expression, "OVERLAPS") 3602 3603 def distance_sql(self, expression: exp.Distance) -> str: 3604 return self.binary(expression, "<->") 3605 3606 def dot_sql(self, expression: exp.Dot) -> str: 3607 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3608 3609 def eq_sql(self, expression: exp.EQ) -> str: 3610 return self.binary(expression, "=") 3611 3612 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3613 return self.binary(expression, ":=") 3614 3615 def escape_sql(self, expression: exp.Escape) -> str: 3616 return self.binary(expression, "ESCAPE") 3617 3618 def glob_sql(self, expression: exp.Glob) -> str: 3619 return self.binary(expression, "GLOB") 3620 3621 def gt_sql(self, expression: exp.GT) -> str: 3622 return self.binary(expression, ">") 3623 3624 def gte_sql(self, expression: exp.GTE) -> str: 3625 return self.binary(expression, ">=") 3626 3627 def ilike_sql(self, expression: exp.ILike) -> str: 3628 return self.binary(expression, "ILIKE") 3629 3630 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3631 return self.binary(expression, "ILIKE ANY") 3632 3633 def is_sql(self, expression: exp.Is) -> str: 3634 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3635 return self.sql( 3636 expression.this if expression.expression.this else exp.not_(expression.this) 3637 ) 3638 return self.binary(expression, "IS") 3639 3640 def like_sql(self, expression: exp.Like) -> str: 3641 return self.binary(expression, "LIKE") 3642 3643 def likeany_sql(self, expression: exp.LikeAny) -> str: 3644 return self.binary(expression, "LIKE ANY") 3645 3646 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3647 return self.binary(expression, "SIMILAR TO") 3648 3649 def lt_sql(self, expression: exp.LT) -> str: 3650 return self.binary(expression, "<") 3651 3652 def lte_sql(self, expression: exp.LTE) -> str: 3653 return self.binary(expression, "<=") 3654 3655 def mod_sql(self, expression: exp.Mod) -> str: 3656 return self.binary(expression, "%") 3657 3658 def mul_sql(self, expression: exp.Mul) -> str: 3659 return self.binary(expression, "*") 3660 3661 def neq_sql(self, expression: exp.NEQ) -> str: 3662 return self.binary(expression, "<>") 3663 3664 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3665 return self.binary(expression, "IS NOT DISTINCT FROM") 3666 3667 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3668 return self.binary(expression, "IS DISTINCT FROM") 3669 3670 def slice_sql(self, expression: exp.Slice) -> str: 3671 return self.binary(expression, ":") 3672 3673 def sub_sql(self, expression: exp.Sub) -> str: 3674 return self.binary(expression, "-") 3675 3676 def trycast_sql(self, expression: exp.TryCast) -> str: 3677 return self.cast_sql(expression, safe_prefix="TRY_") 3678 3679 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3680 return self.cast_sql(expression) 3681 3682 def try_sql(self, expression: exp.Try) -> str: 3683 if not self.TRY_SUPPORTED: 3684 self.unsupported("Unsupported TRY function") 3685 return self.sql(expression, "this") 3686 3687 return self.func("TRY", expression.this) 3688 3689 def log_sql(self, expression: exp.Log) -> str: 3690 this = expression.this 3691 expr = expression.expression 3692 3693 if self.dialect.LOG_BASE_FIRST is False: 3694 this, expr = expr, this 3695 elif self.dialect.LOG_BASE_FIRST is None and expr: 3696 if this.name in ("2", "10"): 3697 return self.func(f"LOG{this.name}", expr) 3698 3699 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3700 3701 return self.func("LOG", this, expr) 3702 3703 def use_sql(self, expression: exp.Use) -> str: 3704 kind = self.sql(expression, "kind") 3705 kind = f" {kind}" if kind else "" 3706 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3707 this = f" {this}" if this else "" 3708 return f"USE{kind}{this}" 3709 3710 def binary(self, expression: exp.Binary, op: str) -> str: 3711 sqls: t.List[str] = [] 3712 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3713 binary_type = type(expression) 3714 3715 while stack: 3716 node = stack.pop() 3717 3718 if type(node) is binary_type: 3719 op_func = node.args.get("operator") 3720 if op_func: 3721 op = f"OPERATOR({self.sql(op_func)})" 3722 3723 stack.append(node.right) 3724 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3725 stack.append(node.left) 3726 else: 3727 sqls.append(self.sql(node)) 3728 3729 return "".join(sqls) 3730 3731 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3732 to_clause = self.sql(expression, "to") 3733 if to_clause: 3734 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3735 3736 return self.function_fallback_sql(expression) 3737 3738 def function_fallback_sql(self, expression: exp.Func) -> str: 3739 args = [] 3740 3741 for key in expression.arg_types: 3742 arg_value = expression.args.get(key) 3743 3744 if isinstance(arg_value, list): 3745 for value in arg_value: 3746 args.append(value) 3747 elif arg_value is not None: 3748 args.append(arg_value) 3749 3750 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3751 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3752 else: 3753 name = expression.sql_name() 3754 3755 return self.func(name, *args) 3756 3757 def func( 3758 self, 3759 name: str, 3760 *args: t.Optional[exp.Expression | str], 3761 prefix: str = "(", 3762 suffix: str = ")", 3763 normalize: bool = True, 3764 ) -> str: 3765 name = self.normalize_func(name) if normalize else name 3766 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3767 3768 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3769 arg_sqls = tuple( 3770 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3771 ) 3772 if self.pretty and self.too_wide(arg_sqls): 3773 return self.indent( 3774 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3775 ) 3776 return sep.join(arg_sqls) 3777 3778 def too_wide(self, args: t.Iterable) -> bool: 3779 return sum(len(arg) for arg in args) > self.max_text_width 3780 3781 def format_time( 3782 self, 3783 expression: exp.Expression, 3784 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3785 inverse_time_trie: t.Optional[t.Dict] = None, 3786 ) -> t.Optional[str]: 3787 return format_time( 3788 self.sql(expression, "format"), 3789 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3790 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3791 ) 3792 3793 def expressions( 3794 self, 3795 expression: t.Optional[exp.Expression] = None, 3796 key: t.Optional[str] = None, 3797 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3798 flat: bool = False, 3799 indent: bool = True, 3800 skip_first: bool = False, 3801 skip_last: bool = False, 3802 sep: str = ", ", 3803 prefix: str = "", 3804 dynamic: bool = False, 3805 new_line: bool = False, 3806 ) -> str: 3807 expressions = expression.args.get(key or "expressions") if expression else sqls 3808 3809 if not expressions: 3810 return "" 3811 3812 if flat: 3813 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3814 3815 num_sqls = len(expressions) 3816 result_sqls = [] 3817 3818 for i, e in enumerate(expressions): 3819 sql = self.sql(e, comment=False) 3820 if not sql: 3821 continue 3822 3823 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3824 3825 if self.pretty: 3826 if self.leading_comma: 3827 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3828 else: 3829 result_sqls.append( 3830 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3831 ) 3832 else: 3833 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3834 3835 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3836 if new_line: 3837 result_sqls.insert(0, "") 3838 result_sqls.append("") 3839 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3840 else: 3841 result_sql = "".join(result_sqls) 3842 3843 return ( 3844 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3845 if indent 3846 else result_sql 3847 ) 3848 3849 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3850 flat = flat or isinstance(expression.parent, exp.Properties) 3851 expressions_sql = self.expressions(expression, flat=flat) 3852 if flat: 3853 return f"{op} {expressions_sql}" 3854 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3855 3856 def naked_property(self, expression: exp.Property) -> str: 3857 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3858 if not property_name: 3859 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3860 return f"{property_name} {self.sql(expression, 'this')}" 3861 3862 def tag_sql(self, expression: exp.Tag) -> str: 3863 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3864 3865 def token_sql(self, token_type: TokenType) -> str: 3866 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3867 3868 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3869 this = self.sql(expression, "this") 3870 expressions = self.no_identify(self.expressions, expression) 3871 expressions = ( 3872 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3873 ) 3874 return f"{this}{expressions}" if expressions.strip() != "" else this 3875 3876 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3877 this = self.sql(expression, "this") 3878 expressions = self.expressions(expression, flat=True) 3879 return f"{this}({expressions})" 3880 3881 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3882 return self.binary(expression, "=>") 3883 3884 def when_sql(self, expression: exp.When) -> str: 3885 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3886 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3887 condition = self.sql(expression, "condition") 3888 condition = f" AND {condition}" if condition else "" 3889 3890 then_expression = expression.args.get("then") 3891 if isinstance(then_expression, exp.Insert): 3892 this = self.sql(then_expression, "this") 3893 this = f"INSERT {this}" if this else "INSERT" 3894 then = self.sql(then_expression, "expression") 3895 then = f"{this} VALUES {then}" if then else this 3896 elif isinstance(then_expression, exp.Update): 3897 if isinstance(then_expression.args.get("expressions"), exp.Star): 3898 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3899 else: 3900 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3901 else: 3902 then = self.sql(then_expression) 3903 return f"WHEN {matched}{source}{condition} THEN {then}" 3904 3905 def whens_sql(self, expression: exp.Whens) -> str: 3906 return self.expressions(expression, sep=" ", indent=False) 3907 3908 def merge_sql(self, expression: exp.Merge) -> str: 3909 table = expression.this 3910 table_alias = "" 3911 3912 hints = table.args.get("hints") 3913 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3914 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3915 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3916 3917 this = self.sql(table) 3918 using = f"USING {self.sql(expression, 'using')}" 3919 on = f"ON {self.sql(expression, 'on')}" 3920 whens = self.sql(expression, "whens") 3921 3922 returning = self.sql(expression, "returning") 3923 if returning: 3924 whens = f"{whens}{returning}" 3925 3926 sep = self.sep() 3927 3928 return self.prepend_ctes( 3929 expression, 3930 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3931 ) 3932 3933 @unsupported_args("format") 3934 def tochar_sql(self, expression: exp.ToChar) -> str: 3935 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3936 3937 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3938 if not self.SUPPORTS_TO_NUMBER: 3939 self.unsupported("Unsupported TO_NUMBER function") 3940 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3941 3942 fmt = expression.args.get("format") 3943 if not fmt: 3944 self.unsupported("Conversion format is required for TO_NUMBER") 3945 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3946 3947 return self.func("TO_NUMBER", expression.this, fmt) 3948 3949 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3950 this = self.sql(expression, "this") 3951 kind = self.sql(expression, "kind") 3952 settings_sql = self.expressions(expression, key="settings", sep=" ") 3953 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3954 return f"{this}({kind}{args})" 3955 3956 def dictrange_sql(self, expression: exp.DictRange) -> str: 3957 this = self.sql(expression, "this") 3958 max = self.sql(expression, "max") 3959 min = self.sql(expression, "min") 3960 return f"{this}(MIN {min} MAX {max})" 3961 3962 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3963 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3964 3965 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3966 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3967 3968 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 3969 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 3970 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 3971 3972 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3973 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3974 expressions = self.expressions(expression, flat=True) 3975 expressions = f" {self.wrap(expressions)}" if expressions else "" 3976 buckets = self.sql(expression, "buckets") 3977 kind = self.sql(expression, "kind") 3978 buckets = f" BUCKETS {buckets}" if buckets else "" 3979 order = self.sql(expression, "order") 3980 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3981 3982 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3983 return "" 3984 3985 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3986 expressions = self.expressions(expression, key="expressions", flat=True) 3987 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3988 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3989 buckets = self.sql(expression, "buckets") 3990 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3991 3992 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3993 this = self.sql(expression, "this") 3994 having = self.sql(expression, "having") 3995 3996 if having: 3997 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3998 3999 return self.func("ANY_VALUE", this) 4000 4001 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4002 transform = self.func("TRANSFORM", *expression.expressions) 4003 row_format_before = self.sql(expression, "row_format_before") 4004 row_format_before = f" {row_format_before}" if row_format_before else "" 4005 record_writer = self.sql(expression, "record_writer") 4006 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4007 using = f" USING {self.sql(expression, 'command_script')}" 4008 schema = self.sql(expression, "schema") 4009 schema = f" AS {schema}" if schema else "" 4010 row_format_after = self.sql(expression, "row_format_after") 4011 row_format_after = f" {row_format_after}" if row_format_after else "" 4012 record_reader = self.sql(expression, "record_reader") 4013 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4014 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4015 4016 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4017 key_block_size = self.sql(expression, "key_block_size") 4018 if key_block_size: 4019 return f"KEY_BLOCK_SIZE = {key_block_size}" 4020 4021 using = self.sql(expression, "using") 4022 if using: 4023 return f"USING {using}" 4024 4025 parser = self.sql(expression, "parser") 4026 if parser: 4027 return f"WITH PARSER {parser}" 4028 4029 comment = self.sql(expression, "comment") 4030 if comment: 4031 return f"COMMENT {comment}" 4032 4033 visible = expression.args.get("visible") 4034 if visible is not None: 4035 return "VISIBLE" if visible else "INVISIBLE" 4036 4037 engine_attr = self.sql(expression, "engine_attr") 4038 if engine_attr: 4039 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4040 4041 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4042 if secondary_engine_attr: 4043 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4044 4045 self.unsupported("Unsupported index constraint option.") 4046 return "" 4047 4048 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4049 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4050 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4051 4052 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4053 kind = self.sql(expression, "kind") 4054 kind = f"{kind} INDEX" if kind else "INDEX" 4055 this = self.sql(expression, "this") 4056 this = f" {this}" if this else "" 4057 index_type = self.sql(expression, "index_type") 4058 index_type = f" USING {index_type}" if index_type else "" 4059 expressions = self.expressions(expression, flat=True) 4060 expressions = f" ({expressions})" if expressions else "" 4061 options = self.expressions(expression, key="options", sep=" ") 4062 options = f" {options}" if options else "" 4063 return f"{kind}{this}{index_type}{expressions}{options}" 4064 4065 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4066 if self.NVL2_SUPPORTED: 4067 return self.function_fallback_sql(expression) 4068 4069 case = exp.Case().when( 4070 expression.this.is_(exp.null()).not_(copy=False), 4071 expression.args["true"], 4072 copy=False, 4073 ) 4074 else_cond = expression.args.get("false") 4075 if else_cond: 4076 case.else_(else_cond, copy=False) 4077 4078 return self.sql(case) 4079 4080 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4081 this = self.sql(expression, "this") 4082 expr = self.sql(expression, "expression") 4083 iterator = self.sql(expression, "iterator") 4084 condition = self.sql(expression, "condition") 4085 condition = f" IF {condition}" if condition else "" 4086 return f"{this} FOR {expr} IN {iterator}{condition}" 4087 4088 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4089 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4090 4091 def opclass_sql(self, expression: exp.Opclass) -> str: 4092 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4093 4094 def predict_sql(self, expression: exp.Predict) -> str: 4095 model = self.sql(expression, "this") 4096 model = f"MODEL {model}" 4097 table = self.sql(expression, "expression") 4098 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4099 parameters = self.sql(expression, "params_struct") 4100 return self.func("PREDICT", model, table, parameters or None) 4101 4102 def forin_sql(self, expression: exp.ForIn) -> str: 4103 this = self.sql(expression, "this") 4104 expression_sql = self.sql(expression, "expression") 4105 return f"FOR {this} DO {expression_sql}" 4106 4107 def refresh_sql(self, expression: exp.Refresh) -> str: 4108 this = self.sql(expression, "this") 4109 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4110 return f"REFRESH {table}{this}" 4111 4112 def toarray_sql(self, expression: exp.ToArray) -> str: 4113 arg = expression.this 4114 if not arg.type: 4115 from sqlglot.optimizer.annotate_types import annotate_types 4116 4117 arg = annotate_types(arg, dialect=self.dialect) 4118 4119 if arg.is_type(exp.DataType.Type.ARRAY): 4120 return self.sql(arg) 4121 4122 cond_for_null = arg.is_(exp.null()) 4123 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4124 4125 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4126 this = expression.this 4127 time_format = self.format_time(expression) 4128 4129 if time_format: 4130 return self.sql( 4131 exp.cast( 4132 exp.StrToTime(this=this, format=expression.args["format"]), 4133 exp.DataType.Type.TIME, 4134 ) 4135 ) 4136 4137 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4138 return self.sql(this) 4139 4140 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4141 4142 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4143 this = expression.this 4144 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4145 return self.sql(this) 4146 4147 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4148 4149 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4150 this = expression.this 4151 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4152 return self.sql(this) 4153 4154 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4155 4156 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4157 this = expression.this 4158 time_format = self.format_time(expression) 4159 4160 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4161 return self.sql( 4162 exp.cast( 4163 exp.StrToTime(this=this, format=expression.args["format"]), 4164 exp.DataType.Type.DATE, 4165 ) 4166 ) 4167 4168 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4169 return self.sql(this) 4170 4171 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4172 4173 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4174 return self.sql( 4175 exp.func( 4176 "DATEDIFF", 4177 expression.this, 4178 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4179 "day", 4180 ) 4181 ) 4182 4183 def lastday_sql(self, expression: exp.LastDay) -> str: 4184 if self.LAST_DAY_SUPPORTS_DATE_PART: 4185 return self.function_fallback_sql(expression) 4186 4187 unit = expression.text("unit") 4188 if unit and unit != "MONTH": 4189 self.unsupported("Date parts are not supported in LAST_DAY.") 4190 4191 return self.func("LAST_DAY", expression.this) 4192 4193 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4194 from sqlglot.dialects.dialect import unit_to_str 4195 4196 return self.func( 4197 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4198 ) 4199 4200 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4201 if self.CAN_IMPLEMENT_ARRAY_ANY: 4202 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4203 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4204 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4205 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4206 4207 from sqlglot.dialects import Dialect 4208 4209 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4210 if self.dialect.__class__ != Dialect: 4211 self.unsupported("ARRAY_ANY is unsupported") 4212 4213 return self.function_fallback_sql(expression) 4214 4215 def struct_sql(self, expression: exp.Struct) -> str: 4216 expression.set( 4217 "expressions", 4218 [ 4219 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4220 if isinstance(e, exp.PropertyEQ) 4221 else e 4222 for e in expression.expressions 4223 ], 4224 ) 4225 4226 return self.function_fallback_sql(expression) 4227 4228 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4229 low = self.sql(expression, "this") 4230 high = self.sql(expression, "expression") 4231 4232 return f"{low} TO {high}" 4233 4234 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4235 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4236 tables = f" {self.expressions(expression)}" 4237 4238 exists = " IF EXISTS" if expression.args.get("exists") else "" 4239 4240 on_cluster = self.sql(expression, "cluster") 4241 on_cluster = f" {on_cluster}" if on_cluster else "" 4242 4243 identity = self.sql(expression, "identity") 4244 identity = f" {identity} IDENTITY" if identity else "" 4245 4246 option = self.sql(expression, "option") 4247 option = f" {option}" if option else "" 4248 4249 partition = self.sql(expression, "partition") 4250 partition = f" {partition}" if partition else "" 4251 4252 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4253 4254 # This transpiles T-SQL's CONVERT function 4255 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4256 def convert_sql(self, expression: exp.Convert) -> str: 4257 to = expression.this 4258 value = expression.expression 4259 style = expression.args.get("style") 4260 safe = expression.args.get("safe") 4261 strict = expression.args.get("strict") 4262 4263 if not to or not value: 4264 return "" 4265 4266 # Retrieve length of datatype and override to default if not specified 4267 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4268 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4269 4270 transformed: t.Optional[exp.Expression] = None 4271 cast = exp.Cast if strict else exp.TryCast 4272 4273 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4274 if isinstance(style, exp.Literal) and style.is_int: 4275 from sqlglot.dialects.tsql import TSQL 4276 4277 style_value = style.name 4278 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4279 if not converted_style: 4280 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4281 4282 fmt = exp.Literal.string(converted_style) 4283 4284 if to.this == exp.DataType.Type.DATE: 4285 transformed = exp.StrToDate(this=value, format=fmt) 4286 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4287 transformed = exp.StrToTime(this=value, format=fmt) 4288 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4289 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4290 elif to.this == exp.DataType.Type.TEXT: 4291 transformed = exp.TimeToStr(this=value, format=fmt) 4292 4293 if not transformed: 4294 transformed = cast(this=value, to=to, safe=safe) 4295 4296 return self.sql(transformed) 4297 4298 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4299 this = expression.this 4300 if isinstance(this, exp.JSONPathWildcard): 4301 this = self.json_path_part(this) 4302 return f".{this}" if this else "" 4303 4304 if exp.SAFE_IDENTIFIER_RE.match(this): 4305 return f".{this}" 4306 4307 this = self.json_path_part(this) 4308 return ( 4309 f"[{this}]" 4310 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4311 else f".{this}" 4312 ) 4313 4314 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4315 this = self.json_path_part(expression.this) 4316 return f"[{this}]" if this else "" 4317 4318 def _simplify_unless_literal(self, expression: E) -> E: 4319 if not isinstance(expression, exp.Literal): 4320 from sqlglot.optimizer.simplify import simplify 4321 4322 expression = simplify(expression, dialect=self.dialect) 4323 4324 return expression 4325 4326 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4327 this = expression.this 4328 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4329 self.unsupported( 4330 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4331 ) 4332 return self.sql(this) 4333 4334 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4335 # The first modifier here will be the one closest to the AggFunc's arg 4336 mods = sorted( 4337 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4338 key=lambda x: 0 4339 if isinstance(x, exp.HavingMax) 4340 else (1 if isinstance(x, exp.Order) else 2), 4341 ) 4342 4343 if mods: 4344 mod = mods[0] 4345 this = expression.__class__(this=mod.this.copy()) 4346 this.meta["inline"] = True 4347 mod.this.replace(this) 4348 return self.sql(expression.this) 4349 4350 agg_func = expression.find(exp.AggFunc) 4351 4352 if agg_func: 4353 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4354 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4355 4356 return f"{self.sql(expression, 'this')} {text}" 4357 4358 def _replace_line_breaks(self, string: str) -> str: 4359 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4360 if self.pretty: 4361 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4362 return string 4363 4364 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4365 option = self.sql(expression, "this") 4366 4367 if expression.expressions: 4368 upper = option.upper() 4369 4370 # Snowflake FILE_FORMAT options are separated by whitespace 4371 sep = " " if upper == "FILE_FORMAT" else ", " 4372 4373 # Databricks copy/format options do not set their list of values with EQ 4374 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4375 values = self.expressions(expression, flat=True, sep=sep) 4376 return f"{option}{op}({values})" 4377 4378 value = self.sql(expression, "expression") 4379 4380 if not value: 4381 return option 4382 4383 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4384 4385 return f"{option}{op}{value}" 4386 4387 def credentials_sql(self, expression: exp.Credentials) -> str: 4388 cred_expr = expression.args.get("credentials") 4389 if isinstance(cred_expr, exp.Literal): 4390 # Redshift case: CREDENTIALS <string> 4391 credentials = self.sql(expression, "credentials") 4392 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4393 else: 4394 # Snowflake case: CREDENTIALS = (...) 4395 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4396 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4397 4398 storage = self.sql(expression, "storage") 4399 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4400 4401 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4402 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4403 4404 iam_role = self.sql(expression, "iam_role") 4405 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4406 4407 region = self.sql(expression, "region") 4408 region = f" REGION {region}" if region else "" 4409 4410 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4411 4412 def copy_sql(self, expression: exp.Copy) -> str: 4413 this = self.sql(expression, "this") 4414 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4415 4416 credentials = self.sql(expression, "credentials") 4417 credentials = self.seg(credentials) if credentials else "" 4418 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4419 files = self.expressions(expression, key="files", flat=True) 4420 4421 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4422 params = self.expressions( 4423 expression, 4424 key="params", 4425 sep=sep, 4426 new_line=True, 4427 skip_last=True, 4428 skip_first=True, 4429 indent=self.COPY_PARAMS_ARE_WRAPPED, 4430 ) 4431 4432 if params: 4433 if self.COPY_PARAMS_ARE_WRAPPED: 4434 params = f" WITH ({params})" 4435 elif not self.pretty: 4436 params = f" {params}" 4437 4438 return f"COPY{this}{kind} {files}{credentials}{params}" 4439 4440 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4441 return "" 4442 4443 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4444 on_sql = "ON" if expression.args.get("on") else "OFF" 4445 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4446 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4447 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4448 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4449 4450 if filter_col or retention_period: 4451 on_sql = self.func("ON", filter_col, retention_period) 4452 4453 return f"DATA_DELETION={on_sql}" 4454 4455 def maskingpolicycolumnconstraint_sql( 4456 self, expression: exp.MaskingPolicyColumnConstraint 4457 ) -> str: 4458 this = self.sql(expression, "this") 4459 expressions = self.expressions(expression, flat=True) 4460 expressions = f" USING ({expressions})" if expressions else "" 4461 return f"MASKING POLICY {this}{expressions}" 4462 4463 def gapfill_sql(self, expression: exp.GapFill) -> str: 4464 this = self.sql(expression, "this") 4465 this = f"TABLE {this}" 4466 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4467 4468 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4469 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4470 4471 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4472 this = self.sql(expression, "this") 4473 expr = expression.expression 4474 4475 if isinstance(expr, exp.Func): 4476 # T-SQL's CLR functions are case sensitive 4477 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4478 else: 4479 expr = self.sql(expression, "expression") 4480 4481 return self.scope_resolution(expr, this) 4482 4483 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4484 if self.PARSE_JSON_NAME is None: 4485 return self.sql(expression.this) 4486 4487 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4488 4489 def rand_sql(self, expression: exp.Rand) -> str: 4490 lower = self.sql(expression, "lower") 4491 upper = self.sql(expression, "upper") 4492 4493 if lower and upper: 4494 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4495 return self.func("RAND", expression.this) 4496 4497 def changes_sql(self, expression: exp.Changes) -> str: 4498 information = self.sql(expression, "information") 4499 information = f"INFORMATION => {information}" 4500 at_before = self.sql(expression, "at_before") 4501 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4502 end = self.sql(expression, "end") 4503 end = f"{self.seg('')}{end}" if end else "" 4504 4505 return f"CHANGES ({information}){at_before}{end}" 4506 4507 def pad_sql(self, expression: exp.Pad) -> str: 4508 prefix = "L" if expression.args.get("is_left") else "R" 4509 4510 fill_pattern = self.sql(expression, "fill_pattern") or None 4511 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4512 fill_pattern = "' '" 4513 4514 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4515 4516 def summarize_sql(self, expression: exp.Summarize) -> str: 4517 table = " TABLE" if expression.args.get("table") else "" 4518 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4519 4520 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4521 generate_series = exp.GenerateSeries(**expression.args) 4522 4523 parent = expression.parent 4524 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4525 parent = parent.parent 4526 4527 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4528 return self.sql(exp.Unnest(expressions=[generate_series])) 4529 4530 if isinstance(parent, exp.Select): 4531 self.unsupported("GenerateSeries projection unnesting is not supported.") 4532 4533 return self.sql(generate_series) 4534 4535 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4536 exprs = expression.expressions 4537 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4538 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4539 else: 4540 rhs = self.expressions(expression) 4541 4542 return self.func(name, expression.this, rhs or None) 4543 4544 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4545 if self.SUPPORTS_CONVERT_TIMEZONE: 4546 return self.function_fallback_sql(expression) 4547 4548 source_tz = expression.args.get("source_tz") 4549 target_tz = expression.args.get("target_tz") 4550 timestamp = expression.args.get("timestamp") 4551 4552 if source_tz and timestamp: 4553 timestamp = exp.AtTimeZone( 4554 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4555 ) 4556 4557 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4558 4559 return self.sql(expr) 4560 4561 def json_sql(self, expression: exp.JSON) -> str: 4562 this = self.sql(expression, "this") 4563 this = f" {this}" if this else "" 4564 4565 _with = expression.args.get("with") 4566 4567 if _with is None: 4568 with_sql = "" 4569 elif not _with: 4570 with_sql = " WITHOUT" 4571 else: 4572 with_sql = " WITH" 4573 4574 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4575 4576 return f"JSON{this}{with_sql}{unique_sql}" 4577 4578 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4579 def _generate_on_options(arg: t.Any) -> str: 4580 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4581 4582 path = self.sql(expression, "path") 4583 returning = self.sql(expression, "returning") 4584 returning = f" RETURNING {returning}" if returning else "" 4585 4586 on_condition = self.sql(expression, "on_condition") 4587 on_condition = f" {on_condition}" if on_condition else "" 4588 4589 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4590 4591 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4592 else_ = "ELSE " if expression.args.get("else_") else "" 4593 condition = self.sql(expression, "expression") 4594 condition = f"WHEN {condition} THEN " if condition else else_ 4595 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4596 return f"{condition}{insert}" 4597 4598 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4599 kind = self.sql(expression, "kind") 4600 expressions = self.seg(self.expressions(expression, sep=" ")) 4601 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4602 return res 4603 4604 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4605 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4606 empty = expression.args.get("empty") 4607 empty = ( 4608 f"DEFAULT {empty} ON EMPTY" 4609 if isinstance(empty, exp.Expression) 4610 else self.sql(expression, "empty") 4611 ) 4612 4613 error = expression.args.get("error") 4614 error = ( 4615 f"DEFAULT {error} ON ERROR" 4616 if isinstance(error, exp.Expression) 4617 else self.sql(expression, "error") 4618 ) 4619 4620 if error and empty: 4621 error = ( 4622 f"{empty} {error}" 4623 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4624 else f"{error} {empty}" 4625 ) 4626 empty = "" 4627 4628 null = self.sql(expression, "null") 4629 4630 return f"{empty}{error}{null}" 4631 4632 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4633 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4634 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4635 4636 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4637 this = self.sql(expression, "this") 4638 path = self.sql(expression, "path") 4639 4640 passing = self.expressions(expression, "passing") 4641 passing = f" PASSING {passing}" if passing else "" 4642 4643 on_condition = self.sql(expression, "on_condition") 4644 on_condition = f" {on_condition}" if on_condition else "" 4645 4646 path = f"{path}{passing}{on_condition}" 4647 4648 return self.func("JSON_EXISTS", this, path) 4649 4650 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4651 array_agg = self.function_fallback_sql(expression) 4652 4653 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4654 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4655 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4656 parent = expression.parent 4657 if isinstance(parent, exp.Filter): 4658 parent_cond = parent.expression.this 4659 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4660 else: 4661 this = expression.this 4662 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4663 if this.find(exp.Column): 4664 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4665 this_sql = ( 4666 self.expressions(this) 4667 if isinstance(this, exp.Distinct) 4668 else self.sql(expression, "this") 4669 ) 4670 4671 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4672 4673 return array_agg 4674 4675 def apply_sql(self, expression: exp.Apply) -> str: 4676 this = self.sql(expression, "this") 4677 expr = self.sql(expression, "expression") 4678 4679 return f"{this} APPLY({expr})" 4680 4681 def grant_sql(self, expression: exp.Grant) -> str: 4682 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4683 4684 kind = self.sql(expression, "kind") 4685 kind = f" {kind}" if kind else "" 4686 4687 securable = self.sql(expression, "securable") 4688 securable = f" {securable}" if securable else "" 4689 4690 principals = self.expressions(expression, key="principals", flat=True) 4691 4692 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4693 4694 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4695 4696 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4697 this = self.sql(expression, "this") 4698 columns = self.expressions(expression, flat=True) 4699 columns = f"({columns})" if columns else "" 4700 4701 return f"{this}{columns}" 4702 4703 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4704 this = self.sql(expression, "this") 4705 4706 kind = self.sql(expression, "kind") 4707 kind = f"{kind} " if kind else "" 4708 4709 return f"{kind}{this}" 4710 4711 def columns_sql(self, expression: exp.Columns): 4712 func = self.function_fallback_sql(expression) 4713 if expression.args.get("unpack"): 4714 func = f"*{func}" 4715 4716 return func 4717 4718 def overlay_sql(self, expression: exp.Overlay): 4719 this = self.sql(expression, "this") 4720 expr = self.sql(expression, "expression") 4721 from_sql = self.sql(expression, "from") 4722 for_sql = self.sql(expression, "for") 4723 for_sql = f" FOR {for_sql}" if for_sql else "" 4724 4725 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4726 4727 @unsupported_args("format") 4728 def todouble_sql(self, expression: exp.ToDouble) -> str: 4729 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4730 4731 def string_sql(self, expression: exp.String) -> str: 4732 this = expression.this 4733 zone = expression.args.get("zone") 4734 4735 if zone: 4736 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4737 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4738 # set for source_tz to transpile the time conversion before the STRING cast 4739 this = exp.ConvertTimezone( 4740 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4741 ) 4742 4743 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4744 4745 def median_sql(self, expression: exp.Median): 4746 if not self.SUPPORTS_MEDIAN: 4747 return self.sql( 4748 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4749 ) 4750 4751 return self.function_fallback_sql(expression) 4752 4753 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4754 filler = self.sql(expression, "this") 4755 filler = f" {filler}" if filler else "" 4756 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4757 return f"TRUNCATE{filler} {with_count}" 4758 4759 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4760 if self.SUPPORTS_UNIX_SECONDS: 4761 return self.function_fallback_sql(expression) 4762 4763 start_ts = exp.cast( 4764 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4765 ) 4766 4767 return self.sql( 4768 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4769 ) 4770 4771 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4772 dim = expression.expression 4773 4774 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4775 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4776 if not (dim.is_int and dim.name == "1"): 4777 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4778 dim = None 4779 4780 # If dimension is required but not specified, default initialize it 4781 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4782 dim = exp.Literal.number(1) 4783 4784 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4785 4786 def attach_sql(self, expression: exp.Attach) -> str: 4787 this = self.sql(expression, "this") 4788 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4789 expressions = self.expressions(expression) 4790 expressions = f" ({expressions})" if expressions else "" 4791 4792 return f"ATTACH{exists_sql} {this}{expressions}" 4793 4794 def detach_sql(self, expression: exp.Detach) -> str: 4795 this = self.sql(expression, "this") 4796 # the DATABASE keyword is required if IF EXISTS is set 4797 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4798 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4799 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4800 4801 return f"DETACH{exists_sql} {this}" 4802 4803 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4804 this = self.sql(expression, "this") 4805 value = self.sql(expression, "expression") 4806 value = f" {value}" if value else "" 4807 return f"{this}{value}" 4808 4809 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4810 this_sql = self.sql(expression, "this") 4811 if isinstance(expression.this, exp.Table): 4812 this_sql = f"TABLE {this_sql}" 4813 4814 return self.func( 4815 "FEATURES_AT_TIME", 4816 this_sql, 4817 expression.args.get("time"), 4818 expression.args.get("num_rows"), 4819 expression.args.get("ignore_feature_nulls"), 4820 ) 4821 4822 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4823 return ( 4824 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4825 ) 4826 4827 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4828 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4829 encode = f"{encode} {self.sql(expression, 'this')}" 4830 4831 properties = expression.args.get("properties") 4832 if properties: 4833 encode = f"{encode} {self.properties(properties)}" 4834 4835 return encode 4836 4837 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4838 this = self.sql(expression, "this") 4839 include = f"INCLUDE {this}" 4840 4841 column_def = self.sql(expression, "column_def") 4842 if column_def: 4843 include = f"{include} {column_def}" 4844 4845 alias = self.sql(expression, "alias") 4846 if alias: 4847 include = f"{include} AS {alias}" 4848 4849 return include 4850 4851 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4852 name = f"NAME {self.sql(expression, 'this')}" 4853 return self.func("XMLELEMENT", name, *expression.expressions) 4854 4855 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 4856 this = self.sql(expression, "this") 4857 expr = self.sql(expression, "expression") 4858 expr = f"({expr})" if expr else "" 4859 return f"{this}{expr}" 4860 4861 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4862 partitions = self.expressions(expression, "partition_expressions") 4863 create = self.expressions(expression, "create_expressions") 4864 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4865 4866 def partitionbyrangepropertydynamic_sql( 4867 self, expression: exp.PartitionByRangePropertyDynamic 4868 ) -> str: 4869 start = self.sql(expression, "start") 4870 end = self.sql(expression, "end") 4871 4872 every = expression.args["every"] 4873 if isinstance(every, exp.Interval) and every.this.is_string: 4874 every.this.replace(exp.Literal.number(every.name)) 4875 4876 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4877 4878 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4879 name = self.sql(expression, "this") 4880 values = self.expressions(expression, flat=True) 4881 4882 return f"NAME {name} VALUE {values}" 4883 4884 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4885 kind = self.sql(expression, "kind") 4886 sample = self.sql(expression, "sample") 4887 return f"SAMPLE {sample} {kind}" 4888 4889 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4890 kind = self.sql(expression, "kind") 4891 option = self.sql(expression, "option") 4892 option = f" {option}" if option else "" 4893 this = self.sql(expression, "this") 4894 this = f" {this}" if this else "" 4895 columns = self.expressions(expression) 4896 columns = f" {columns}" if columns else "" 4897 return f"{kind}{option} STATISTICS{this}{columns}" 4898 4899 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4900 this = self.sql(expression, "this") 4901 columns = self.expressions(expression) 4902 inner_expression = self.sql(expression, "expression") 4903 inner_expression = f" {inner_expression}" if inner_expression else "" 4904 update_options = self.sql(expression, "update_options") 4905 update_options = f" {update_options} UPDATE" if update_options else "" 4906 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4907 4908 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4909 kind = self.sql(expression, "kind") 4910 kind = f" {kind}" if kind else "" 4911 return f"DELETE{kind} STATISTICS" 4912 4913 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4914 inner_expression = self.sql(expression, "expression") 4915 return f"LIST CHAINED ROWS{inner_expression}" 4916 4917 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4918 kind = self.sql(expression, "kind") 4919 this = self.sql(expression, "this") 4920 this = f" {this}" if this else "" 4921 inner_expression = self.sql(expression, "expression") 4922 return f"VALIDATE {kind}{this}{inner_expression}" 4923 4924 def analyze_sql(self, expression: exp.Analyze) -> str: 4925 options = self.expressions(expression, key="options", sep=" ") 4926 options = f" {options}" if options else "" 4927 kind = self.sql(expression, "kind") 4928 kind = f" {kind}" if kind else "" 4929 this = self.sql(expression, "this") 4930 this = f" {this}" if this else "" 4931 mode = self.sql(expression, "mode") 4932 mode = f" {mode}" if mode else "" 4933 properties = self.sql(expression, "properties") 4934 properties = f" {properties}" if properties else "" 4935 partition = self.sql(expression, "partition") 4936 partition = f" {partition}" if partition else "" 4937 inner_expression = self.sql(expression, "expression") 4938 inner_expression = f" {inner_expression}" if inner_expression else "" 4939 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 4940 4941 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4942 this = self.sql(expression, "this") 4943 namespaces = self.expressions(expression, key="namespaces") 4944 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4945 passing = self.expressions(expression, key="passing") 4946 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4947 columns = self.expressions(expression, key="columns") 4948 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4949 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4950 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 4951 4952 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 4953 this = self.sql(expression, "this") 4954 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 4955 4956 def export_sql(self, expression: exp.Export) -> str: 4957 this = self.sql(expression, "this") 4958 connection = self.sql(expression, "connection") 4959 connection = f"WITH CONNECTION {connection} " if connection else "" 4960 options = self.sql(expression, "options") 4961 return f"EXPORT DATA {connection}{options} AS {this}" 4962 4963 def declare_sql(self, expression: exp.Declare) -> str: 4964 return f"DECLARE {self.expressions(expression, flat=True)}" 4965 4966 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4967 variable = self.sql(expression, "this") 4968 default = self.sql(expression, "default") 4969 default = f" = {default}" if default else "" 4970 4971 kind = self.sql(expression, "kind") 4972 if isinstance(expression.args.get("kind"), exp.Schema): 4973 kind = f"TABLE {kind}" 4974 4975 return f"{variable} AS {kind}{default}" 4976 4977 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 4978 kind = self.sql(expression, "kind") 4979 this = self.sql(expression, "this") 4980 set = self.sql(expression, "expression") 4981 using = self.sql(expression, "using") 4982 using = f" USING {using}" if using else "" 4983 4984 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 4985 4986 return f"{kind_sql} {this} SET {set}{using}" 4987 4988 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 4989 params = self.expressions(expression, key="params", flat=True) 4990 return self.func(expression.name, *expression.expressions) + f"({params})" 4991 4992 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 4993 return self.func(expression.name, *expression.expressions) 4994 4995 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 4996 return self.anonymousaggfunc_sql(expression) 4997 4998 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 4999 return self.parameterizedagg_sql(expression) 5000 5001 def show_sql(self, expression: exp.Show) -> str: 5002 self.unsupported("Unsupported SHOW statement") 5003 return "" 5004 5005 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5006 # Snowflake GET/PUT statements: 5007 # PUT <file> <internalStage> <properties> 5008 # GET <internalStage> <file> <properties> 5009 props = expression.args.get("properties") 5010 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5011 this = self.sql(expression, "this") 5012 target = self.sql(expression, "target") 5013 5014 if isinstance(expression, exp.Put): 5015 return f"PUT {this} {target}{props_sql}" 5016 else: 5017 return f"GET {target} {this}{props_sql}" 5018 5019 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5020 this = self.sql(expression, "this") 5021 expr = self.sql(expression, "expression") 5022 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5023 return f"TRANSLATE({this} USING {expr}{with_error})" 5024 5025 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5026 if self.SUPPORTS_DECODE_CASE: 5027 return self.func("DECODE", *expression.expressions) 5028 5029 expression, *expressions = expression.expressions 5030 5031 ifs = [] 5032 for search, result in zip(expressions[::2], expressions[1::2]): 5033 if isinstance(search, exp.Literal): 5034 ifs.append(exp.If(this=expression.eq(search), true=result)) 5035 elif isinstance(search, exp.Null): 5036 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5037 else: 5038 if isinstance(search, exp.Binary): 5039 search = exp.paren(search) 5040 5041 cond = exp.or_( 5042 expression.eq(search), 5043 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5044 copy=False, 5045 ) 5046 ifs.append(exp.If(this=cond, true=result)) 5047 5048 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5049 return self.sql(case)
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.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.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 223 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 224 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 225 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 226 exp.ForceProperty: lambda *_: "FORCE", 227 } 228 229 # Whether null ordering is supported in order by 230 # True: Full Support, None: No support, False: No support for certain cases 231 # such as window specifications, aggregate functions etc 232 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 233 234 # Whether ignore nulls is inside the agg or outside. 235 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 236 IGNORE_NULLS_IN_FUNC = False 237 238 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 239 LOCKING_READS_SUPPORTED = False 240 241 # Whether the EXCEPT and INTERSECT operations can return duplicates 242 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 243 244 # Wrap derived values in parens, usually standard but spark doesn't support it 245 WRAP_DERIVED_VALUES = True 246 247 # Whether create function uses an AS before the RETURN 248 CREATE_FUNCTION_RETURN_AS = True 249 250 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 251 MATCHED_BY_SOURCE = True 252 253 # Whether the INTERVAL expression works only with values like '1 day' 254 SINGLE_STRING_INTERVAL = False 255 256 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 257 INTERVAL_ALLOWS_PLURAL_FORM = True 258 259 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 260 LIMIT_FETCH = "ALL" 261 262 # Whether limit and fetch allows expresions or just limits 263 LIMIT_ONLY_LITERALS = False 264 265 # Whether a table is allowed to be renamed with a db 266 RENAME_TABLE_WITH_DB = True 267 268 # The separator for grouping sets and rollups 269 GROUPINGS_SEP = "," 270 271 # The string used for creating an index on a table 272 INDEX_ON = "ON" 273 274 # Whether join hints should be generated 275 JOIN_HINTS = True 276 277 # Whether table hints should be generated 278 TABLE_HINTS = True 279 280 # Whether query hints should be generated 281 QUERY_HINTS = True 282 283 # What kind of separator to use for query hints 284 QUERY_HINT_SEP = ", " 285 286 # Whether comparing against booleans (e.g. x IS TRUE) is supported 287 IS_BOOL_ALLOWED = True 288 289 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 290 DUPLICATE_KEY_UPDATE_WITH_SET = True 291 292 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 293 LIMIT_IS_TOP = False 294 295 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 296 RETURNING_END = True 297 298 # Whether to generate an unquoted value for EXTRACT's date part argument 299 EXTRACT_ALLOWS_QUOTES = True 300 301 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 302 TZ_TO_WITH_TIME_ZONE = False 303 304 # Whether the NVL2 function is supported 305 NVL2_SUPPORTED = True 306 307 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 308 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 309 310 # Whether VALUES statements can be used as derived tables. 311 # MySQL 5 and Redshift do not allow this, so when False, it will convert 312 # SELECT * VALUES into SELECT UNION 313 VALUES_AS_TABLE = True 314 315 # Whether the word COLUMN is included when adding a column with ALTER TABLE 316 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 317 318 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 319 UNNEST_WITH_ORDINALITY = True 320 321 # Whether FILTER (WHERE cond) can be used for conditional aggregation 322 AGGREGATE_FILTER_SUPPORTED = True 323 324 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 325 SEMI_ANTI_JOIN_WITH_SIDE = True 326 327 # Whether to include the type of a computed column in the CREATE DDL 328 COMPUTED_COLUMN_WITH_TYPE = True 329 330 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 331 SUPPORTS_TABLE_COPY = True 332 333 # Whether parentheses are required around the table sample's expression 334 TABLESAMPLE_REQUIRES_PARENS = True 335 336 # Whether a table sample clause's size needs to be followed by the ROWS keyword 337 TABLESAMPLE_SIZE_IS_ROWS = True 338 339 # The keyword(s) to use when generating a sample clause 340 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 341 342 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 343 TABLESAMPLE_WITH_METHOD = True 344 345 # The keyword to use when specifying the seed of a sample clause 346 TABLESAMPLE_SEED_KEYWORD = "SEED" 347 348 # Whether COLLATE is a function instead of a binary operator 349 COLLATE_IS_FUNC = False 350 351 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 352 DATA_TYPE_SPECIFIERS_ALLOWED = False 353 354 # Whether conditions require booleans WHERE x = 0 vs WHERE x 355 ENSURE_BOOLS = False 356 357 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 358 CTE_RECURSIVE_KEYWORD_REQUIRED = True 359 360 # Whether CONCAT requires >1 arguments 361 SUPPORTS_SINGLE_ARG_CONCAT = True 362 363 # Whether LAST_DAY function supports a date part argument 364 LAST_DAY_SUPPORTS_DATE_PART = True 365 366 # Whether named columns are allowed in table aliases 367 SUPPORTS_TABLE_ALIAS_COLUMNS = True 368 369 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 370 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 371 372 # What delimiter to use for separating JSON key/value pairs 373 JSON_KEY_VALUE_PAIR_SEP = ":" 374 375 # INSERT OVERWRITE TABLE x override 376 INSERT_OVERWRITE = " OVERWRITE TABLE" 377 378 # Whether the SELECT .. INTO syntax is used instead of CTAS 379 SUPPORTS_SELECT_INTO = False 380 381 # Whether UNLOGGED tables can be created 382 SUPPORTS_UNLOGGED_TABLES = False 383 384 # Whether the CREATE TABLE LIKE statement is supported 385 SUPPORTS_CREATE_TABLE_LIKE = True 386 387 # Whether the LikeProperty needs to be specified inside of the schema clause 388 LIKE_PROPERTY_INSIDE_SCHEMA = False 389 390 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 391 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 392 MULTI_ARG_DISTINCT = True 393 394 # Whether the JSON extraction operators expect a value of type JSON 395 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 396 397 # Whether bracketed keys like ["foo"] are supported in JSON paths 398 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 399 400 # Whether to escape keys using single quotes in JSON paths 401 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 402 403 # The JSONPathPart expressions supported by this dialect 404 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 405 406 # Whether any(f(x) for x in array) can be implemented by this dialect 407 CAN_IMPLEMENT_ARRAY_ANY = False 408 409 # Whether the function TO_NUMBER is supported 410 SUPPORTS_TO_NUMBER = True 411 412 # Whether EXCLUDE in window specification is supported 413 SUPPORTS_WINDOW_EXCLUDE = False 414 415 # Whether or not set op modifiers apply to the outer set op or select. 416 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 417 # True means limit 1 happens after the set op, False means it it happens on y. 418 SET_OP_MODIFIERS = True 419 420 # Whether parameters from COPY statement are wrapped in parentheses 421 COPY_PARAMS_ARE_WRAPPED = True 422 423 # Whether values of params are set with "=" token or empty space 424 COPY_PARAMS_EQ_REQUIRED = False 425 426 # Whether COPY statement has INTO keyword 427 COPY_HAS_INTO_KEYWORD = True 428 429 # Whether the conditional TRY(expression) function is supported 430 TRY_SUPPORTED = True 431 432 # Whether the UESCAPE syntax in unicode strings is supported 433 SUPPORTS_UESCAPE = True 434 435 # The keyword to use when generating a star projection with excluded columns 436 STAR_EXCEPT = "EXCEPT" 437 438 # The HEX function name 439 HEX_FUNC = "HEX" 440 441 # The keywords to use when prefixing & separating WITH based properties 442 WITH_PROPERTIES_PREFIX = "WITH" 443 444 # Whether to quote the generated expression of exp.JsonPath 445 QUOTE_JSON_PATH = True 446 447 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 448 PAD_FILL_PATTERN_IS_REQUIRED = False 449 450 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 451 SUPPORTS_EXPLODING_PROJECTIONS = True 452 453 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 454 ARRAY_CONCAT_IS_VAR_LEN = True 455 456 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 457 SUPPORTS_CONVERT_TIMEZONE = False 458 459 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 460 SUPPORTS_MEDIAN = True 461 462 # Whether UNIX_SECONDS(timestamp) is supported 463 SUPPORTS_UNIX_SECONDS = False 464 465 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 466 ALTER_SET_WRAPPED = False 467 468 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 469 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 470 # TODO: The normalization should be done by default once we've tested it across all dialects. 471 NORMALIZE_EXTRACT_DATE_PARTS = False 472 473 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 474 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 475 476 # The function name of the exp.ArraySize expression 477 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 478 479 # The syntax to use when altering the type of a column 480 ALTER_SET_TYPE = "SET DATA TYPE" 481 482 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 483 # None -> Doesn't support it at all 484 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 485 # True (Postgres) -> Explicitly requires it 486 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 487 488 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 489 SUPPORTS_DECODE_CASE = True 490 491 TYPE_MAPPING = { 492 exp.DataType.Type.DATETIME2: "TIMESTAMP", 493 exp.DataType.Type.NCHAR: "CHAR", 494 exp.DataType.Type.NVARCHAR: "VARCHAR", 495 exp.DataType.Type.MEDIUMTEXT: "TEXT", 496 exp.DataType.Type.LONGTEXT: "TEXT", 497 exp.DataType.Type.TINYTEXT: "TEXT", 498 exp.DataType.Type.BLOB: "VARBINARY", 499 exp.DataType.Type.MEDIUMBLOB: "BLOB", 500 exp.DataType.Type.LONGBLOB: "BLOB", 501 exp.DataType.Type.TINYBLOB: "BLOB", 502 exp.DataType.Type.INET: "INET", 503 exp.DataType.Type.ROWVERSION: "VARBINARY", 504 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 505 } 506 507 TIME_PART_SINGULARS = { 508 "MICROSECONDS": "MICROSECOND", 509 "SECONDS": "SECOND", 510 "MINUTES": "MINUTE", 511 "HOURS": "HOUR", 512 "DAYS": "DAY", 513 "WEEKS": "WEEK", 514 "MONTHS": "MONTH", 515 "QUARTERS": "QUARTER", 516 "YEARS": "YEAR", 517 } 518 519 AFTER_HAVING_MODIFIER_TRANSFORMS = { 520 "cluster": lambda self, e: self.sql(e, "cluster"), 521 "distribute": lambda self, e: self.sql(e, "distribute"), 522 "sort": lambda self, e: self.sql(e, "sort"), 523 "windows": lambda self, e: ( 524 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 525 if e.args.get("windows") 526 else "" 527 ), 528 "qualify": lambda self, e: self.sql(e, "qualify"), 529 } 530 531 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 532 533 STRUCT_DELIMITER = ("<", ">") 534 535 PARAMETER_TOKEN = "@" 536 NAMED_PLACEHOLDER_TOKEN = ":" 537 538 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 539 540 PROPERTIES_LOCATION = { 541 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 542 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 543 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 544 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 545 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 546 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 547 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 548 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 549 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 550 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 551 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 552 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 556 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 558 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 559 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 560 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 561 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 562 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 565 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 569 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 570 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 571 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 572 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 573 exp.HeapProperty: exp.Properties.Location.POST_WITH, 574 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 575 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 576 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 579 exp.JournalProperty: exp.Properties.Location.POST_NAME, 580 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 585 exp.LogProperty: exp.Properties.Location.POST_NAME, 586 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 587 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 588 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 589 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 591 exp.Order: exp.Properties.Location.POST_SCHEMA, 592 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 593 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 594 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 595 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 596 exp.Property: exp.Properties.Location.POST_WITH, 597 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 600 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 605 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 607 exp.Set: exp.Properties.Location.POST_SCHEMA, 608 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.SetProperty: exp.Properties.Location.POST_CREATE, 610 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 611 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 612 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 613 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 616 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 619 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 620 exp.Tags: exp.Properties.Location.POST_WITH, 621 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 622 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 624 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 625 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 626 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 627 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 630 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 631 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 632 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 633 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 634 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 635 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 636 } 637 638 # Keywords that can't be used as unquoted identifier names 639 RESERVED_KEYWORDS: t.Set[str] = set() 640 641 # Expressions whose comments are separated from them for better formatting 642 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 643 exp.Command, 644 exp.Create, 645 exp.Describe, 646 exp.Delete, 647 exp.Drop, 648 exp.From, 649 exp.Insert, 650 exp.Join, 651 exp.MultitableInserts, 652 exp.Order, 653 exp.Group, 654 exp.Having, 655 exp.Select, 656 exp.SetOperation, 657 exp.Update, 658 exp.Where, 659 exp.With, 660 ) 661 662 # Expressions that should not have their comments generated in maybe_comment 663 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 664 exp.Binary, 665 exp.SetOperation, 666 ) 667 668 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 669 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 670 exp.Column, 671 exp.Literal, 672 exp.Neg, 673 exp.Paren, 674 ) 675 676 PARAMETERIZABLE_TEXT_TYPES = { 677 exp.DataType.Type.NVARCHAR, 678 exp.DataType.Type.VARCHAR, 679 exp.DataType.Type.CHAR, 680 exp.DataType.Type.NCHAR, 681 } 682 683 # Expressions that need to have all CTEs under them bubbled up to them 684 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 685 686 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 687 688 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 689 690 __slots__ = ( 691 "pretty", 692 "identify", 693 "normalize", 694 "pad", 695 "_indent", 696 "normalize_functions", 697 "unsupported_level", 698 "max_unsupported", 699 "leading_comma", 700 "max_text_width", 701 "comments", 702 "dialect", 703 "unsupported_messages", 704 "_escaped_quote_end", 705 "_escaped_identifier_end", 706 "_next_name", 707 "_identifier_start", 708 "_identifier_end", 709 "_quote_json_path_key_using_brackets", 710 ) 711 712 def __init__( 713 self, 714 pretty: t.Optional[bool] = None, 715 identify: str | bool = False, 716 normalize: bool = False, 717 pad: int = 2, 718 indent: int = 2, 719 normalize_functions: t.Optional[str | bool] = None, 720 unsupported_level: ErrorLevel = ErrorLevel.WARN, 721 max_unsupported: int = 3, 722 leading_comma: bool = False, 723 max_text_width: int = 80, 724 comments: bool = True, 725 dialect: DialectType = None, 726 ): 727 import sqlglot 728 from sqlglot.dialects import Dialect 729 730 self.pretty = pretty if pretty is not None else sqlglot.pretty 731 self.identify = identify 732 self.normalize = normalize 733 self.pad = pad 734 self._indent = indent 735 self.unsupported_level = unsupported_level 736 self.max_unsupported = max_unsupported 737 self.leading_comma = leading_comma 738 self.max_text_width = max_text_width 739 self.comments = comments 740 self.dialect = Dialect.get_or_raise(dialect) 741 742 # This is both a Dialect property and a Generator argument, so we prioritize the latter 743 self.normalize_functions = ( 744 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 745 ) 746 747 self.unsupported_messages: t.List[str] = [] 748 self._escaped_quote_end: str = ( 749 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 750 ) 751 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 752 753 self._next_name = name_sequence("_t") 754 755 self._identifier_start = self.dialect.IDENTIFIER_START 756 self._identifier_end = self.dialect.IDENTIFIER_END 757 758 self._quote_json_path_key_using_brackets = True 759 760 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 761 """ 762 Generates the SQL string corresponding to the given syntax tree. 763 764 Args: 765 expression: The syntax tree. 766 copy: Whether to copy the expression. The generator performs mutations so 767 it is safer to copy. 768 769 Returns: 770 The SQL string corresponding to `expression`. 771 """ 772 if copy: 773 expression = expression.copy() 774 775 expression = self.preprocess(expression) 776 777 self.unsupported_messages = [] 778 sql = self.sql(expression).strip() 779 780 if self.pretty: 781 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 782 783 if self.unsupported_level == ErrorLevel.IGNORE: 784 return sql 785 786 if self.unsupported_level == ErrorLevel.WARN: 787 for msg in self.unsupported_messages: 788 logger.warning(msg) 789 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 790 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 791 792 return sql 793 794 def preprocess(self, expression: exp.Expression) -> exp.Expression: 795 """Apply generic preprocessing transformations to a given expression.""" 796 expression = self._move_ctes_to_top_level(expression) 797 798 if self.ENSURE_BOOLS: 799 from sqlglot.transforms import ensure_bools 800 801 expression = ensure_bools(expression) 802 803 return expression 804 805 def _move_ctes_to_top_level(self, expression: E) -> E: 806 if ( 807 not expression.parent 808 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 809 and any(node.parent is not expression for node in expression.find_all(exp.With)) 810 ): 811 from sqlglot.transforms import move_ctes_to_top_level 812 813 expression = move_ctes_to_top_level(expression) 814 return expression 815 816 def unsupported(self, message: str) -> None: 817 if self.unsupported_level == ErrorLevel.IMMEDIATE: 818 raise UnsupportedError(message) 819 self.unsupported_messages.append(message) 820 821 def sep(self, sep: str = " ") -> str: 822 return f"{sep.strip()}\n" if self.pretty else sep 823 824 def seg(self, sql: str, sep: str = " ") -> str: 825 return f"{self.sep(sep)}{sql}" 826 827 def sanitize_comment(self, comment: str) -> str: 828 comment = " " + comment if comment[0].strip() else comment 829 comment = comment + " " if comment[-1].strip() else comment 830 831 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 832 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 833 comment = comment.replace("*/", "* /") 834 835 return comment 836 837 def maybe_comment( 838 self, 839 sql: str, 840 expression: t.Optional[exp.Expression] = None, 841 comments: t.Optional[t.List[str]] = None, 842 separated: bool = False, 843 ) -> str: 844 comments = ( 845 ((expression and expression.comments) if comments is None else comments) # type: ignore 846 if self.comments 847 else None 848 ) 849 850 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 851 return sql 852 853 comments_sql = " ".join( 854 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 855 ) 856 857 if not comments_sql: 858 return sql 859 860 comments_sql = self._replace_line_breaks(comments_sql) 861 862 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 863 return ( 864 f"{self.sep()}{comments_sql}{sql}" 865 if not sql or sql[0].isspace() 866 else f"{comments_sql}{self.sep()}{sql}" 867 ) 868 869 return f"{sql} {comments_sql}" 870 871 def wrap(self, expression: exp.Expression | str) -> str: 872 this_sql = ( 873 self.sql(expression) 874 if isinstance(expression, exp.UNWRAPPED_QUERIES) 875 else self.sql(expression, "this") 876 ) 877 if not this_sql: 878 return "()" 879 880 this_sql = self.indent(this_sql, level=1, pad=0) 881 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 882 883 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 884 original = self.identify 885 self.identify = False 886 result = func(*args, **kwargs) 887 self.identify = original 888 return result 889 890 def normalize_func(self, name: str) -> str: 891 if self.normalize_functions == "upper" or self.normalize_functions is True: 892 return name.upper() 893 if self.normalize_functions == "lower": 894 return name.lower() 895 return name 896 897 def indent( 898 self, 899 sql: str, 900 level: int = 0, 901 pad: t.Optional[int] = None, 902 skip_first: bool = False, 903 skip_last: bool = False, 904 ) -> str: 905 if not self.pretty or not sql: 906 return sql 907 908 pad = self.pad if pad is None else pad 909 lines = sql.split("\n") 910 911 return "\n".join( 912 ( 913 line 914 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 915 else f"{' ' * (level * self._indent + pad)}{line}" 916 ) 917 for i, line in enumerate(lines) 918 ) 919 920 def sql( 921 self, 922 expression: t.Optional[str | exp.Expression], 923 key: t.Optional[str] = None, 924 comment: bool = True, 925 ) -> str: 926 if not expression: 927 return "" 928 929 if isinstance(expression, str): 930 return expression 931 932 if key: 933 value = expression.args.get(key) 934 if value: 935 return self.sql(value) 936 return "" 937 938 transform = self.TRANSFORMS.get(expression.__class__) 939 940 if callable(transform): 941 sql = transform(self, expression) 942 elif isinstance(expression, exp.Expression): 943 exp_handler_name = f"{expression.key}_sql" 944 945 if hasattr(self, exp_handler_name): 946 sql = getattr(self, exp_handler_name)(expression) 947 elif isinstance(expression, exp.Func): 948 sql = self.function_fallback_sql(expression) 949 elif isinstance(expression, exp.Property): 950 sql = self.property_sql(expression) 951 else: 952 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 953 else: 954 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 955 956 return self.maybe_comment(sql, expression) if self.comments and comment else sql 957 958 def uncache_sql(self, expression: exp.Uncache) -> str: 959 table = self.sql(expression, "this") 960 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 961 return f"UNCACHE TABLE{exists_sql} {table}" 962 963 def cache_sql(self, expression: exp.Cache) -> str: 964 lazy = " LAZY" if expression.args.get("lazy") else "" 965 table = self.sql(expression, "this") 966 options = expression.args.get("options") 967 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 968 sql = self.sql(expression, "expression") 969 sql = f" AS{self.sep()}{sql}" if sql else "" 970 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 971 return self.prepend_ctes(expression, sql) 972 973 def characterset_sql(self, expression: exp.CharacterSet) -> str: 974 if isinstance(expression.parent, exp.Cast): 975 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 976 default = "DEFAULT " if expression.args.get("default") else "" 977 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 978 979 def column_parts(self, expression: exp.Column) -> str: 980 return ".".join( 981 self.sql(part) 982 for part in ( 983 expression.args.get("catalog"), 984 expression.args.get("db"), 985 expression.args.get("table"), 986 expression.args.get("this"), 987 ) 988 if part 989 ) 990 991 def column_sql(self, expression: exp.Column) -> str: 992 join_mark = " (+)" if expression.args.get("join_mark") else "" 993 994 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 995 join_mark = "" 996 self.unsupported("Outer join syntax using the (+) operator is not supported.") 997 998 return f"{self.column_parts(expression)}{join_mark}" 999 1000 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1001 this = self.sql(expression, "this") 1002 this = f" {this}" if this else "" 1003 position = self.sql(expression, "position") 1004 return f"{position}{this}" 1005 1006 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1007 column = self.sql(expression, "this") 1008 kind = self.sql(expression, "kind") 1009 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1010 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1011 kind = f"{sep}{kind}" if kind else "" 1012 constraints = f" {constraints}" if constraints else "" 1013 position = self.sql(expression, "position") 1014 position = f" {position}" if position else "" 1015 1016 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1017 kind = "" 1018 1019 return f"{exists}{column}{kind}{constraints}{position}" 1020 1021 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1022 this = self.sql(expression, "this") 1023 kind_sql = self.sql(expression, "kind").strip() 1024 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1025 1026 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1027 this = self.sql(expression, "this") 1028 if expression.args.get("not_null"): 1029 persisted = " PERSISTED NOT NULL" 1030 elif expression.args.get("persisted"): 1031 persisted = " PERSISTED" 1032 else: 1033 persisted = "" 1034 1035 return f"AS {this}{persisted}" 1036 1037 def autoincrementcolumnconstraint_sql(self, _) -> str: 1038 return self.token_sql(TokenType.AUTO_INCREMENT) 1039 1040 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1041 if isinstance(expression.this, list): 1042 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1043 else: 1044 this = self.sql(expression, "this") 1045 1046 return f"COMPRESS {this}" 1047 1048 def generatedasidentitycolumnconstraint_sql( 1049 self, expression: exp.GeneratedAsIdentityColumnConstraint 1050 ) -> str: 1051 this = "" 1052 if expression.this is not None: 1053 on_null = " ON NULL" if expression.args.get("on_null") else "" 1054 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1055 1056 start = expression.args.get("start") 1057 start = f"START WITH {start}" if start else "" 1058 increment = expression.args.get("increment") 1059 increment = f" INCREMENT BY {increment}" if increment else "" 1060 minvalue = expression.args.get("minvalue") 1061 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1062 maxvalue = expression.args.get("maxvalue") 1063 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1064 cycle = expression.args.get("cycle") 1065 cycle_sql = "" 1066 1067 if cycle is not None: 1068 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1069 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1070 1071 sequence_opts = "" 1072 if start or increment or cycle_sql: 1073 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1074 sequence_opts = f" ({sequence_opts.strip()})" 1075 1076 expr = self.sql(expression, "expression") 1077 expr = f"({expr})" if expr else "IDENTITY" 1078 1079 return f"GENERATED{this} AS {expr}{sequence_opts}" 1080 1081 def generatedasrowcolumnconstraint_sql( 1082 self, expression: exp.GeneratedAsRowColumnConstraint 1083 ) -> str: 1084 start = "START" if expression.args.get("start") else "END" 1085 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1086 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1087 1088 def periodforsystemtimeconstraint_sql( 1089 self, expression: exp.PeriodForSystemTimeConstraint 1090 ) -> str: 1091 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1092 1093 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1094 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1095 1096 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1097 desc = expression.args.get("desc") 1098 if desc is not None: 1099 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1100 options = self.expressions(expression, key="options", flat=True, sep=" ") 1101 options = f" {options}" if options else "" 1102 return f"PRIMARY KEY{options}" 1103 1104 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1105 this = self.sql(expression, "this") 1106 this = f" {this}" if this else "" 1107 index_type = expression.args.get("index_type") 1108 index_type = f" USING {index_type}" if index_type else "" 1109 on_conflict = self.sql(expression, "on_conflict") 1110 on_conflict = f" {on_conflict}" if on_conflict else "" 1111 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1112 options = self.expressions(expression, key="options", flat=True, sep=" ") 1113 options = f" {options}" if options else "" 1114 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1115 1116 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1117 return self.sql(expression, "this") 1118 1119 def create_sql(self, expression: exp.Create) -> str: 1120 kind = self.sql(expression, "kind") 1121 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1122 properties = expression.args.get("properties") 1123 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1124 1125 this = self.createable_sql(expression, properties_locs) 1126 1127 properties_sql = "" 1128 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1129 exp.Properties.Location.POST_WITH 1130 ): 1131 properties_sql = self.sql( 1132 exp.Properties( 1133 expressions=[ 1134 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1135 *properties_locs[exp.Properties.Location.POST_WITH], 1136 ] 1137 ) 1138 ) 1139 1140 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1141 properties_sql = self.sep() + properties_sql 1142 elif not self.pretty: 1143 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1144 properties_sql = f" {properties_sql}" 1145 1146 begin = " BEGIN" if expression.args.get("begin") else "" 1147 end = " END" if expression.args.get("end") else "" 1148 1149 expression_sql = self.sql(expression, "expression") 1150 if expression_sql: 1151 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1152 1153 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1154 postalias_props_sql = "" 1155 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1156 postalias_props_sql = self.properties( 1157 exp.Properties( 1158 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1159 ), 1160 wrapped=False, 1161 ) 1162 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1163 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1164 1165 postindex_props_sql = "" 1166 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1167 postindex_props_sql = self.properties( 1168 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1169 wrapped=False, 1170 prefix=" ", 1171 ) 1172 1173 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1174 indexes = f" {indexes}" if indexes else "" 1175 index_sql = indexes + postindex_props_sql 1176 1177 replace = " OR REPLACE" if expression.args.get("replace") else "" 1178 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1179 unique = " UNIQUE" if expression.args.get("unique") else "" 1180 1181 clustered = expression.args.get("clustered") 1182 if clustered is None: 1183 clustered_sql = "" 1184 elif clustered: 1185 clustered_sql = " CLUSTERED COLUMNSTORE" 1186 else: 1187 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1188 1189 postcreate_props_sql = "" 1190 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1191 postcreate_props_sql = self.properties( 1192 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1193 sep=" ", 1194 prefix=" ", 1195 wrapped=False, 1196 ) 1197 1198 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1199 1200 postexpression_props_sql = "" 1201 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1202 postexpression_props_sql = self.properties( 1203 exp.Properties( 1204 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1205 ), 1206 sep=" ", 1207 prefix=" ", 1208 wrapped=False, 1209 ) 1210 1211 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1212 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1213 no_schema_binding = ( 1214 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1215 ) 1216 1217 clone = self.sql(expression, "clone") 1218 clone = f" {clone}" if clone else "" 1219 1220 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1221 properties_expression = f"{expression_sql}{properties_sql}" 1222 else: 1223 properties_expression = f"{properties_sql}{expression_sql}" 1224 1225 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1226 return self.prepend_ctes(expression, expression_sql) 1227 1228 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1229 start = self.sql(expression, "start") 1230 start = f"START WITH {start}" if start else "" 1231 increment = self.sql(expression, "increment") 1232 increment = f" INCREMENT BY {increment}" if increment else "" 1233 minvalue = self.sql(expression, "minvalue") 1234 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1235 maxvalue = self.sql(expression, "maxvalue") 1236 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1237 owned = self.sql(expression, "owned") 1238 owned = f" OWNED BY {owned}" if owned else "" 1239 1240 cache = expression.args.get("cache") 1241 if cache is None: 1242 cache_str = "" 1243 elif cache is True: 1244 cache_str = " CACHE" 1245 else: 1246 cache_str = f" CACHE {cache}" 1247 1248 options = self.expressions(expression, key="options", flat=True, sep=" ") 1249 options = f" {options}" if options else "" 1250 1251 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1252 1253 def clone_sql(self, expression: exp.Clone) -> str: 1254 this = self.sql(expression, "this") 1255 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1256 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1257 return f"{shallow}{keyword} {this}" 1258 1259 def describe_sql(self, expression: exp.Describe) -> str: 1260 style = expression.args.get("style") 1261 style = f" {style}" if style else "" 1262 partition = self.sql(expression, "partition") 1263 partition = f" {partition}" if partition else "" 1264 format = self.sql(expression, "format") 1265 format = f" {format}" if format else "" 1266 1267 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1268 1269 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1270 tag = self.sql(expression, "tag") 1271 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1272 1273 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1274 with_ = self.sql(expression, "with") 1275 if with_: 1276 sql = f"{with_}{self.sep()}{sql}" 1277 return sql 1278 1279 def with_sql(self, expression: exp.With) -> str: 1280 sql = self.expressions(expression, flat=True) 1281 recursive = ( 1282 "RECURSIVE " 1283 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1284 else "" 1285 ) 1286 search = self.sql(expression, "search") 1287 search = f" {search}" if search else "" 1288 1289 return f"WITH {recursive}{sql}{search}" 1290 1291 def cte_sql(self, expression: exp.CTE) -> str: 1292 alias = expression.args.get("alias") 1293 if alias: 1294 alias.add_comments(expression.pop_comments()) 1295 1296 alias_sql = self.sql(expression, "alias") 1297 1298 materialized = expression.args.get("materialized") 1299 if materialized is False: 1300 materialized = "NOT MATERIALIZED " 1301 elif materialized: 1302 materialized = "MATERIALIZED " 1303 1304 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1305 1306 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1307 alias = self.sql(expression, "this") 1308 columns = self.expressions(expression, key="columns", flat=True) 1309 columns = f"({columns})" if columns else "" 1310 1311 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1312 columns = "" 1313 self.unsupported("Named columns are not supported in table alias.") 1314 1315 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1316 alias = self._next_name() 1317 1318 return f"{alias}{columns}" 1319 1320 def bitstring_sql(self, expression: exp.BitString) -> str: 1321 this = self.sql(expression, "this") 1322 if self.dialect.BIT_START: 1323 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1324 return f"{int(this, 2)}" 1325 1326 def hexstring_sql( 1327 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1328 ) -> str: 1329 this = self.sql(expression, "this") 1330 is_integer_type = expression.args.get("is_integer") 1331 1332 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1333 not self.dialect.HEX_START and not binary_function_repr 1334 ): 1335 # Integer representation will be returned if: 1336 # - The read dialect treats the hex value as integer literal but not the write 1337 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1338 return f"{int(this, 16)}" 1339 1340 if not is_integer_type: 1341 # Read dialect treats the hex value as BINARY/BLOB 1342 if binary_function_repr: 1343 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1344 return self.func(binary_function_repr, exp.Literal.string(this)) 1345 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1346 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1347 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1348 1349 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1350 1351 def bytestring_sql(self, expression: exp.ByteString) -> str: 1352 this = self.sql(expression, "this") 1353 if self.dialect.BYTE_START: 1354 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1355 return this 1356 1357 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1358 this = self.sql(expression, "this") 1359 escape = expression.args.get("escape") 1360 1361 if self.dialect.UNICODE_START: 1362 escape_substitute = r"\\\1" 1363 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1364 else: 1365 escape_substitute = r"\\u\1" 1366 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1367 1368 if escape: 1369 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1370 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1371 else: 1372 escape_pattern = ESCAPED_UNICODE_RE 1373 escape_sql = "" 1374 1375 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1376 this = escape_pattern.sub(escape_substitute, this) 1377 1378 return f"{left_quote}{this}{right_quote}{escape_sql}" 1379 1380 def rawstring_sql(self, expression: exp.RawString) -> str: 1381 string = expression.this 1382 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1383 string = string.replace("\\", "\\\\") 1384 1385 string = self.escape_str(string, escape_backslash=False) 1386 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1387 1388 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1389 this = self.sql(expression, "this") 1390 specifier = self.sql(expression, "expression") 1391 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1392 return f"{this}{specifier}" 1393 1394 def datatype_sql(self, expression: exp.DataType) -> str: 1395 nested = "" 1396 values = "" 1397 interior = self.expressions(expression, flat=True) 1398 1399 type_value = expression.this 1400 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1401 type_sql = self.sql(expression, "kind") 1402 else: 1403 type_sql = ( 1404 self.TYPE_MAPPING.get(type_value, type_value.value) 1405 if isinstance(type_value, exp.DataType.Type) 1406 else type_value 1407 ) 1408 1409 if interior: 1410 if expression.args.get("nested"): 1411 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1412 if expression.args.get("values") is not None: 1413 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1414 values = self.expressions(expression, key="values", flat=True) 1415 values = f"{delimiters[0]}{values}{delimiters[1]}" 1416 elif type_value == exp.DataType.Type.INTERVAL: 1417 nested = f" {interior}" 1418 else: 1419 nested = f"({interior})" 1420 1421 type_sql = f"{type_sql}{nested}{values}" 1422 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1423 exp.DataType.Type.TIMETZ, 1424 exp.DataType.Type.TIMESTAMPTZ, 1425 ): 1426 type_sql = f"{type_sql} WITH TIME ZONE" 1427 1428 return type_sql 1429 1430 def directory_sql(self, expression: exp.Directory) -> str: 1431 local = "LOCAL " if expression.args.get("local") else "" 1432 row_format = self.sql(expression, "row_format") 1433 row_format = f" {row_format}" if row_format else "" 1434 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1435 1436 def delete_sql(self, expression: exp.Delete) -> str: 1437 this = self.sql(expression, "this") 1438 this = f" FROM {this}" if this else "" 1439 using = self.sql(expression, "using") 1440 using = f" USING {using}" if using else "" 1441 cluster = self.sql(expression, "cluster") 1442 cluster = f" {cluster}" if cluster else "" 1443 where = self.sql(expression, "where") 1444 returning = self.sql(expression, "returning") 1445 limit = self.sql(expression, "limit") 1446 tables = self.expressions(expression, key="tables") 1447 tables = f" {tables}" if tables else "" 1448 if self.RETURNING_END: 1449 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1450 else: 1451 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1452 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1453 1454 def drop_sql(self, expression: exp.Drop) -> str: 1455 this = self.sql(expression, "this") 1456 expressions = self.expressions(expression, flat=True) 1457 expressions = f" ({expressions})" if expressions else "" 1458 kind = expression.args["kind"] 1459 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1460 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1461 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1462 on_cluster = self.sql(expression, "cluster") 1463 on_cluster = f" {on_cluster}" if on_cluster else "" 1464 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1465 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1466 cascade = " CASCADE" if expression.args.get("cascade") else "" 1467 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1468 purge = " PURGE" if expression.args.get("purge") else "" 1469 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1470 1471 def set_operation(self, expression: exp.SetOperation) -> str: 1472 op_type = type(expression) 1473 op_name = op_type.key.upper() 1474 1475 distinct = expression.args.get("distinct") 1476 if ( 1477 distinct is False 1478 and op_type in (exp.Except, exp.Intersect) 1479 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1480 ): 1481 self.unsupported(f"{op_name} ALL is not supported") 1482 1483 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1484 1485 if distinct is None: 1486 distinct = default_distinct 1487 if distinct is None: 1488 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1489 1490 if distinct is default_distinct: 1491 distinct_or_all = "" 1492 else: 1493 distinct_or_all = " DISTINCT" if distinct else " ALL" 1494 1495 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1496 side_kind = f"{side_kind} " if side_kind else "" 1497 1498 by_name = " BY NAME" if expression.args.get("by_name") else "" 1499 on = self.expressions(expression, key="on", flat=True) 1500 on = f" ON ({on})" if on else "" 1501 1502 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1503 1504 def set_operations(self, expression: exp.SetOperation) -> str: 1505 if not self.SET_OP_MODIFIERS: 1506 limit = expression.args.get("limit") 1507 order = expression.args.get("order") 1508 1509 if limit or order: 1510 select = self._move_ctes_to_top_level( 1511 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1512 ) 1513 1514 if limit: 1515 select = select.limit(limit.pop(), copy=False) 1516 if order: 1517 select = select.order_by(order.pop(), copy=False) 1518 return self.sql(select) 1519 1520 sqls: t.List[str] = [] 1521 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1522 1523 while stack: 1524 node = stack.pop() 1525 1526 if isinstance(node, exp.SetOperation): 1527 stack.append(node.expression) 1528 stack.append( 1529 self.maybe_comment( 1530 self.set_operation(node), comments=node.comments, separated=True 1531 ) 1532 ) 1533 stack.append(node.this) 1534 else: 1535 sqls.append(self.sql(node)) 1536 1537 this = self.sep().join(sqls) 1538 this = self.query_modifiers(expression, this) 1539 return self.prepend_ctes(expression, this) 1540 1541 def fetch_sql(self, expression: exp.Fetch) -> str: 1542 direction = expression.args.get("direction") 1543 direction = f" {direction}" if direction else "" 1544 count = self.sql(expression, "count") 1545 count = f" {count}" if count else "" 1546 limit_options = self.sql(expression, "limit_options") 1547 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1548 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1549 1550 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1551 percent = " PERCENT" if expression.args.get("percent") else "" 1552 rows = " ROWS" if expression.args.get("rows") else "" 1553 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1554 if not with_ties and rows: 1555 with_ties = " ONLY" 1556 return f"{percent}{rows}{with_ties}" 1557 1558 def filter_sql(self, expression: exp.Filter) -> str: 1559 if self.AGGREGATE_FILTER_SUPPORTED: 1560 this = self.sql(expression, "this") 1561 where = self.sql(expression, "expression").strip() 1562 return f"{this} FILTER({where})" 1563 1564 agg = expression.this 1565 agg_arg = agg.this 1566 cond = expression.expression.this 1567 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1568 return self.sql(agg) 1569 1570 def hint_sql(self, expression: exp.Hint) -> str: 1571 if not self.QUERY_HINTS: 1572 self.unsupported("Hints are not supported") 1573 return "" 1574 1575 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1576 1577 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1578 using = self.sql(expression, "using") 1579 using = f" USING {using}" if using else "" 1580 columns = self.expressions(expression, key="columns", flat=True) 1581 columns = f"({columns})" if columns else "" 1582 partition_by = self.expressions(expression, key="partition_by", flat=True) 1583 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1584 where = self.sql(expression, "where") 1585 include = self.expressions(expression, key="include", flat=True) 1586 if include: 1587 include = f" INCLUDE ({include})" 1588 with_storage = self.expressions(expression, key="with_storage", flat=True) 1589 with_storage = f" WITH ({with_storage})" if with_storage else "" 1590 tablespace = self.sql(expression, "tablespace") 1591 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1592 on = self.sql(expression, "on") 1593 on = f" ON {on}" if on else "" 1594 1595 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1596 1597 def index_sql(self, expression: exp.Index) -> str: 1598 unique = "UNIQUE " if expression.args.get("unique") else "" 1599 primary = "PRIMARY " if expression.args.get("primary") else "" 1600 amp = "AMP " if expression.args.get("amp") else "" 1601 name = self.sql(expression, "this") 1602 name = f"{name} " if name else "" 1603 table = self.sql(expression, "table") 1604 table = f"{self.INDEX_ON} {table}" if table else "" 1605 1606 index = "INDEX " if not table else "" 1607 1608 params = self.sql(expression, "params") 1609 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1610 1611 def identifier_sql(self, expression: exp.Identifier) -> str: 1612 text = expression.name 1613 lower = text.lower() 1614 text = lower if self.normalize and not expression.quoted else text 1615 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1616 if ( 1617 expression.quoted 1618 or self.dialect.can_identify(text, self.identify) 1619 or lower in self.RESERVED_KEYWORDS 1620 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1621 ): 1622 text = f"{self._identifier_start}{text}{self._identifier_end}" 1623 return text 1624 1625 def hex_sql(self, expression: exp.Hex) -> str: 1626 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1627 if self.dialect.HEX_LOWERCASE: 1628 text = self.func("LOWER", text) 1629 1630 return text 1631 1632 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1633 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1634 if not self.dialect.HEX_LOWERCASE: 1635 text = self.func("LOWER", text) 1636 return text 1637 1638 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1639 input_format = self.sql(expression, "input_format") 1640 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1641 output_format = self.sql(expression, "output_format") 1642 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1643 return self.sep().join((input_format, output_format)) 1644 1645 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1646 string = self.sql(exp.Literal.string(expression.name)) 1647 return f"{prefix}{string}" 1648 1649 def partition_sql(self, expression: exp.Partition) -> str: 1650 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1651 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1652 1653 def properties_sql(self, expression: exp.Properties) -> str: 1654 root_properties = [] 1655 with_properties = [] 1656 1657 for p in expression.expressions: 1658 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1659 if p_loc == exp.Properties.Location.POST_WITH: 1660 with_properties.append(p) 1661 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1662 root_properties.append(p) 1663 1664 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1665 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1666 1667 if root_props and with_props and not self.pretty: 1668 with_props = " " + with_props 1669 1670 return root_props + with_props 1671 1672 def root_properties(self, properties: exp.Properties) -> str: 1673 if properties.expressions: 1674 return self.expressions(properties, indent=False, sep=" ") 1675 return "" 1676 1677 def properties( 1678 self, 1679 properties: exp.Properties, 1680 prefix: str = "", 1681 sep: str = ", ", 1682 suffix: str = "", 1683 wrapped: bool = True, 1684 ) -> str: 1685 if properties.expressions: 1686 expressions = self.expressions(properties, sep=sep, indent=False) 1687 if expressions: 1688 expressions = self.wrap(expressions) if wrapped else expressions 1689 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1690 return "" 1691 1692 def with_properties(self, properties: exp.Properties) -> str: 1693 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1694 1695 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1696 properties_locs = defaultdict(list) 1697 for p in properties.expressions: 1698 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1699 if p_loc != exp.Properties.Location.UNSUPPORTED: 1700 properties_locs[p_loc].append(p) 1701 else: 1702 self.unsupported(f"Unsupported property {p.key}") 1703 1704 return properties_locs 1705 1706 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1707 if isinstance(expression.this, exp.Dot): 1708 return self.sql(expression, "this") 1709 return f"'{expression.name}'" if string_key else expression.name 1710 1711 def property_sql(self, expression: exp.Property) -> str: 1712 property_cls = expression.__class__ 1713 if property_cls == exp.Property: 1714 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1715 1716 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1717 if not property_name: 1718 self.unsupported(f"Unsupported property {expression.key}") 1719 1720 return f"{property_name}={self.sql(expression, 'this')}" 1721 1722 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1723 if self.SUPPORTS_CREATE_TABLE_LIKE: 1724 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1725 options = f" {options}" if options else "" 1726 1727 like = f"LIKE {self.sql(expression, 'this')}{options}" 1728 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1729 like = f"({like})" 1730 1731 return like 1732 1733 if expression.expressions: 1734 self.unsupported("Transpilation of LIKE property options is unsupported") 1735 1736 select = exp.select("*").from_(expression.this).limit(0) 1737 return f"AS {self.sql(select)}" 1738 1739 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1740 no = "NO " if expression.args.get("no") else "" 1741 protection = " PROTECTION" if expression.args.get("protection") else "" 1742 return f"{no}FALLBACK{protection}" 1743 1744 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1745 no = "NO " if expression.args.get("no") else "" 1746 local = expression.args.get("local") 1747 local = f"{local} " if local else "" 1748 dual = "DUAL " if expression.args.get("dual") else "" 1749 before = "BEFORE " if expression.args.get("before") else "" 1750 after = "AFTER " if expression.args.get("after") else "" 1751 return f"{no}{local}{dual}{before}{after}JOURNAL" 1752 1753 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1754 freespace = self.sql(expression, "this") 1755 percent = " PERCENT" if expression.args.get("percent") else "" 1756 return f"FREESPACE={freespace}{percent}" 1757 1758 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1759 if expression.args.get("default"): 1760 property = "DEFAULT" 1761 elif expression.args.get("on"): 1762 property = "ON" 1763 else: 1764 property = "OFF" 1765 return f"CHECKSUM={property}" 1766 1767 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1768 if expression.args.get("no"): 1769 return "NO MERGEBLOCKRATIO" 1770 if expression.args.get("default"): 1771 return "DEFAULT MERGEBLOCKRATIO" 1772 1773 percent = " PERCENT" if expression.args.get("percent") else "" 1774 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1775 1776 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1777 default = expression.args.get("default") 1778 minimum = expression.args.get("minimum") 1779 maximum = expression.args.get("maximum") 1780 if default or minimum or maximum: 1781 if default: 1782 prop = "DEFAULT" 1783 elif minimum: 1784 prop = "MINIMUM" 1785 else: 1786 prop = "MAXIMUM" 1787 return f"{prop} DATABLOCKSIZE" 1788 units = expression.args.get("units") 1789 units = f" {units}" if units else "" 1790 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1791 1792 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1793 autotemp = expression.args.get("autotemp") 1794 always = expression.args.get("always") 1795 default = expression.args.get("default") 1796 manual = expression.args.get("manual") 1797 never = expression.args.get("never") 1798 1799 if autotemp is not None: 1800 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1801 elif always: 1802 prop = "ALWAYS" 1803 elif default: 1804 prop = "DEFAULT" 1805 elif manual: 1806 prop = "MANUAL" 1807 elif never: 1808 prop = "NEVER" 1809 return f"BLOCKCOMPRESSION={prop}" 1810 1811 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1812 no = expression.args.get("no") 1813 no = " NO" if no else "" 1814 concurrent = expression.args.get("concurrent") 1815 concurrent = " CONCURRENT" if concurrent else "" 1816 target = self.sql(expression, "target") 1817 target = f" {target}" if target else "" 1818 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1819 1820 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1821 if isinstance(expression.this, list): 1822 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1823 if expression.this: 1824 modulus = self.sql(expression, "this") 1825 remainder = self.sql(expression, "expression") 1826 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1827 1828 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1829 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1830 return f"FROM ({from_expressions}) TO ({to_expressions})" 1831 1832 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1833 this = self.sql(expression, "this") 1834 1835 for_values_or_default = expression.expression 1836 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1837 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1838 else: 1839 for_values_or_default = " DEFAULT" 1840 1841 return f"PARTITION OF {this}{for_values_or_default}" 1842 1843 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1844 kind = expression.args.get("kind") 1845 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1846 for_or_in = expression.args.get("for_or_in") 1847 for_or_in = f" {for_or_in}" if for_or_in else "" 1848 lock_type = expression.args.get("lock_type") 1849 override = " OVERRIDE" if expression.args.get("override") else "" 1850 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1851 1852 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1853 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1854 statistics = expression.args.get("statistics") 1855 statistics_sql = "" 1856 if statistics is not None: 1857 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1858 return f"{data_sql}{statistics_sql}" 1859 1860 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1861 this = self.sql(expression, "this") 1862 this = f"HISTORY_TABLE={this}" if this else "" 1863 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1864 data_consistency = ( 1865 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1866 ) 1867 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1868 retention_period = ( 1869 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1870 ) 1871 1872 if this: 1873 on_sql = self.func("ON", this, data_consistency, retention_period) 1874 else: 1875 on_sql = "ON" if expression.args.get("on") else "OFF" 1876 1877 sql = f"SYSTEM_VERSIONING={on_sql}" 1878 1879 return f"WITH({sql})" if expression.args.get("with") else sql 1880 1881 def insert_sql(self, expression: exp.Insert) -> str: 1882 hint = self.sql(expression, "hint") 1883 overwrite = expression.args.get("overwrite") 1884 1885 if isinstance(expression.this, exp.Directory): 1886 this = " OVERWRITE" if overwrite else " INTO" 1887 else: 1888 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1889 1890 stored = self.sql(expression, "stored") 1891 stored = f" {stored}" if stored else "" 1892 alternative = expression.args.get("alternative") 1893 alternative = f" OR {alternative}" if alternative else "" 1894 ignore = " IGNORE" if expression.args.get("ignore") else "" 1895 is_function = expression.args.get("is_function") 1896 if is_function: 1897 this = f"{this} FUNCTION" 1898 this = f"{this} {self.sql(expression, 'this')}" 1899 1900 exists = " IF EXISTS" if expression.args.get("exists") else "" 1901 where = self.sql(expression, "where") 1902 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1903 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1904 on_conflict = self.sql(expression, "conflict") 1905 on_conflict = f" {on_conflict}" if on_conflict else "" 1906 by_name = " BY NAME" if expression.args.get("by_name") else "" 1907 returning = self.sql(expression, "returning") 1908 1909 if self.RETURNING_END: 1910 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1911 else: 1912 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1913 1914 partition_by = self.sql(expression, "partition") 1915 partition_by = f" {partition_by}" if partition_by else "" 1916 settings = self.sql(expression, "settings") 1917 settings = f" {settings}" if settings else "" 1918 1919 source = self.sql(expression, "source") 1920 source = f"TABLE {source}" if source else "" 1921 1922 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1923 return self.prepend_ctes(expression, sql) 1924 1925 def introducer_sql(self, expression: exp.Introducer) -> str: 1926 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1927 1928 def kill_sql(self, expression: exp.Kill) -> str: 1929 kind = self.sql(expression, "kind") 1930 kind = f" {kind}" if kind else "" 1931 this = self.sql(expression, "this") 1932 this = f" {this}" if this else "" 1933 return f"KILL{kind}{this}" 1934 1935 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1936 return expression.name 1937 1938 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1939 return expression.name 1940 1941 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1942 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1943 1944 constraint = self.sql(expression, "constraint") 1945 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1946 1947 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1948 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1949 action = self.sql(expression, "action") 1950 1951 expressions = self.expressions(expression, flat=True) 1952 if expressions: 1953 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1954 expressions = f" {set_keyword}{expressions}" 1955 1956 where = self.sql(expression, "where") 1957 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1958 1959 def returning_sql(self, expression: exp.Returning) -> str: 1960 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1961 1962 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1963 fields = self.sql(expression, "fields") 1964 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1965 escaped = self.sql(expression, "escaped") 1966 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1967 items = self.sql(expression, "collection_items") 1968 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1969 keys = self.sql(expression, "map_keys") 1970 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1971 lines = self.sql(expression, "lines") 1972 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1973 null = self.sql(expression, "null") 1974 null = f" NULL DEFINED AS {null}" if null else "" 1975 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1976 1977 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1978 return f"WITH ({self.expressions(expression, flat=True)})" 1979 1980 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1981 this = f"{self.sql(expression, 'this')} INDEX" 1982 target = self.sql(expression, "target") 1983 target = f" FOR {target}" if target else "" 1984 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1985 1986 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1987 this = self.sql(expression, "this") 1988 kind = self.sql(expression, "kind") 1989 expr = self.sql(expression, "expression") 1990 return f"{this} ({kind} => {expr})" 1991 1992 def table_parts(self, expression: exp.Table) -> str: 1993 return ".".join( 1994 self.sql(part) 1995 for part in ( 1996 expression.args.get("catalog"), 1997 expression.args.get("db"), 1998 expression.args.get("this"), 1999 ) 2000 if part is not None 2001 ) 2002 2003 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2004 table = self.table_parts(expression) 2005 only = "ONLY " if expression.args.get("only") else "" 2006 partition = self.sql(expression, "partition") 2007 partition = f" {partition}" if partition else "" 2008 version = self.sql(expression, "version") 2009 version = f" {version}" if version else "" 2010 alias = self.sql(expression, "alias") 2011 alias = f"{sep}{alias}" if alias else "" 2012 2013 sample = self.sql(expression, "sample") 2014 if self.dialect.ALIAS_POST_TABLESAMPLE: 2015 sample_pre_alias = sample 2016 sample_post_alias = "" 2017 else: 2018 sample_pre_alias = "" 2019 sample_post_alias = sample 2020 2021 hints = self.expressions(expression, key="hints", sep=" ") 2022 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2023 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2024 joins = self.indent( 2025 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2026 ) 2027 laterals = self.expressions(expression, key="laterals", sep="") 2028 2029 file_format = self.sql(expression, "format") 2030 if file_format: 2031 pattern = self.sql(expression, "pattern") 2032 pattern = f", PATTERN => {pattern}" if pattern else "" 2033 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2034 2035 ordinality = expression.args.get("ordinality") or "" 2036 if ordinality: 2037 ordinality = f" WITH ORDINALITY{alias}" 2038 alias = "" 2039 2040 when = self.sql(expression, "when") 2041 if when: 2042 table = f"{table} {when}" 2043 2044 changes = self.sql(expression, "changes") 2045 changes = f" {changes}" if changes else "" 2046 2047 rows_from = self.expressions(expression, key="rows_from") 2048 if rows_from: 2049 table = f"ROWS FROM {self.wrap(rows_from)}" 2050 2051 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2052 2053 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2054 table = self.func("TABLE", expression.this) 2055 alias = self.sql(expression, "alias") 2056 alias = f" AS {alias}" if alias else "" 2057 sample = self.sql(expression, "sample") 2058 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2059 joins = self.indent( 2060 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2061 ) 2062 return f"{table}{alias}{pivots}{sample}{joins}" 2063 2064 def tablesample_sql( 2065 self, 2066 expression: exp.TableSample, 2067 tablesample_keyword: t.Optional[str] = None, 2068 ) -> str: 2069 method = self.sql(expression, "method") 2070 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2071 numerator = self.sql(expression, "bucket_numerator") 2072 denominator = self.sql(expression, "bucket_denominator") 2073 field = self.sql(expression, "bucket_field") 2074 field = f" ON {field}" if field else "" 2075 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2076 seed = self.sql(expression, "seed") 2077 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2078 2079 size = self.sql(expression, "size") 2080 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2081 size = f"{size} ROWS" 2082 2083 percent = self.sql(expression, "percent") 2084 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2085 percent = f"{percent} PERCENT" 2086 2087 expr = f"{bucket}{percent}{size}" 2088 if self.TABLESAMPLE_REQUIRES_PARENS: 2089 expr = f"({expr})" 2090 2091 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2092 2093 def pivot_sql(self, expression: exp.Pivot) -> str: 2094 expressions = self.expressions(expression, flat=True) 2095 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2096 2097 group = self.sql(expression, "group") 2098 2099 if expression.this: 2100 this = self.sql(expression, "this") 2101 if not expressions: 2102 return f"UNPIVOT {this}" 2103 2104 on = f"{self.seg('ON')} {expressions}" 2105 into = self.sql(expression, "into") 2106 into = f"{self.seg('INTO')} {into}" if into else "" 2107 using = self.expressions(expression, key="using", flat=True) 2108 using = f"{self.seg('USING')} {using}" if using else "" 2109 return f"{direction} {this}{on}{into}{using}{group}" 2110 2111 alias = self.sql(expression, "alias") 2112 alias = f" AS {alias}" if alias else "" 2113 2114 fields = self.expressions( 2115 expression, 2116 "fields", 2117 sep=" ", 2118 dynamic=True, 2119 new_line=True, 2120 skip_first=True, 2121 skip_last=True, 2122 ) 2123 2124 include_nulls = expression.args.get("include_nulls") 2125 if include_nulls is not None: 2126 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2127 else: 2128 nulls = "" 2129 2130 default_on_null = self.sql(expression, "default_on_null") 2131 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2132 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2133 2134 def version_sql(self, expression: exp.Version) -> str: 2135 this = f"FOR {expression.name}" 2136 kind = expression.text("kind") 2137 expr = self.sql(expression, "expression") 2138 return f"{this} {kind} {expr}" 2139 2140 def tuple_sql(self, expression: exp.Tuple) -> str: 2141 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2142 2143 def update_sql(self, expression: exp.Update) -> str: 2144 this = self.sql(expression, "this") 2145 set_sql = self.expressions(expression, flat=True) 2146 from_sql = self.sql(expression, "from") 2147 where_sql = self.sql(expression, "where") 2148 returning = self.sql(expression, "returning") 2149 order = self.sql(expression, "order") 2150 limit = self.sql(expression, "limit") 2151 if self.RETURNING_END: 2152 expression_sql = f"{from_sql}{where_sql}{returning}" 2153 else: 2154 expression_sql = f"{returning}{from_sql}{where_sql}" 2155 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2156 return self.prepend_ctes(expression, sql) 2157 2158 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2159 values_as_table = values_as_table and self.VALUES_AS_TABLE 2160 2161 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2162 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2163 args = self.expressions(expression) 2164 alias = self.sql(expression, "alias") 2165 values = f"VALUES{self.seg('')}{args}" 2166 values = ( 2167 f"({values})" 2168 if self.WRAP_DERIVED_VALUES 2169 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2170 else values 2171 ) 2172 return f"{values} AS {alias}" if alias else values 2173 2174 # Converts `VALUES...` expression into a series of select unions. 2175 alias_node = expression.args.get("alias") 2176 column_names = alias_node and alias_node.columns 2177 2178 selects: t.List[exp.Query] = [] 2179 2180 for i, tup in enumerate(expression.expressions): 2181 row = tup.expressions 2182 2183 if i == 0 and column_names: 2184 row = [ 2185 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2186 ] 2187 2188 selects.append(exp.Select(expressions=row)) 2189 2190 if self.pretty: 2191 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2192 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2193 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2194 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2195 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2196 2197 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2198 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2199 return f"({unions}){alias}" 2200 2201 def var_sql(self, expression: exp.Var) -> str: 2202 return self.sql(expression, "this") 2203 2204 @unsupported_args("expressions") 2205 def into_sql(self, expression: exp.Into) -> str: 2206 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2207 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2208 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2209 2210 def from_sql(self, expression: exp.From) -> str: 2211 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2212 2213 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2214 grouping_sets = self.expressions(expression, indent=False) 2215 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2216 2217 def rollup_sql(self, expression: exp.Rollup) -> str: 2218 expressions = self.expressions(expression, indent=False) 2219 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2220 2221 def cube_sql(self, expression: exp.Cube) -> str: 2222 expressions = self.expressions(expression, indent=False) 2223 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2224 2225 def group_sql(self, expression: exp.Group) -> str: 2226 group_by_all = expression.args.get("all") 2227 if group_by_all is True: 2228 modifier = " ALL" 2229 elif group_by_all is False: 2230 modifier = " DISTINCT" 2231 else: 2232 modifier = "" 2233 2234 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2235 2236 grouping_sets = self.expressions(expression, key="grouping_sets") 2237 cube = self.expressions(expression, key="cube") 2238 rollup = self.expressions(expression, key="rollup") 2239 2240 groupings = csv( 2241 self.seg(grouping_sets) if grouping_sets else "", 2242 self.seg(cube) if cube else "", 2243 self.seg(rollup) if rollup else "", 2244 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2245 sep=self.GROUPINGS_SEP, 2246 ) 2247 2248 if ( 2249 expression.expressions 2250 and groupings 2251 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2252 ): 2253 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2254 2255 return f"{group_by}{groupings}" 2256 2257 def having_sql(self, expression: exp.Having) -> str: 2258 this = self.indent(self.sql(expression, "this")) 2259 return f"{self.seg('HAVING')}{self.sep()}{this}" 2260 2261 def connect_sql(self, expression: exp.Connect) -> str: 2262 start = self.sql(expression, "start") 2263 start = self.seg(f"START WITH {start}") if start else "" 2264 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2265 connect = self.sql(expression, "connect") 2266 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2267 return start + connect 2268 2269 def prior_sql(self, expression: exp.Prior) -> str: 2270 return f"PRIOR {self.sql(expression, 'this')}" 2271 2272 def join_sql(self, expression: exp.Join) -> str: 2273 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2274 side = None 2275 else: 2276 side = expression.side 2277 2278 op_sql = " ".join( 2279 op 2280 for op in ( 2281 expression.method, 2282 "GLOBAL" if expression.args.get("global") else None, 2283 side, 2284 expression.kind, 2285 expression.hint if self.JOIN_HINTS else None, 2286 ) 2287 if op 2288 ) 2289 match_cond = self.sql(expression, "match_condition") 2290 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2291 on_sql = self.sql(expression, "on") 2292 using = expression.args.get("using") 2293 2294 if not on_sql and using: 2295 on_sql = csv(*(self.sql(column) for column in using)) 2296 2297 this = expression.this 2298 this_sql = self.sql(this) 2299 2300 exprs = self.expressions(expression) 2301 if exprs: 2302 this_sql = f"{this_sql},{self.seg(exprs)}" 2303 2304 if on_sql: 2305 on_sql = self.indent(on_sql, skip_first=True) 2306 space = self.seg(" " * self.pad) if self.pretty else " " 2307 if using: 2308 on_sql = f"{space}USING ({on_sql})" 2309 else: 2310 on_sql = f"{space}ON {on_sql}" 2311 elif not op_sql: 2312 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2313 return f" {this_sql}" 2314 2315 return f", {this_sql}" 2316 2317 if op_sql != "STRAIGHT_JOIN": 2318 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2319 2320 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2321 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2322 2323 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2324 args = self.expressions(expression, flat=True) 2325 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2326 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2327 2328 def lateral_op(self, expression: exp.Lateral) -> str: 2329 cross_apply = expression.args.get("cross_apply") 2330 2331 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2332 if cross_apply is True: 2333 op = "INNER JOIN " 2334 elif cross_apply is False: 2335 op = "LEFT JOIN " 2336 else: 2337 op = "" 2338 2339 return f"{op}LATERAL" 2340 2341 def lateral_sql(self, expression: exp.Lateral) -> str: 2342 this = self.sql(expression, "this") 2343 2344 if expression.args.get("view"): 2345 alias = expression.args["alias"] 2346 columns = self.expressions(alias, key="columns", flat=True) 2347 table = f" {alias.name}" if alias.name else "" 2348 columns = f" AS {columns}" if columns else "" 2349 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2350 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2351 2352 alias = self.sql(expression, "alias") 2353 alias = f" AS {alias}" if alias else "" 2354 2355 ordinality = expression.args.get("ordinality") or "" 2356 if ordinality: 2357 ordinality = f" WITH ORDINALITY{alias}" 2358 alias = "" 2359 2360 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2361 2362 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2363 this = self.sql(expression, "this") 2364 2365 args = [ 2366 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2367 for e in (expression.args.get(k) for k in ("offset", "expression")) 2368 if e 2369 ] 2370 2371 args_sql = ", ".join(self.sql(e) for e in args) 2372 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2373 expressions = self.expressions(expression, flat=True) 2374 limit_options = self.sql(expression, "limit_options") 2375 expressions = f" BY {expressions}" if expressions else "" 2376 2377 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2378 2379 def offset_sql(self, expression: exp.Offset) -> str: 2380 this = self.sql(expression, "this") 2381 value = expression.expression 2382 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2383 expressions = self.expressions(expression, flat=True) 2384 expressions = f" BY {expressions}" if expressions else "" 2385 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2386 2387 def setitem_sql(self, expression: exp.SetItem) -> str: 2388 kind = self.sql(expression, "kind") 2389 kind = f"{kind} " if kind else "" 2390 this = self.sql(expression, "this") 2391 expressions = self.expressions(expression) 2392 collate = self.sql(expression, "collate") 2393 collate = f" COLLATE {collate}" if collate else "" 2394 global_ = "GLOBAL " if expression.args.get("global") else "" 2395 return f"{global_}{kind}{this}{expressions}{collate}" 2396 2397 def set_sql(self, expression: exp.Set) -> str: 2398 expressions = f" {self.expressions(expression, flat=True)}" 2399 tag = " TAG" if expression.args.get("tag") else "" 2400 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2401 2402 def pragma_sql(self, expression: exp.Pragma) -> str: 2403 return f"PRAGMA {self.sql(expression, 'this')}" 2404 2405 def lock_sql(self, expression: exp.Lock) -> str: 2406 if not self.LOCKING_READS_SUPPORTED: 2407 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2408 return "" 2409 2410 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2411 expressions = self.expressions(expression, flat=True) 2412 expressions = f" OF {expressions}" if expressions else "" 2413 wait = expression.args.get("wait") 2414 2415 if wait is not None: 2416 if isinstance(wait, exp.Literal): 2417 wait = f" WAIT {self.sql(wait)}" 2418 else: 2419 wait = " NOWAIT" if wait else " SKIP LOCKED" 2420 2421 return f"{lock_type}{expressions}{wait or ''}" 2422 2423 def literal_sql(self, expression: exp.Literal) -> str: 2424 text = expression.this or "" 2425 if expression.is_string: 2426 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2427 return text 2428 2429 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2430 if self.dialect.ESCAPED_SEQUENCES: 2431 to_escaped = self.dialect.ESCAPED_SEQUENCES 2432 text = "".join( 2433 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2434 ) 2435 2436 return self._replace_line_breaks(text).replace( 2437 self.dialect.QUOTE_END, self._escaped_quote_end 2438 ) 2439 2440 def loaddata_sql(self, expression: exp.LoadData) -> str: 2441 local = " LOCAL" if expression.args.get("local") else "" 2442 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2443 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2444 this = f" INTO TABLE {self.sql(expression, 'this')}" 2445 partition = self.sql(expression, "partition") 2446 partition = f" {partition}" if partition else "" 2447 input_format = self.sql(expression, "input_format") 2448 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2449 serde = self.sql(expression, "serde") 2450 serde = f" SERDE {serde}" if serde else "" 2451 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2452 2453 def null_sql(self, *_) -> str: 2454 return "NULL" 2455 2456 def boolean_sql(self, expression: exp.Boolean) -> str: 2457 return "TRUE" if expression.this else "FALSE" 2458 2459 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2460 this = self.sql(expression, "this") 2461 this = f"{this} " if this else this 2462 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2463 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2464 2465 def withfill_sql(self, expression: exp.WithFill) -> str: 2466 from_sql = self.sql(expression, "from") 2467 from_sql = f" FROM {from_sql}" if from_sql else "" 2468 to_sql = self.sql(expression, "to") 2469 to_sql = f" TO {to_sql}" if to_sql else "" 2470 step_sql = self.sql(expression, "step") 2471 step_sql = f" STEP {step_sql}" if step_sql else "" 2472 interpolated_values = [ 2473 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2474 if isinstance(e, exp.Alias) 2475 else self.sql(e, "this") 2476 for e in expression.args.get("interpolate") or [] 2477 ] 2478 interpolate = ( 2479 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2480 ) 2481 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2482 2483 def cluster_sql(self, expression: exp.Cluster) -> str: 2484 return self.op_expressions("CLUSTER BY", expression) 2485 2486 def distribute_sql(self, expression: exp.Distribute) -> str: 2487 return self.op_expressions("DISTRIBUTE BY", expression) 2488 2489 def sort_sql(self, expression: exp.Sort) -> str: 2490 return self.op_expressions("SORT BY", expression) 2491 2492 def ordered_sql(self, expression: exp.Ordered) -> str: 2493 desc = expression.args.get("desc") 2494 asc = not desc 2495 2496 nulls_first = expression.args.get("nulls_first") 2497 nulls_last = not nulls_first 2498 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2499 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2500 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2501 2502 this = self.sql(expression, "this") 2503 2504 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2505 nulls_sort_change = "" 2506 if nulls_first and ( 2507 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2508 ): 2509 nulls_sort_change = " NULLS FIRST" 2510 elif ( 2511 nulls_last 2512 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2513 and not nulls_are_last 2514 ): 2515 nulls_sort_change = " NULLS LAST" 2516 2517 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2518 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2519 window = expression.find_ancestor(exp.Window, exp.Select) 2520 if isinstance(window, exp.Window) and window.args.get("spec"): 2521 self.unsupported( 2522 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2523 ) 2524 nulls_sort_change = "" 2525 elif self.NULL_ORDERING_SUPPORTED is False and ( 2526 (asc and nulls_sort_change == " NULLS LAST") 2527 or (desc and nulls_sort_change == " NULLS FIRST") 2528 ): 2529 # BigQuery does not allow these ordering/nulls combinations when used under 2530 # an aggregation func or under a window containing one 2531 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2532 2533 if isinstance(ancestor, exp.Window): 2534 ancestor = ancestor.this 2535 if isinstance(ancestor, exp.AggFunc): 2536 self.unsupported( 2537 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2538 ) 2539 nulls_sort_change = "" 2540 elif self.NULL_ORDERING_SUPPORTED is None: 2541 if expression.this.is_int: 2542 self.unsupported( 2543 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2544 ) 2545 elif not isinstance(expression.this, exp.Rand): 2546 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2547 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2548 nulls_sort_change = "" 2549 2550 with_fill = self.sql(expression, "with_fill") 2551 with_fill = f" {with_fill}" if with_fill else "" 2552 2553 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2554 2555 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2556 window_frame = self.sql(expression, "window_frame") 2557 window_frame = f"{window_frame} " if window_frame else "" 2558 2559 this = self.sql(expression, "this") 2560 2561 return f"{window_frame}{this}" 2562 2563 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2564 partition = self.partition_by_sql(expression) 2565 order = self.sql(expression, "order") 2566 measures = self.expressions(expression, key="measures") 2567 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2568 rows = self.sql(expression, "rows") 2569 rows = self.seg(rows) if rows else "" 2570 after = self.sql(expression, "after") 2571 after = self.seg(after) if after else "" 2572 pattern = self.sql(expression, "pattern") 2573 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2574 definition_sqls = [ 2575 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2576 for definition in expression.args.get("define", []) 2577 ] 2578 definitions = self.expressions(sqls=definition_sqls) 2579 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2580 body = "".join( 2581 ( 2582 partition, 2583 order, 2584 measures, 2585 rows, 2586 after, 2587 pattern, 2588 define, 2589 ) 2590 ) 2591 alias = self.sql(expression, "alias") 2592 alias = f" {alias}" if alias else "" 2593 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2594 2595 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2596 limit = expression.args.get("limit") 2597 2598 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2599 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2600 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2601 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2602 2603 return csv( 2604 *sqls, 2605 *[self.sql(join) for join in expression.args.get("joins") or []], 2606 self.sql(expression, "match"), 2607 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2608 self.sql(expression, "prewhere"), 2609 self.sql(expression, "where"), 2610 self.sql(expression, "connect"), 2611 self.sql(expression, "group"), 2612 self.sql(expression, "having"), 2613 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2614 self.sql(expression, "order"), 2615 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2616 *self.after_limit_modifiers(expression), 2617 self.options_modifier(expression), 2618 self.for_modifiers(expression), 2619 sep="", 2620 ) 2621 2622 def options_modifier(self, expression: exp.Expression) -> str: 2623 options = self.expressions(expression, key="options") 2624 return f" {options}" if options else "" 2625 2626 def for_modifiers(self, expression: exp.Expression) -> str: 2627 for_modifiers = self.expressions(expression, key="for") 2628 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2629 2630 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2631 self.unsupported("Unsupported query option.") 2632 return "" 2633 2634 def offset_limit_modifiers( 2635 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2636 ) -> t.List[str]: 2637 return [ 2638 self.sql(expression, "offset") if fetch else self.sql(limit), 2639 self.sql(limit) if fetch else self.sql(expression, "offset"), 2640 ] 2641 2642 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2643 locks = self.expressions(expression, key="locks", sep=" ") 2644 locks = f" {locks}" if locks else "" 2645 return [locks, self.sql(expression, "sample")] 2646 2647 def select_sql(self, expression: exp.Select) -> str: 2648 into = expression.args.get("into") 2649 if not self.SUPPORTS_SELECT_INTO and into: 2650 into.pop() 2651 2652 hint = self.sql(expression, "hint") 2653 distinct = self.sql(expression, "distinct") 2654 distinct = f" {distinct}" if distinct else "" 2655 kind = self.sql(expression, "kind") 2656 2657 limit = expression.args.get("limit") 2658 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2659 top = self.limit_sql(limit, top=True) 2660 limit.pop() 2661 else: 2662 top = "" 2663 2664 expressions = self.expressions(expression) 2665 2666 if kind: 2667 if kind in self.SELECT_KINDS: 2668 kind = f" AS {kind}" 2669 else: 2670 if kind == "STRUCT": 2671 expressions = self.expressions( 2672 sqls=[ 2673 self.sql( 2674 exp.Struct( 2675 expressions=[ 2676 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2677 if isinstance(e, exp.Alias) 2678 else e 2679 for e in expression.expressions 2680 ] 2681 ) 2682 ) 2683 ] 2684 ) 2685 kind = "" 2686 2687 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2688 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2689 2690 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2691 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2692 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2693 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2694 sql = self.query_modifiers( 2695 expression, 2696 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2697 self.sql(expression, "into", comment=False), 2698 self.sql(expression, "from", comment=False), 2699 ) 2700 2701 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2702 if expression.args.get("with"): 2703 sql = self.maybe_comment(sql, expression) 2704 expression.pop_comments() 2705 2706 sql = self.prepend_ctes(expression, sql) 2707 2708 if not self.SUPPORTS_SELECT_INTO and into: 2709 if into.args.get("temporary"): 2710 table_kind = " TEMPORARY" 2711 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2712 table_kind = " UNLOGGED" 2713 else: 2714 table_kind = "" 2715 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2716 2717 return sql 2718 2719 def schema_sql(self, expression: exp.Schema) -> str: 2720 this = self.sql(expression, "this") 2721 sql = self.schema_columns_sql(expression) 2722 return f"{this} {sql}" if this and sql else this or sql 2723 2724 def schema_columns_sql(self, expression: exp.Schema) -> str: 2725 if expression.expressions: 2726 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2727 return "" 2728 2729 def star_sql(self, expression: exp.Star) -> str: 2730 except_ = self.expressions(expression, key="except", flat=True) 2731 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2732 replace = self.expressions(expression, key="replace", flat=True) 2733 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2734 rename = self.expressions(expression, key="rename", flat=True) 2735 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2736 return f"*{except_}{replace}{rename}" 2737 2738 def parameter_sql(self, expression: exp.Parameter) -> str: 2739 this = self.sql(expression, "this") 2740 return f"{self.PARAMETER_TOKEN}{this}" 2741 2742 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2743 this = self.sql(expression, "this") 2744 kind = expression.text("kind") 2745 if kind: 2746 kind = f"{kind}." 2747 return f"@@{kind}{this}" 2748 2749 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2750 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2751 2752 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2753 alias = self.sql(expression, "alias") 2754 alias = f"{sep}{alias}" if alias else "" 2755 sample = self.sql(expression, "sample") 2756 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2757 alias = f"{sample}{alias}" 2758 2759 # Set to None so it's not generated again by self.query_modifiers() 2760 expression.set("sample", None) 2761 2762 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2763 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2764 return self.prepend_ctes(expression, sql) 2765 2766 def qualify_sql(self, expression: exp.Qualify) -> str: 2767 this = self.indent(self.sql(expression, "this")) 2768 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2769 2770 def unnest_sql(self, expression: exp.Unnest) -> str: 2771 args = self.expressions(expression, flat=True) 2772 2773 alias = expression.args.get("alias") 2774 offset = expression.args.get("offset") 2775 2776 if self.UNNEST_WITH_ORDINALITY: 2777 if alias and isinstance(offset, exp.Expression): 2778 alias.append("columns", offset) 2779 2780 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2781 columns = alias.columns 2782 alias = self.sql(columns[0]) if columns else "" 2783 else: 2784 alias = self.sql(alias) 2785 2786 alias = f" AS {alias}" if alias else alias 2787 if self.UNNEST_WITH_ORDINALITY: 2788 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2789 else: 2790 if isinstance(offset, exp.Expression): 2791 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2792 elif offset: 2793 suffix = f"{alias} WITH OFFSET" 2794 else: 2795 suffix = alias 2796 2797 return f"UNNEST({args}){suffix}" 2798 2799 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2800 return "" 2801 2802 def where_sql(self, expression: exp.Where) -> str: 2803 this = self.indent(self.sql(expression, "this")) 2804 return f"{self.seg('WHERE')}{self.sep()}{this}" 2805 2806 def window_sql(self, expression: exp.Window) -> str: 2807 this = self.sql(expression, "this") 2808 partition = self.partition_by_sql(expression) 2809 order = expression.args.get("order") 2810 order = self.order_sql(order, flat=True) if order else "" 2811 spec = self.sql(expression, "spec") 2812 alias = self.sql(expression, "alias") 2813 over = self.sql(expression, "over") or "OVER" 2814 2815 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2816 2817 first = expression.args.get("first") 2818 if first is None: 2819 first = "" 2820 else: 2821 first = "FIRST" if first else "LAST" 2822 2823 if not partition and not order and not spec and alias: 2824 return f"{this} {alias}" 2825 2826 args = self.format_args( 2827 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2828 ) 2829 return f"{this} ({args})" 2830 2831 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2832 partition = self.expressions(expression, key="partition_by", flat=True) 2833 return f"PARTITION BY {partition}" if partition else "" 2834 2835 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2836 kind = self.sql(expression, "kind") 2837 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2838 end = ( 2839 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2840 or "CURRENT ROW" 2841 ) 2842 2843 window_spec = f"{kind} BETWEEN {start} AND {end}" 2844 2845 exclude = self.sql(expression, "exclude") 2846 if exclude: 2847 if self.SUPPORTS_WINDOW_EXCLUDE: 2848 window_spec += f" EXCLUDE {exclude}" 2849 else: 2850 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2851 2852 return window_spec 2853 2854 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2855 this = self.sql(expression, "this") 2856 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2857 return f"{this} WITHIN GROUP ({expression_sql})" 2858 2859 def between_sql(self, expression: exp.Between) -> str: 2860 this = self.sql(expression, "this") 2861 low = self.sql(expression, "low") 2862 high = self.sql(expression, "high") 2863 return f"{this} BETWEEN {low} AND {high}" 2864 2865 def bracket_offset_expressions( 2866 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2867 ) -> t.List[exp.Expression]: 2868 return apply_index_offset( 2869 expression.this, 2870 expression.expressions, 2871 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2872 dialect=self.dialect, 2873 ) 2874 2875 def bracket_sql(self, expression: exp.Bracket) -> str: 2876 expressions = self.bracket_offset_expressions(expression) 2877 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2878 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2879 2880 def all_sql(self, expression: exp.All) -> str: 2881 return f"ALL {self.wrap(expression)}" 2882 2883 def any_sql(self, expression: exp.Any) -> str: 2884 this = self.sql(expression, "this") 2885 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2886 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2887 this = self.wrap(this) 2888 return f"ANY{this}" 2889 return f"ANY {this}" 2890 2891 def exists_sql(self, expression: exp.Exists) -> str: 2892 return f"EXISTS{self.wrap(expression)}" 2893 2894 def case_sql(self, expression: exp.Case) -> str: 2895 this = self.sql(expression, "this") 2896 statements = [f"CASE {this}" if this else "CASE"] 2897 2898 for e in expression.args["ifs"]: 2899 statements.append(f"WHEN {self.sql(e, 'this')}") 2900 statements.append(f"THEN {self.sql(e, 'true')}") 2901 2902 default = self.sql(expression, "default") 2903 2904 if default: 2905 statements.append(f"ELSE {default}") 2906 2907 statements.append("END") 2908 2909 if self.pretty and self.too_wide(statements): 2910 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2911 2912 return " ".join(statements) 2913 2914 def constraint_sql(self, expression: exp.Constraint) -> str: 2915 this = self.sql(expression, "this") 2916 expressions = self.expressions(expression, flat=True) 2917 return f"CONSTRAINT {this} {expressions}" 2918 2919 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2920 order = expression.args.get("order") 2921 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2922 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2923 2924 def extract_sql(self, expression: exp.Extract) -> str: 2925 from sqlglot.dialects.dialect import map_date_part 2926 2927 this = ( 2928 map_date_part(expression.this, self.dialect) 2929 if self.NORMALIZE_EXTRACT_DATE_PARTS 2930 else expression.this 2931 ) 2932 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2933 expression_sql = self.sql(expression, "expression") 2934 2935 return f"EXTRACT({this_sql} FROM {expression_sql})" 2936 2937 def trim_sql(self, expression: exp.Trim) -> str: 2938 trim_type = self.sql(expression, "position") 2939 2940 if trim_type == "LEADING": 2941 func_name = "LTRIM" 2942 elif trim_type == "TRAILING": 2943 func_name = "RTRIM" 2944 else: 2945 func_name = "TRIM" 2946 2947 return self.func(func_name, expression.this, expression.expression) 2948 2949 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2950 args = expression.expressions 2951 if isinstance(expression, exp.ConcatWs): 2952 args = args[1:] # Skip the delimiter 2953 2954 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2955 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2956 2957 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2958 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2959 2960 return args 2961 2962 def concat_sql(self, expression: exp.Concat) -> str: 2963 expressions = self.convert_concat_args(expression) 2964 2965 # Some dialects don't allow a single-argument CONCAT call 2966 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2967 return self.sql(expressions[0]) 2968 2969 return self.func("CONCAT", *expressions) 2970 2971 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2972 return self.func( 2973 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2974 ) 2975 2976 def check_sql(self, expression: exp.Check) -> str: 2977 this = self.sql(expression, key="this") 2978 return f"CHECK ({this})" 2979 2980 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2981 expressions = self.expressions(expression, flat=True) 2982 expressions = f" ({expressions})" if expressions else "" 2983 reference = self.sql(expression, "reference") 2984 reference = f" {reference}" if reference else "" 2985 delete = self.sql(expression, "delete") 2986 delete = f" ON DELETE {delete}" if delete else "" 2987 update = self.sql(expression, "update") 2988 update = f" ON UPDATE {update}" if update else "" 2989 options = self.expressions(expression, key="options", flat=True, sep=" ") 2990 options = f" {options}" if options else "" 2991 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 2992 2993 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2994 expressions = self.expressions(expression, flat=True) 2995 options = self.expressions(expression, key="options", flat=True, sep=" ") 2996 options = f" {options}" if options else "" 2997 return f"PRIMARY KEY ({expressions}){options}" 2998 2999 def if_sql(self, expression: exp.If) -> str: 3000 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3001 3002 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3003 modifier = expression.args.get("modifier") 3004 modifier = f" {modifier}" if modifier else "" 3005 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3006 3007 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3008 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3009 3010 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3011 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3012 3013 if expression.args.get("escape"): 3014 path = self.escape_str(path) 3015 3016 if self.QUOTE_JSON_PATH: 3017 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3018 3019 return path 3020 3021 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3022 if isinstance(expression, exp.JSONPathPart): 3023 transform = self.TRANSFORMS.get(expression.__class__) 3024 if not callable(transform): 3025 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3026 return "" 3027 3028 return transform(self, expression) 3029 3030 if isinstance(expression, int): 3031 return str(expression) 3032 3033 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3034 escaped = expression.replace("'", "\\'") 3035 escaped = f"\\'{expression}\\'" 3036 else: 3037 escaped = expression.replace('"', '\\"') 3038 escaped = f'"{escaped}"' 3039 3040 return escaped 3041 3042 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3043 return f"{self.sql(expression, 'this')} FORMAT JSON" 3044 3045 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3046 # Output the Teradata column FORMAT override. 3047 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3048 this = self.sql(expression, "this") 3049 fmt = self.sql(expression, "format") 3050 return f"{this} (FORMAT {fmt})" 3051 3052 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3053 null_handling = expression.args.get("null_handling") 3054 null_handling = f" {null_handling}" if null_handling else "" 3055 3056 unique_keys = expression.args.get("unique_keys") 3057 if unique_keys is not None: 3058 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3059 else: 3060 unique_keys = "" 3061 3062 return_type = self.sql(expression, "return_type") 3063 return_type = f" RETURNING {return_type}" if return_type else "" 3064 encoding = self.sql(expression, "encoding") 3065 encoding = f" ENCODING {encoding}" if encoding else "" 3066 3067 return self.func( 3068 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3069 *expression.expressions, 3070 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3071 ) 3072 3073 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3074 return self.jsonobject_sql(expression) 3075 3076 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3077 null_handling = expression.args.get("null_handling") 3078 null_handling = f" {null_handling}" if null_handling else "" 3079 return_type = self.sql(expression, "return_type") 3080 return_type = f" RETURNING {return_type}" if return_type else "" 3081 strict = " STRICT" if expression.args.get("strict") else "" 3082 return self.func( 3083 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3084 ) 3085 3086 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3087 this = self.sql(expression, "this") 3088 order = self.sql(expression, "order") 3089 null_handling = expression.args.get("null_handling") 3090 null_handling = f" {null_handling}" if null_handling else "" 3091 return_type = self.sql(expression, "return_type") 3092 return_type = f" RETURNING {return_type}" if return_type else "" 3093 strict = " STRICT" if expression.args.get("strict") else "" 3094 return self.func( 3095 "JSON_ARRAYAGG", 3096 this, 3097 suffix=f"{order}{null_handling}{return_type}{strict})", 3098 ) 3099 3100 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3101 path = self.sql(expression, "path") 3102 path = f" PATH {path}" if path else "" 3103 nested_schema = self.sql(expression, "nested_schema") 3104 3105 if nested_schema: 3106 return f"NESTED{path} {nested_schema}" 3107 3108 this = self.sql(expression, "this") 3109 kind = self.sql(expression, "kind") 3110 kind = f" {kind}" if kind else "" 3111 return f"{this}{kind}{path}" 3112 3113 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3114 return self.func("COLUMNS", *expression.expressions) 3115 3116 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3117 this = self.sql(expression, "this") 3118 path = self.sql(expression, "path") 3119 path = f", {path}" if path else "" 3120 error_handling = expression.args.get("error_handling") 3121 error_handling = f" {error_handling}" if error_handling else "" 3122 empty_handling = expression.args.get("empty_handling") 3123 empty_handling = f" {empty_handling}" if empty_handling else "" 3124 schema = self.sql(expression, "schema") 3125 return self.func( 3126 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3127 ) 3128 3129 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3130 this = self.sql(expression, "this") 3131 kind = self.sql(expression, "kind") 3132 path = self.sql(expression, "path") 3133 path = f" {path}" if path else "" 3134 as_json = " AS JSON" if expression.args.get("as_json") else "" 3135 return f"{this} {kind}{path}{as_json}" 3136 3137 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3138 this = self.sql(expression, "this") 3139 path = self.sql(expression, "path") 3140 path = f", {path}" if path else "" 3141 expressions = self.expressions(expression) 3142 with_ = ( 3143 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3144 if expressions 3145 else "" 3146 ) 3147 return f"OPENJSON({this}{path}){with_}" 3148 3149 def in_sql(self, expression: exp.In) -> str: 3150 query = expression.args.get("query") 3151 unnest = expression.args.get("unnest") 3152 field = expression.args.get("field") 3153 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3154 3155 if query: 3156 in_sql = self.sql(query) 3157 elif unnest: 3158 in_sql = self.in_unnest_op(unnest) 3159 elif field: 3160 in_sql = self.sql(field) 3161 else: 3162 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3163 3164 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3165 3166 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3167 return f"(SELECT {self.sql(unnest)})" 3168 3169 def interval_sql(self, expression: exp.Interval) -> str: 3170 unit = self.sql(expression, "unit") 3171 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3172 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3173 unit = f" {unit}" if unit else "" 3174 3175 if self.SINGLE_STRING_INTERVAL: 3176 this = expression.this.name if expression.this else "" 3177 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3178 3179 this = self.sql(expression, "this") 3180 if this: 3181 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3182 this = f" {this}" if unwrapped else f" ({this})" 3183 3184 return f"INTERVAL{this}{unit}" 3185 3186 def return_sql(self, expression: exp.Return) -> str: 3187 return f"RETURN {self.sql(expression, 'this')}" 3188 3189 def reference_sql(self, expression: exp.Reference) -> str: 3190 this = self.sql(expression, "this") 3191 expressions = self.expressions(expression, flat=True) 3192 expressions = f"({expressions})" if expressions else "" 3193 options = self.expressions(expression, key="options", flat=True, sep=" ") 3194 options = f" {options}" if options else "" 3195 return f"REFERENCES {this}{expressions}{options}" 3196 3197 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3198 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3199 parent = expression.parent 3200 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3201 return self.func( 3202 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3203 ) 3204 3205 def paren_sql(self, expression: exp.Paren) -> str: 3206 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3207 return f"({sql}{self.seg(')', sep='')}" 3208 3209 def neg_sql(self, expression: exp.Neg) -> str: 3210 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3211 this_sql = self.sql(expression, "this") 3212 sep = " " if this_sql[0] == "-" else "" 3213 return f"-{sep}{this_sql}" 3214 3215 def not_sql(self, expression: exp.Not) -> str: 3216 return f"NOT {self.sql(expression, 'this')}" 3217 3218 def alias_sql(self, expression: exp.Alias) -> str: 3219 alias = self.sql(expression, "alias") 3220 alias = f" AS {alias}" if alias else "" 3221 return f"{self.sql(expression, 'this')}{alias}" 3222 3223 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3224 alias = expression.args["alias"] 3225 3226 parent = expression.parent 3227 pivot = parent and parent.parent 3228 3229 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3230 identifier_alias = isinstance(alias, exp.Identifier) 3231 literal_alias = isinstance(alias, exp.Literal) 3232 3233 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3234 alias.replace(exp.Literal.string(alias.output_name)) 3235 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3236 alias.replace(exp.to_identifier(alias.output_name)) 3237 3238 return self.alias_sql(expression) 3239 3240 def aliases_sql(self, expression: exp.Aliases) -> str: 3241 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3242 3243 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3244 this = self.sql(expression, "this") 3245 index = self.sql(expression, "expression") 3246 return f"{this} AT {index}" 3247 3248 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3249 this = self.sql(expression, "this") 3250 zone = self.sql(expression, "zone") 3251 return f"{this} AT TIME ZONE {zone}" 3252 3253 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3254 this = self.sql(expression, "this") 3255 zone = self.sql(expression, "zone") 3256 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3257 3258 def add_sql(self, expression: exp.Add) -> str: 3259 return self.binary(expression, "+") 3260 3261 def and_sql( 3262 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3263 ) -> str: 3264 return self.connector_sql(expression, "AND", stack) 3265 3266 def or_sql( 3267 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3268 ) -> str: 3269 return self.connector_sql(expression, "OR", stack) 3270 3271 def xor_sql( 3272 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3273 ) -> str: 3274 return self.connector_sql(expression, "XOR", stack) 3275 3276 def connector_sql( 3277 self, 3278 expression: exp.Connector, 3279 op: str, 3280 stack: t.Optional[t.List[str | exp.Expression]] = None, 3281 ) -> str: 3282 if stack is not None: 3283 if expression.expressions: 3284 stack.append(self.expressions(expression, sep=f" {op} ")) 3285 else: 3286 stack.append(expression.right) 3287 if expression.comments and self.comments: 3288 for comment in expression.comments: 3289 if comment: 3290 op += f" /*{self.sanitize_comment(comment)}*/" 3291 stack.extend((op, expression.left)) 3292 return op 3293 3294 stack = [expression] 3295 sqls: t.List[str] = [] 3296 ops = set() 3297 3298 while stack: 3299 node = stack.pop() 3300 if isinstance(node, exp.Connector): 3301 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3302 else: 3303 sql = self.sql(node) 3304 if sqls and sqls[-1] in ops: 3305 sqls[-1] += f" {sql}" 3306 else: 3307 sqls.append(sql) 3308 3309 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3310 return sep.join(sqls) 3311 3312 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3313 return self.binary(expression, "&") 3314 3315 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3316 return self.binary(expression, "<<") 3317 3318 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3319 return f"~{self.sql(expression, 'this')}" 3320 3321 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3322 return self.binary(expression, "|") 3323 3324 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3325 return self.binary(expression, ">>") 3326 3327 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3328 return self.binary(expression, "^") 3329 3330 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3331 format_sql = self.sql(expression, "format") 3332 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3333 to_sql = self.sql(expression, "to") 3334 to_sql = f" {to_sql}" if to_sql else "" 3335 action = self.sql(expression, "action") 3336 action = f" {action}" if action else "" 3337 default = self.sql(expression, "default") 3338 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3339 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3340 3341 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3342 zone = self.sql(expression, "this") 3343 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3344 3345 def collate_sql(self, expression: exp.Collate) -> str: 3346 if self.COLLATE_IS_FUNC: 3347 return self.function_fallback_sql(expression) 3348 return self.binary(expression, "COLLATE") 3349 3350 def command_sql(self, expression: exp.Command) -> str: 3351 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3352 3353 def comment_sql(self, expression: exp.Comment) -> str: 3354 this = self.sql(expression, "this") 3355 kind = expression.args["kind"] 3356 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3357 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3358 expression_sql = self.sql(expression, "expression") 3359 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3360 3361 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3362 this = self.sql(expression, "this") 3363 delete = " DELETE" if expression.args.get("delete") else "" 3364 recompress = self.sql(expression, "recompress") 3365 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3366 to_disk = self.sql(expression, "to_disk") 3367 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3368 to_volume = self.sql(expression, "to_volume") 3369 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3370 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3371 3372 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3373 where = self.sql(expression, "where") 3374 group = self.sql(expression, "group") 3375 aggregates = self.expressions(expression, key="aggregates") 3376 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3377 3378 if not (where or group or aggregates) and len(expression.expressions) == 1: 3379 return f"TTL {self.expressions(expression, flat=True)}" 3380 3381 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3382 3383 def transaction_sql(self, expression: exp.Transaction) -> str: 3384 return "BEGIN" 3385 3386 def commit_sql(self, expression: exp.Commit) -> str: 3387 chain = expression.args.get("chain") 3388 if chain is not None: 3389 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3390 3391 return f"COMMIT{chain or ''}" 3392 3393 def rollback_sql(self, expression: exp.Rollback) -> str: 3394 savepoint = expression.args.get("savepoint") 3395 savepoint = f" TO {savepoint}" if savepoint else "" 3396 return f"ROLLBACK{savepoint}" 3397 3398 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3399 this = self.sql(expression, "this") 3400 3401 dtype = self.sql(expression, "dtype") 3402 if dtype: 3403 collate = self.sql(expression, "collate") 3404 collate = f" COLLATE {collate}" if collate else "" 3405 using = self.sql(expression, "using") 3406 using = f" USING {using}" if using else "" 3407 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3408 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3409 3410 default = self.sql(expression, "default") 3411 if default: 3412 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3413 3414 comment = self.sql(expression, "comment") 3415 if comment: 3416 return f"ALTER COLUMN {this} COMMENT {comment}" 3417 3418 visible = expression.args.get("visible") 3419 if visible: 3420 return f"ALTER COLUMN {this} SET {visible}" 3421 3422 allow_null = expression.args.get("allow_null") 3423 drop = expression.args.get("drop") 3424 3425 if not drop and not allow_null: 3426 self.unsupported("Unsupported ALTER COLUMN syntax") 3427 3428 if allow_null is not None: 3429 keyword = "DROP" if drop else "SET" 3430 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3431 3432 return f"ALTER COLUMN {this} DROP DEFAULT" 3433 3434 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3435 this = self.sql(expression, "this") 3436 3437 visible = expression.args.get("visible") 3438 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3439 3440 return f"ALTER INDEX {this} {visible_sql}" 3441 3442 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3443 this = self.sql(expression, "this") 3444 if not isinstance(expression.this, exp.Var): 3445 this = f"KEY DISTKEY {this}" 3446 return f"ALTER DISTSTYLE {this}" 3447 3448 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3449 compound = " COMPOUND" if expression.args.get("compound") else "" 3450 this = self.sql(expression, "this") 3451 expressions = self.expressions(expression, flat=True) 3452 expressions = f"({expressions})" if expressions else "" 3453 return f"ALTER{compound} SORTKEY {this or expressions}" 3454 3455 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3456 if not self.RENAME_TABLE_WITH_DB: 3457 # Remove db from tables 3458 expression = expression.transform( 3459 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3460 ).assert_is(exp.AlterRename) 3461 this = self.sql(expression, "this") 3462 return f"RENAME TO {this}" 3463 3464 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3465 exists = " IF EXISTS" if expression.args.get("exists") else "" 3466 old_column = self.sql(expression, "this") 3467 new_column = self.sql(expression, "to") 3468 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3469 3470 def alterset_sql(self, expression: exp.AlterSet) -> str: 3471 exprs = self.expressions(expression, flat=True) 3472 if self.ALTER_SET_WRAPPED: 3473 exprs = f"({exprs})" 3474 3475 return f"SET {exprs}" 3476 3477 def alter_sql(self, expression: exp.Alter) -> str: 3478 actions = expression.args["actions"] 3479 3480 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3481 actions[0], exp.ColumnDef 3482 ): 3483 actions_sql = self.expressions(expression, key="actions", flat=True) 3484 actions_sql = f"ADD {actions_sql}" 3485 else: 3486 actions_list = [] 3487 for action in actions: 3488 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3489 action_sql = self.add_column_sql(action) 3490 else: 3491 action_sql = self.sql(action) 3492 if isinstance(action, exp.Query): 3493 action_sql = f"AS {action_sql}" 3494 3495 actions_list.append(action_sql) 3496 3497 actions_sql = self.format_args(*actions_list).lstrip("\n") 3498 3499 exists = " IF EXISTS" if expression.args.get("exists") else "" 3500 on_cluster = self.sql(expression, "cluster") 3501 on_cluster = f" {on_cluster}" if on_cluster else "" 3502 only = " ONLY" if expression.args.get("only") else "" 3503 options = self.expressions(expression, key="options") 3504 options = f", {options}" if options else "" 3505 kind = self.sql(expression, "kind") 3506 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3507 3508 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}" 3509 3510 def add_column_sql(self, expression: exp.Expression) -> str: 3511 sql = self.sql(expression) 3512 if isinstance(expression, exp.Schema): 3513 column_text = " COLUMNS" 3514 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3515 column_text = " COLUMN" 3516 else: 3517 column_text = "" 3518 3519 return f"ADD{column_text} {sql}" 3520 3521 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3522 expressions = self.expressions(expression) 3523 exists = " IF EXISTS " if expression.args.get("exists") else " " 3524 return f"DROP{exists}{expressions}" 3525 3526 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3527 return f"ADD {self.expressions(expression, indent=False)}" 3528 3529 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3530 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3531 return f"ADD {exists}{self.sql(expression.this)}" 3532 3533 def distinct_sql(self, expression: exp.Distinct) -> str: 3534 this = self.expressions(expression, flat=True) 3535 3536 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3537 case = exp.case() 3538 for arg in expression.expressions: 3539 case = case.when(arg.is_(exp.null()), exp.null()) 3540 this = self.sql(case.else_(f"({this})")) 3541 3542 this = f" {this}" if this else "" 3543 3544 on = self.sql(expression, "on") 3545 on = f" ON {on}" if on else "" 3546 return f"DISTINCT{this}{on}" 3547 3548 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3549 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3550 3551 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3552 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3553 3554 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3555 this_sql = self.sql(expression, "this") 3556 expression_sql = self.sql(expression, "expression") 3557 kind = "MAX" if expression.args.get("max") else "MIN" 3558 return f"{this_sql} HAVING {kind} {expression_sql}" 3559 3560 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3561 return self.sql( 3562 exp.Cast( 3563 this=exp.Div(this=expression.this, expression=expression.expression), 3564 to=exp.DataType(this=exp.DataType.Type.INT), 3565 ) 3566 ) 3567 3568 def dpipe_sql(self, expression: exp.DPipe) -> str: 3569 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3570 return self.func( 3571 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3572 ) 3573 return self.binary(expression, "||") 3574 3575 def div_sql(self, expression: exp.Div) -> str: 3576 l, r = expression.left, expression.right 3577 3578 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3579 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3580 3581 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3582 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3583 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3584 3585 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3586 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3587 return self.sql( 3588 exp.cast( 3589 l / r, 3590 to=exp.DataType.Type.BIGINT, 3591 ) 3592 ) 3593 3594 return self.binary(expression, "/") 3595 3596 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3597 n = exp._wrap(expression.this, exp.Binary) 3598 d = exp._wrap(expression.expression, exp.Binary) 3599 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3600 3601 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3602 return self.binary(expression, "OVERLAPS") 3603 3604 def distance_sql(self, expression: exp.Distance) -> str: 3605 return self.binary(expression, "<->") 3606 3607 def dot_sql(self, expression: exp.Dot) -> str: 3608 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3609 3610 def eq_sql(self, expression: exp.EQ) -> str: 3611 return self.binary(expression, "=") 3612 3613 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3614 return self.binary(expression, ":=") 3615 3616 def escape_sql(self, expression: exp.Escape) -> str: 3617 return self.binary(expression, "ESCAPE") 3618 3619 def glob_sql(self, expression: exp.Glob) -> str: 3620 return self.binary(expression, "GLOB") 3621 3622 def gt_sql(self, expression: exp.GT) -> str: 3623 return self.binary(expression, ">") 3624 3625 def gte_sql(self, expression: exp.GTE) -> str: 3626 return self.binary(expression, ">=") 3627 3628 def ilike_sql(self, expression: exp.ILike) -> str: 3629 return self.binary(expression, "ILIKE") 3630 3631 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3632 return self.binary(expression, "ILIKE ANY") 3633 3634 def is_sql(self, expression: exp.Is) -> str: 3635 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3636 return self.sql( 3637 expression.this if expression.expression.this else exp.not_(expression.this) 3638 ) 3639 return self.binary(expression, "IS") 3640 3641 def like_sql(self, expression: exp.Like) -> str: 3642 return self.binary(expression, "LIKE") 3643 3644 def likeany_sql(self, expression: exp.LikeAny) -> str: 3645 return self.binary(expression, "LIKE ANY") 3646 3647 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3648 return self.binary(expression, "SIMILAR TO") 3649 3650 def lt_sql(self, expression: exp.LT) -> str: 3651 return self.binary(expression, "<") 3652 3653 def lte_sql(self, expression: exp.LTE) -> str: 3654 return self.binary(expression, "<=") 3655 3656 def mod_sql(self, expression: exp.Mod) -> str: 3657 return self.binary(expression, "%") 3658 3659 def mul_sql(self, expression: exp.Mul) -> str: 3660 return self.binary(expression, "*") 3661 3662 def neq_sql(self, expression: exp.NEQ) -> str: 3663 return self.binary(expression, "<>") 3664 3665 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3666 return self.binary(expression, "IS NOT DISTINCT FROM") 3667 3668 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3669 return self.binary(expression, "IS DISTINCT FROM") 3670 3671 def slice_sql(self, expression: exp.Slice) -> str: 3672 return self.binary(expression, ":") 3673 3674 def sub_sql(self, expression: exp.Sub) -> str: 3675 return self.binary(expression, "-") 3676 3677 def trycast_sql(self, expression: exp.TryCast) -> str: 3678 return self.cast_sql(expression, safe_prefix="TRY_") 3679 3680 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3681 return self.cast_sql(expression) 3682 3683 def try_sql(self, expression: exp.Try) -> str: 3684 if not self.TRY_SUPPORTED: 3685 self.unsupported("Unsupported TRY function") 3686 return self.sql(expression, "this") 3687 3688 return self.func("TRY", expression.this) 3689 3690 def log_sql(self, expression: exp.Log) -> str: 3691 this = expression.this 3692 expr = expression.expression 3693 3694 if self.dialect.LOG_BASE_FIRST is False: 3695 this, expr = expr, this 3696 elif self.dialect.LOG_BASE_FIRST is None and expr: 3697 if this.name in ("2", "10"): 3698 return self.func(f"LOG{this.name}", expr) 3699 3700 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3701 3702 return self.func("LOG", this, expr) 3703 3704 def use_sql(self, expression: exp.Use) -> str: 3705 kind = self.sql(expression, "kind") 3706 kind = f" {kind}" if kind else "" 3707 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3708 this = f" {this}" if this else "" 3709 return f"USE{kind}{this}" 3710 3711 def binary(self, expression: exp.Binary, op: str) -> str: 3712 sqls: t.List[str] = [] 3713 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3714 binary_type = type(expression) 3715 3716 while stack: 3717 node = stack.pop() 3718 3719 if type(node) is binary_type: 3720 op_func = node.args.get("operator") 3721 if op_func: 3722 op = f"OPERATOR({self.sql(op_func)})" 3723 3724 stack.append(node.right) 3725 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3726 stack.append(node.left) 3727 else: 3728 sqls.append(self.sql(node)) 3729 3730 return "".join(sqls) 3731 3732 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3733 to_clause = self.sql(expression, "to") 3734 if to_clause: 3735 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3736 3737 return self.function_fallback_sql(expression) 3738 3739 def function_fallback_sql(self, expression: exp.Func) -> str: 3740 args = [] 3741 3742 for key in expression.arg_types: 3743 arg_value = expression.args.get(key) 3744 3745 if isinstance(arg_value, list): 3746 for value in arg_value: 3747 args.append(value) 3748 elif arg_value is not None: 3749 args.append(arg_value) 3750 3751 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3752 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3753 else: 3754 name = expression.sql_name() 3755 3756 return self.func(name, *args) 3757 3758 def func( 3759 self, 3760 name: str, 3761 *args: t.Optional[exp.Expression | str], 3762 prefix: str = "(", 3763 suffix: str = ")", 3764 normalize: bool = True, 3765 ) -> str: 3766 name = self.normalize_func(name) if normalize else name 3767 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3768 3769 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3770 arg_sqls = tuple( 3771 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3772 ) 3773 if self.pretty and self.too_wide(arg_sqls): 3774 return self.indent( 3775 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3776 ) 3777 return sep.join(arg_sqls) 3778 3779 def too_wide(self, args: t.Iterable) -> bool: 3780 return sum(len(arg) for arg in args) > self.max_text_width 3781 3782 def format_time( 3783 self, 3784 expression: exp.Expression, 3785 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3786 inverse_time_trie: t.Optional[t.Dict] = None, 3787 ) -> t.Optional[str]: 3788 return format_time( 3789 self.sql(expression, "format"), 3790 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3791 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3792 ) 3793 3794 def expressions( 3795 self, 3796 expression: t.Optional[exp.Expression] = None, 3797 key: t.Optional[str] = None, 3798 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3799 flat: bool = False, 3800 indent: bool = True, 3801 skip_first: bool = False, 3802 skip_last: bool = False, 3803 sep: str = ", ", 3804 prefix: str = "", 3805 dynamic: bool = False, 3806 new_line: bool = False, 3807 ) -> str: 3808 expressions = expression.args.get(key or "expressions") if expression else sqls 3809 3810 if not expressions: 3811 return "" 3812 3813 if flat: 3814 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3815 3816 num_sqls = len(expressions) 3817 result_sqls = [] 3818 3819 for i, e in enumerate(expressions): 3820 sql = self.sql(e, comment=False) 3821 if not sql: 3822 continue 3823 3824 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3825 3826 if self.pretty: 3827 if self.leading_comma: 3828 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3829 else: 3830 result_sqls.append( 3831 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3832 ) 3833 else: 3834 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3835 3836 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3837 if new_line: 3838 result_sqls.insert(0, "") 3839 result_sqls.append("") 3840 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3841 else: 3842 result_sql = "".join(result_sqls) 3843 3844 return ( 3845 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3846 if indent 3847 else result_sql 3848 ) 3849 3850 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3851 flat = flat or isinstance(expression.parent, exp.Properties) 3852 expressions_sql = self.expressions(expression, flat=flat) 3853 if flat: 3854 return f"{op} {expressions_sql}" 3855 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3856 3857 def naked_property(self, expression: exp.Property) -> str: 3858 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3859 if not property_name: 3860 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3861 return f"{property_name} {self.sql(expression, 'this')}" 3862 3863 def tag_sql(self, expression: exp.Tag) -> str: 3864 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3865 3866 def token_sql(self, token_type: TokenType) -> str: 3867 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3868 3869 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3870 this = self.sql(expression, "this") 3871 expressions = self.no_identify(self.expressions, expression) 3872 expressions = ( 3873 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3874 ) 3875 return f"{this}{expressions}" if expressions.strip() != "" else this 3876 3877 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3878 this = self.sql(expression, "this") 3879 expressions = self.expressions(expression, flat=True) 3880 return f"{this}({expressions})" 3881 3882 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3883 return self.binary(expression, "=>") 3884 3885 def when_sql(self, expression: exp.When) -> str: 3886 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3887 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3888 condition = self.sql(expression, "condition") 3889 condition = f" AND {condition}" if condition else "" 3890 3891 then_expression = expression.args.get("then") 3892 if isinstance(then_expression, exp.Insert): 3893 this = self.sql(then_expression, "this") 3894 this = f"INSERT {this}" if this else "INSERT" 3895 then = self.sql(then_expression, "expression") 3896 then = f"{this} VALUES {then}" if then else this 3897 elif isinstance(then_expression, exp.Update): 3898 if isinstance(then_expression.args.get("expressions"), exp.Star): 3899 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3900 else: 3901 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3902 else: 3903 then = self.sql(then_expression) 3904 return f"WHEN {matched}{source}{condition} THEN {then}" 3905 3906 def whens_sql(self, expression: exp.Whens) -> str: 3907 return self.expressions(expression, sep=" ", indent=False) 3908 3909 def merge_sql(self, expression: exp.Merge) -> str: 3910 table = expression.this 3911 table_alias = "" 3912 3913 hints = table.args.get("hints") 3914 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3915 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3916 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3917 3918 this = self.sql(table) 3919 using = f"USING {self.sql(expression, 'using')}" 3920 on = f"ON {self.sql(expression, 'on')}" 3921 whens = self.sql(expression, "whens") 3922 3923 returning = self.sql(expression, "returning") 3924 if returning: 3925 whens = f"{whens}{returning}" 3926 3927 sep = self.sep() 3928 3929 return self.prepend_ctes( 3930 expression, 3931 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3932 ) 3933 3934 @unsupported_args("format") 3935 def tochar_sql(self, expression: exp.ToChar) -> str: 3936 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3937 3938 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3939 if not self.SUPPORTS_TO_NUMBER: 3940 self.unsupported("Unsupported TO_NUMBER function") 3941 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3942 3943 fmt = expression.args.get("format") 3944 if not fmt: 3945 self.unsupported("Conversion format is required for TO_NUMBER") 3946 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3947 3948 return self.func("TO_NUMBER", expression.this, fmt) 3949 3950 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3951 this = self.sql(expression, "this") 3952 kind = self.sql(expression, "kind") 3953 settings_sql = self.expressions(expression, key="settings", sep=" ") 3954 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3955 return f"{this}({kind}{args})" 3956 3957 def dictrange_sql(self, expression: exp.DictRange) -> str: 3958 this = self.sql(expression, "this") 3959 max = self.sql(expression, "max") 3960 min = self.sql(expression, "min") 3961 return f"{this}(MIN {min} MAX {max})" 3962 3963 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3964 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3965 3966 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3967 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3968 3969 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 3970 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 3971 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 3972 3973 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3974 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3975 expressions = self.expressions(expression, flat=True) 3976 expressions = f" {self.wrap(expressions)}" if expressions else "" 3977 buckets = self.sql(expression, "buckets") 3978 kind = self.sql(expression, "kind") 3979 buckets = f" BUCKETS {buckets}" if buckets else "" 3980 order = self.sql(expression, "order") 3981 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3982 3983 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3984 return "" 3985 3986 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3987 expressions = self.expressions(expression, key="expressions", flat=True) 3988 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3989 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3990 buckets = self.sql(expression, "buckets") 3991 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3992 3993 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3994 this = self.sql(expression, "this") 3995 having = self.sql(expression, "having") 3996 3997 if having: 3998 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3999 4000 return self.func("ANY_VALUE", this) 4001 4002 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4003 transform = self.func("TRANSFORM", *expression.expressions) 4004 row_format_before = self.sql(expression, "row_format_before") 4005 row_format_before = f" {row_format_before}" if row_format_before else "" 4006 record_writer = self.sql(expression, "record_writer") 4007 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4008 using = f" USING {self.sql(expression, 'command_script')}" 4009 schema = self.sql(expression, "schema") 4010 schema = f" AS {schema}" if schema else "" 4011 row_format_after = self.sql(expression, "row_format_after") 4012 row_format_after = f" {row_format_after}" if row_format_after else "" 4013 record_reader = self.sql(expression, "record_reader") 4014 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4015 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4016 4017 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4018 key_block_size = self.sql(expression, "key_block_size") 4019 if key_block_size: 4020 return f"KEY_BLOCK_SIZE = {key_block_size}" 4021 4022 using = self.sql(expression, "using") 4023 if using: 4024 return f"USING {using}" 4025 4026 parser = self.sql(expression, "parser") 4027 if parser: 4028 return f"WITH PARSER {parser}" 4029 4030 comment = self.sql(expression, "comment") 4031 if comment: 4032 return f"COMMENT {comment}" 4033 4034 visible = expression.args.get("visible") 4035 if visible is not None: 4036 return "VISIBLE" if visible else "INVISIBLE" 4037 4038 engine_attr = self.sql(expression, "engine_attr") 4039 if engine_attr: 4040 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4041 4042 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4043 if secondary_engine_attr: 4044 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4045 4046 self.unsupported("Unsupported index constraint option.") 4047 return "" 4048 4049 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4050 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4051 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4052 4053 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4054 kind = self.sql(expression, "kind") 4055 kind = f"{kind} INDEX" if kind else "INDEX" 4056 this = self.sql(expression, "this") 4057 this = f" {this}" if this else "" 4058 index_type = self.sql(expression, "index_type") 4059 index_type = f" USING {index_type}" if index_type else "" 4060 expressions = self.expressions(expression, flat=True) 4061 expressions = f" ({expressions})" if expressions else "" 4062 options = self.expressions(expression, key="options", sep=" ") 4063 options = f" {options}" if options else "" 4064 return f"{kind}{this}{index_type}{expressions}{options}" 4065 4066 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4067 if self.NVL2_SUPPORTED: 4068 return self.function_fallback_sql(expression) 4069 4070 case = exp.Case().when( 4071 expression.this.is_(exp.null()).not_(copy=False), 4072 expression.args["true"], 4073 copy=False, 4074 ) 4075 else_cond = expression.args.get("false") 4076 if else_cond: 4077 case.else_(else_cond, copy=False) 4078 4079 return self.sql(case) 4080 4081 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4082 this = self.sql(expression, "this") 4083 expr = self.sql(expression, "expression") 4084 iterator = self.sql(expression, "iterator") 4085 condition = self.sql(expression, "condition") 4086 condition = f" IF {condition}" if condition else "" 4087 return f"{this} FOR {expr} IN {iterator}{condition}" 4088 4089 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4090 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4091 4092 def opclass_sql(self, expression: exp.Opclass) -> str: 4093 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4094 4095 def predict_sql(self, expression: exp.Predict) -> str: 4096 model = self.sql(expression, "this") 4097 model = f"MODEL {model}" 4098 table = self.sql(expression, "expression") 4099 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4100 parameters = self.sql(expression, "params_struct") 4101 return self.func("PREDICT", model, table, parameters or None) 4102 4103 def forin_sql(self, expression: exp.ForIn) -> str: 4104 this = self.sql(expression, "this") 4105 expression_sql = self.sql(expression, "expression") 4106 return f"FOR {this} DO {expression_sql}" 4107 4108 def refresh_sql(self, expression: exp.Refresh) -> str: 4109 this = self.sql(expression, "this") 4110 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4111 return f"REFRESH {table}{this}" 4112 4113 def toarray_sql(self, expression: exp.ToArray) -> str: 4114 arg = expression.this 4115 if not arg.type: 4116 from sqlglot.optimizer.annotate_types import annotate_types 4117 4118 arg = annotate_types(arg, dialect=self.dialect) 4119 4120 if arg.is_type(exp.DataType.Type.ARRAY): 4121 return self.sql(arg) 4122 4123 cond_for_null = arg.is_(exp.null()) 4124 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4125 4126 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4127 this = expression.this 4128 time_format = self.format_time(expression) 4129 4130 if time_format: 4131 return self.sql( 4132 exp.cast( 4133 exp.StrToTime(this=this, format=expression.args["format"]), 4134 exp.DataType.Type.TIME, 4135 ) 4136 ) 4137 4138 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4139 return self.sql(this) 4140 4141 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4142 4143 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4144 this = expression.this 4145 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4146 return self.sql(this) 4147 4148 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4149 4150 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4151 this = expression.this 4152 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4153 return self.sql(this) 4154 4155 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4156 4157 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4158 this = expression.this 4159 time_format = self.format_time(expression) 4160 4161 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4162 return self.sql( 4163 exp.cast( 4164 exp.StrToTime(this=this, format=expression.args["format"]), 4165 exp.DataType.Type.DATE, 4166 ) 4167 ) 4168 4169 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4170 return self.sql(this) 4171 4172 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4173 4174 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4175 return self.sql( 4176 exp.func( 4177 "DATEDIFF", 4178 expression.this, 4179 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4180 "day", 4181 ) 4182 ) 4183 4184 def lastday_sql(self, expression: exp.LastDay) -> str: 4185 if self.LAST_DAY_SUPPORTS_DATE_PART: 4186 return self.function_fallback_sql(expression) 4187 4188 unit = expression.text("unit") 4189 if unit and unit != "MONTH": 4190 self.unsupported("Date parts are not supported in LAST_DAY.") 4191 4192 return self.func("LAST_DAY", expression.this) 4193 4194 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4195 from sqlglot.dialects.dialect import unit_to_str 4196 4197 return self.func( 4198 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4199 ) 4200 4201 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4202 if self.CAN_IMPLEMENT_ARRAY_ANY: 4203 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4204 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4205 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4206 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4207 4208 from sqlglot.dialects import Dialect 4209 4210 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4211 if self.dialect.__class__ != Dialect: 4212 self.unsupported("ARRAY_ANY is unsupported") 4213 4214 return self.function_fallback_sql(expression) 4215 4216 def struct_sql(self, expression: exp.Struct) -> str: 4217 expression.set( 4218 "expressions", 4219 [ 4220 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4221 if isinstance(e, exp.PropertyEQ) 4222 else e 4223 for e in expression.expressions 4224 ], 4225 ) 4226 4227 return self.function_fallback_sql(expression) 4228 4229 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4230 low = self.sql(expression, "this") 4231 high = self.sql(expression, "expression") 4232 4233 return f"{low} TO {high}" 4234 4235 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4236 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4237 tables = f" {self.expressions(expression)}" 4238 4239 exists = " IF EXISTS" if expression.args.get("exists") else "" 4240 4241 on_cluster = self.sql(expression, "cluster") 4242 on_cluster = f" {on_cluster}" if on_cluster else "" 4243 4244 identity = self.sql(expression, "identity") 4245 identity = f" {identity} IDENTITY" if identity else "" 4246 4247 option = self.sql(expression, "option") 4248 option = f" {option}" if option else "" 4249 4250 partition = self.sql(expression, "partition") 4251 partition = f" {partition}" if partition else "" 4252 4253 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4254 4255 # This transpiles T-SQL's CONVERT function 4256 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4257 def convert_sql(self, expression: exp.Convert) -> str: 4258 to = expression.this 4259 value = expression.expression 4260 style = expression.args.get("style") 4261 safe = expression.args.get("safe") 4262 strict = expression.args.get("strict") 4263 4264 if not to or not value: 4265 return "" 4266 4267 # Retrieve length of datatype and override to default if not specified 4268 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4269 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4270 4271 transformed: t.Optional[exp.Expression] = None 4272 cast = exp.Cast if strict else exp.TryCast 4273 4274 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4275 if isinstance(style, exp.Literal) and style.is_int: 4276 from sqlglot.dialects.tsql import TSQL 4277 4278 style_value = style.name 4279 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4280 if not converted_style: 4281 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4282 4283 fmt = exp.Literal.string(converted_style) 4284 4285 if to.this == exp.DataType.Type.DATE: 4286 transformed = exp.StrToDate(this=value, format=fmt) 4287 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4288 transformed = exp.StrToTime(this=value, format=fmt) 4289 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4290 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4291 elif to.this == exp.DataType.Type.TEXT: 4292 transformed = exp.TimeToStr(this=value, format=fmt) 4293 4294 if not transformed: 4295 transformed = cast(this=value, to=to, safe=safe) 4296 4297 return self.sql(transformed) 4298 4299 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4300 this = expression.this 4301 if isinstance(this, exp.JSONPathWildcard): 4302 this = self.json_path_part(this) 4303 return f".{this}" if this else "" 4304 4305 if exp.SAFE_IDENTIFIER_RE.match(this): 4306 return f".{this}" 4307 4308 this = self.json_path_part(this) 4309 return ( 4310 f"[{this}]" 4311 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4312 else f".{this}" 4313 ) 4314 4315 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4316 this = self.json_path_part(expression.this) 4317 return f"[{this}]" if this else "" 4318 4319 def _simplify_unless_literal(self, expression: E) -> E: 4320 if not isinstance(expression, exp.Literal): 4321 from sqlglot.optimizer.simplify import simplify 4322 4323 expression = simplify(expression, dialect=self.dialect) 4324 4325 return expression 4326 4327 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4328 this = expression.this 4329 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4330 self.unsupported( 4331 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4332 ) 4333 return self.sql(this) 4334 4335 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4336 # The first modifier here will be the one closest to the AggFunc's arg 4337 mods = sorted( 4338 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4339 key=lambda x: 0 4340 if isinstance(x, exp.HavingMax) 4341 else (1 if isinstance(x, exp.Order) else 2), 4342 ) 4343 4344 if mods: 4345 mod = mods[0] 4346 this = expression.__class__(this=mod.this.copy()) 4347 this.meta["inline"] = True 4348 mod.this.replace(this) 4349 return self.sql(expression.this) 4350 4351 agg_func = expression.find(exp.AggFunc) 4352 4353 if agg_func: 4354 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4355 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4356 4357 return f"{self.sql(expression, 'this')} {text}" 4358 4359 def _replace_line_breaks(self, string: str) -> str: 4360 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4361 if self.pretty: 4362 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4363 return string 4364 4365 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4366 option = self.sql(expression, "this") 4367 4368 if expression.expressions: 4369 upper = option.upper() 4370 4371 # Snowflake FILE_FORMAT options are separated by whitespace 4372 sep = " " if upper == "FILE_FORMAT" else ", " 4373 4374 # Databricks copy/format options do not set their list of values with EQ 4375 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4376 values = self.expressions(expression, flat=True, sep=sep) 4377 return f"{option}{op}({values})" 4378 4379 value = self.sql(expression, "expression") 4380 4381 if not value: 4382 return option 4383 4384 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4385 4386 return f"{option}{op}{value}" 4387 4388 def credentials_sql(self, expression: exp.Credentials) -> str: 4389 cred_expr = expression.args.get("credentials") 4390 if isinstance(cred_expr, exp.Literal): 4391 # Redshift case: CREDENTIALS <string> 4392 credentials = self.sql(expression, "credentials") 4393 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4394 else: 4395 # Snowflake case: CREDENTIALS = (...) 4396 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4397 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4398 4399 storage = self.sql(expression, "storage") 4400 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4401 4402 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4403 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4404 4405 iam_role = self.sql(expression, "iam_role") 4406 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4407 4408 region = self.sql(expression, "region") 4409 region = f" REGION {region}" if region else "" 4410 4411 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4412 4413 def copy_sql(self, expression: exp.Copy) -> str: 4414 this = self.sql(expression, "this") 4415 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4416 4417 credentials = self.sql(expression, "credentials") 4418 credentials = self.seg(credentials) if credentials else "" 4419 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4420 files = self.expressions(expression, key="files", flat=True) 4421 4422 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4423 params = self.expressions( 4424 expression, 4425 key="params", 4426 sep=sep, 4427 new_line=True, 4428 skip_last=True, 4429 skip_first=True, 4430 indent=self.COPY_PARAMS_ARE_WRAPPED, 4431 ) 4432 4433 if params: 4434 if self.COPY_PARAMS_ARE_WRAPPED: 4435 params = f" WITH ({params})" 4436 elif not self.pretty: 4437 params = f" {params}" 4438 4439 return f"COPY{this}{kind} {files}{credentials}{params}" 4440 4441 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4442 return "" 4443 4444 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4445 on_sql = "ON" if expression.args.get("on") else "OFF" 4446 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4447 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4448 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4449 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4450 4451 if filter_col or retention_period: 4452 on_sql = self.func("ON", filter_col, retention_period) 4453 4454 return f"DATA_DELETION={on_sql}" 4455 4456 def maskingpolicycolumnconstraint_sql( 4457 self, expression: exp.MaskingPolicyColumnConstraint 4458 ) -> str: 4459 this = self.sql(expression, "this") 4460 expressions = self.expressions(expression, flat=True) 4461 expressions = f" USING ({expressions})" if expressions else "" 4462 return f"MASKING POLICY {this}{expressions}" 4463 4464 def gapfill_sql(self, expression: exp.GapFill) -> str: 4465 this = self.sql(expression, "this") 4466 this = f"TABLE {this}" 4467 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4468 4469 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4470 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4471 4472 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4473 this = self.sql(expression, "this") 4474 expr = expression.expression 4475 4476 if isinstance(expr, exp.Func): 4477 # T-SQL's CLR functions are case sensitive 4478 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4479 else: 4480 expr = self.sql(expression, "expression") 4481 4482 return self.scope_resolution(expr, this) 4483 4484 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4485 if self.PARSE_JSON_NAME is None: 4486 return self.sql(expression.this) 4487 4488 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4489 4490 def rand_sql(self, expression: exp.Rand) -> str: 4491 lower = self.sql(expression, "lower") 4492 upper = self.sql(expression, "upper") 4493 4494 if lower and upper: 4495 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4496 return self.func("RAND", expression.this) 4497 4498 def changes_sql(self, expression: exp.Changes) -> str: 4499 information = self.sql(expression, "information") 4500 information = f"INFORMATION => {information}" 4501 at_before = self.sql(expression, "at_before") 4502 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4503 end = self.sql(expression, "end") 4504 end = f"{self.seg('')}{end}" if end else "" 4505 4506 return f"CHANGES ({information}){at_before}{end}" 4507 4508 def pad_sql(self, expression: exp.Pad) -> str: 4509 prefix = "L" if expression.args.get("is_left") else "R" 4510 4511 fill_pattern = self.sql(expression, "fill_pattern") or None 4512 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4513 fill_pattern = "' '" 4514 4515 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4516 4517 def summarize_sql(self, expression: exp.Summarize) -> str: 4518 table = " TABLE" if expression.args.get("table") else "" 4519 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4520 4521 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4522 generate_series = exp.GenerateSeries(**expression.args) 4523 4524 parent = expression.parent 4525 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4526 parent = parent.parent 4527 4528 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4529 return self.sql(exp.Unnest(expressions=[generate_series])) 4530 4531 if isinstance(parent, exp.Select): 4532 self.unsupported("GenerateSeries projection unnesting is not supported.") 4533 4534 return self.sql(generate_series) 4535 4536 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4537 exprs = expression.expressions 4538 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4539 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4540 else: 4541 rhs = self.expressions(expression) 4542 4543 return self.func(name, expression.this, rhs or None) 4544 4545 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4546 if self.SUPPORTS_CONVERT_TIMEZONE: 4547 return self.function_fallback_sql(expression) 4548 4549 source_tz = expression.args.get("source_tz") 4550 target_tz = expression.args.get("target_tz") 4551 timestamp = expression.args.get("timestamp") 4552 4553 if source_tz and timestamp: 4554 timestamp = exp.AtTimeZone( 4555 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4556 ) 4557 4558 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4559 4560 return self.sql(expr) 4561 4562 def json_sql(self, expression: exp.JSON) -> str: 4563 this = self.sql(expression, "this") 4564 this = f" {this}" if this else "" 4565 4566 _with = expression.args.get("with") 4567 4568 if _with is None: 4569 with_sql = "" 4570 elif not _with: 4571 with_sql = " WITHOUT" 4572 else: 4573 with_sql = " WITH" 4574 4575 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4576 4577 return f"JSON{this}{with_sql}{unique_sql}" 4578 4579 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4580 def _generate_on_options(arg: t.Any) -> str: 4581 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4582 4583 path = self.sql(expression, "path") 4584 returning = self.sql(expression, "returning") 4585 returning = f" RETURNING {returning}" if returning else "" 4586 4587 on_condition = self.sql(expression, "on_condition") 4588 on_condition = f" {on_condition}" if on_condition else "" 4589 4590 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4591 4592 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4593 else_ = "ELSE " if expression.args.get("else_") else "" 4594 condition = self.sql(expression, "expression") 4595 condition = f"WHEN {condition} THEN " if condition else else_ 4596 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4597 return f"{condition}{insert}" 4598 4599 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4600 kind = self.sql(expression, "kind") 4601 expressions = self.seg(self.expressions(expression, sep=" ")) 4602 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4603 return res 4604 4605 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4606 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4607 empty = expression.args.get("empty") 4608 empty = ( 4609 f"DEFAULT {empty} ON EMPTY" 4610 if isinstance(empty, exp.Expression) 4611 else self.sql(expression, "empty") 4612 ) 4613 4614 error = expression.args.get("error") 4615 error = ( 4616 f"DEFAULT {error} ON ERROR" 4617 if isinstance(error, exp.Expression) 4618 else self.sql(expression, "error") 4619 ) 4620 4621 if error and empty: 4622 error = ( 4623 f"{empty} {error}" 4624 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4625 else f"{error} {empty}" 4626 ) 4627 empty = "" 4628 4629 null = self.sql(expression, "null") 4630 4631 return f"{empty}{error}{null}" 4632 4633 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4634 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4635 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4636 4637 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4638 this = self.sql(expression, "this") 4639 path = self.sql(expression, "path") 4640 4641 passing = self.expressions(expression, "passing") 4642 passing = f" PASSING {passing}" if passing else "" 4643 4644 on_condition = self.sql(expression, "on_condition") 4645 on_condition = f" {on_condition}" if on_condition else "" 4646 4647 path = f"{path}{passing}{on_condition}" 4648 4649 return self.func("JSON_EXISTS", this, path) 4650 4651 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4652 array_agg = self.function_fallback_sql(expression) 4653 4654 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4655 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4656 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4657 parent = expression.parent 4658 if isinstance(parent, exp.Filter): 4659 parent_cond = parent.expression.this 4660 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4661 else: 4662 this = expression.this 4663 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4664 if this.find(exp.Column): 4665 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4666 this_sql = ( 4667 self.expressions(this) 4668 if isinstance(this, exp.Distinct) 4669 else self.sql(expression, "this") 4670 ) 4671 4672 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4673 4674 return array_agg 4675 4676 def apply_sql(self, expression: exp.Apply) -> str: 4677 this = self.sql(expression, "this") 4678 expr = self.sql(expression, "expression") 4679 4680 return f"{this} APPLY({expr})" 4681 4682 def grant_sql(self, expression: exp.Grant) -> str: 4683 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4684 4685 kind = self.sql(expression, "kind") 4686 kind = f" {kind}" if kind else "" 4687 4688 securable = self.sql(expression, "securable") 4689 securable = f" {securable}" if securable else "" 4690 4691 principals = self.expressions(expression, key="principals", flat=True) 4692 4693 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4694 4695 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4696 4697 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4698 this = self.sql(expression, "this") 4699 columns = self.expressions(expression, flat=True) 4700 columns = f"({columns})" if columns else "" 4701 4702 return f"{this}{columns}" 4703 4704 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4705 this = self.sql(expression, "this") 4706 4707 kind = self.sql(expression, "kind") 4708 kind = f"{kind} " if kind else "" 4709 4710 return f"{kind}{this}" 4711 4712 def columns_sql(self, expression: exp.Columns): 4713 func = self.function_fallback_sql(expression) 4714 if expression.args.get("unpack"): 4715 func = f"*{func}" 4716 4717 return func 4718 4719 def overlay_sql(self, expression: exp.Overlay): 4720 this = self.sql(expression, "this") 4721 expr = self.sql(expression, "expression") 4722 from_sql = self.sql(expression, "from") 4723 for_sql = self.sql(expression, "for") 4724 for_sql = f" FOR {for_sql}" if for_sql else "" 4725 4726 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4727 4728 @unsupported_args("format") 4729 def todouble_sql(self, expression: exp.ToDouble) -> str: 4730 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4731 4732 def string_sql(self, expression: exp.String) -> str: 4733 this = expression.this 4734 zone = expression.args.get("zone") 4735 4736 if zone: 4737 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4738 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4739 # set for source_tz to transpile the time conversion before the STRING cast 4740 this = exp.ConvertTimezone( 4741 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4742 ) 4743 4744 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4745 4746 def median_sql(self, expression: exp.Median): 4747 if not self.SUPPORTS_MEDIAN: 4748 return self.sql( 4749 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4750 ) 4751 4752 return self.function_fallback_sql(expression) 4753 4754 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4755 filler = self.sql(expression, "this") 4756 filler = f" {filler}" if filler else "" 4757 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4758 return f"TRUNCATE{filler} {with_count}" 4759 4760 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4761 if self.SUPPORTS_UNIX_SECONDS: 4762 return self.function_fallback_sql(expression) 4763 4764 start_ts = exp.cast( 4765 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4766 ) 4767 4768 return self.sql( 4769 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4770 ) 4771 4772 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4773 dim = expression.expression 4774 4775 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4776 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4777 if not (dim.is_int and dim.name == "1"): 4778 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4779 dim = None 4780 4781 # If dimension is required but not specified, default initialize it 4782 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4783 dim = exp.Literal.number(1) 4784 4785 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4786 4787 def attach_sql(self, expression: exp.Attach) -> str: 4788 this = self.sql(expression, "this") 4789 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4790 expressions = self.expressions(expression) 4791 expressions = f" ({expressions})" if expressions else "" 4792 4793 return f"ATTACH{exists_sql} {this}{expressions}" 4794 4795 def detach_sql(self, expression: exp.Detach) -> str: 4796 this = self.sql(expression, "this") 4797 # the DATABASE keyword is required if IF EXISTS is set 4798 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4799 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4800 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4801 4802 return f"DETACH{exists_sql} {this}" 4803 4804 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4805 this = self.sql(expression, "this") 4806 value = self.sql(expression, "expression") 4807 value = f" {value}" if value else "" 4808 return f"{this}{value}" 4809 4810 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4811 this_sql = self.sql(expression, "this") 4812 if isinstance(expression.this, exp.Table): 4813 this_sql = f"TABLE {this_sql}" 4814 4815 return self.func( 4816 "FEATURES_AT_TIME", 4817 this_sql, 4818 expression.args.get("time"), 4819 expression.args.get("num_rows"), 4820 expression.args.get("ignore_feature_nulls"), 4821 ) 4822 4823 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4824 return ( 4825 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4826 ) 4827 4828 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4829 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4830 encode = f"{encode} {self.sql(expression, 'this')}" 4831 4832 properties = expression.args.get("properties") 4833 if properties: 4834 encode = f"{encode} {self.properties(properties)}" 4835 4836 return encode 4837 4838 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4839 this = self.sql(expression, "this") 4840 include = f"INCLUDE {this}" 4841 4842 column_def = self.sql(expression, "column_def") 4843 if column_def: 4844 include = f"{include} {column_def}" 4845 4846 alias = self.sql(expression, "alias") 4847 if alias: 4848 include = f"{include} AS {alias}" 4849 4850 return include 4851 4852 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4853 name = f"NAME {self.sql(expression, 'this')}" 4854 return self.func("XMLELEMENT", name, *expression.expressions) 4855 4856 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 4857 this = self.sql(expression, "this") 4858 expr = self.sql(expression, "expression") 4859 expr = f"({expr})" if expr else "" 4860 return f"{this}{expr}" 4861 4862 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4863 partitions = self.expressions(expression, "partition_expressions") 4864 create = self.expressions(expression, "create_expressions") 4865 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4866 4867 def partitionbyrangepropertydynamic_sql( 4868 self, expression: exp.PartitionByRangePropertyDynamic 4869 ) -> str: 4870 start = self.sql(expression, "start") 4871 end = self.sql(expression, "end") 4872 4873 every = expression.args["every"] 4874 if isinstance(every, exp.Interval) and every.this.is_string: 4875 every.this.replace(exp.Literal.number(every.name)) 4876 4877 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4878 4879 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4880 name = self.sql(expression, "this") 4881 values = self.expressions(expression, flat=True) 4882 4883 return f"NAME {name} VALUE {values}" 4884 4885 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4886 kind = self.sql(expression, "kind") 4887 sample = self.sql(expression, "sample") 4888 return f"SAMPLE {sample} {kind}" 4889 4890 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4891 kind = self.sql(expression, "kind") 4892 option = self.sql(expression, "option") 4893 option = f" {option}" if option else "" 4894 this = self.sql(expression, "this") 4895 this = f" {this}" if this else "" 4896 columns = self.expressions(expression) 4897 columns = f" {columns}" if columns else "" 4898 return f"{kind}{option} STATISTICS{this}{columns}" 4899 4900 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4901 this = self.sql(expression, "this") 4902 columns = self.expressions(expression) 4903 inner_expression = self.sql(expression, "expression") 4904 inner_expression = f" {inner_expression}" if inner_expression else "" 4905 update_options = self.sql(expression, "update_options") 4906 update_options = f" {update_options} UPDATE" if update_options else "" 4907 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4908 4909 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4910 kind = self.sql(expression, "kind") 4911 kind = f" {kind}" if kind else "" 4912 return f"DELETE{kind} STATISTICS" 4913 4914 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4915 inner_expression = self.sql(expression, "expression") 4916 return f"LIST CHAINED ROWS{inner_expression}" 4917 4918 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4919 kind = self.sql(expression, "kind") 4920 this = self.sql(expression, "this") 4921 this = f" {this}" if this else "" 4922 inner_expression = self.sql(expression, "expression") 4923 return f"VALIDATE {kind}{this}{inner_expression}" 4924 4925 def analyze_sql(self, expression: exp.Analyze) -> str: 4926 options = self.expressions(expression, key="options", sep=" ") 4927 options = f" {options}" if options else "" 4928 kind = self.sql(expression, "kind") 4929 kind = f" {kind}" if kind else "" 4930 this = self.sql(expression, "this") 4931 this = f" {this}" if this else "" 4932 mode = self.sql(expression, "mode") 4933 mode = f" {mode}" if mode else "" 4934 properties = self.sql(expression, "properties") 4935 properties = f" {properties}" if properties else "" 4936 partition = self.sql(expression, "partition") 4937 partition = f" {partition}" if partition else "" 4938 inner_expression = self.sql(expression, "expression") 4939 inner_expression = f" {inner_expression}" if inner_expression else "" 4940 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 4941 4942 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4943 this = self.sql(expression, "this") 4944 namespaces = self.expressions(expression, key="namespaces") 4945 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4946 passing = self.expressions(expression, key="passing") 4947 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4948 columns = self.expressions(expression, key="columns") 4949 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4950 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4951 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 4952 4953 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 4954 this = self.sql(expression, "this") 4955 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 4956 4957 def export_sql(self, expression: exp.Export) -> str: 4958 this = self.sql(expression, "this") 4959 connection = self.sql(expression, "connection") 4960 connection = f"WITH CONNECTION {connection} " if connection else "" 4961 options = self.sql(expression, "options") 4962 return f"EXPORT DATA {connection}{options} AS {this}" 4963 4964 def declare_sql(self, expression: exp.Declare) -> str: 4965 return f"DECLARE {self.expressions(expression, flat=True)}" 4966 4967 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4968 variable = self.sql(expression, "this") 4969 default = self.sql(expression, "default") 4970 default = f" = {default}" if default else "" 4971 4972 kind = self.sql(expression, "kind") 4973 if isinstance(expression.args.get("kind"), exp.Schema): 4974 kind = f"TABLE {kind}" 4975 4976 return f"{variable} AS {kind}{default}" 4977 4978 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 4979 kind = self.sql(expression, "kind") 4980 this = self.sql(expression, "this") 4981 set = self.sql(expression, "expression") 4982 using = self.sql(expression, "using") 4983 using = f" USING {using}" if using else "" 4984 4985 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 4986 4987 return f"{kind_sql} {this} SET {set}{using}" 4988 4989 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 4990 params = self.expressions(expression, key="params", flat=True) 4991 return self.func(expression.name, *expression.expressions) + f"({params})" 4992 4993 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 4994 return self.func(expression.name, *expression.expressions) 4995 4996 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 4997 return self.anonymousaggfunc_sql(expression) 4998 4999 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5000 return self.parameterizedagg_sql(expression) 5001 5002 def show_sql(self, expression: exp.Show) -> str: 5003 self.unsupported("Unsupported SHOW statement") 5004 return "" 5005 5006 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5007 # Snowflake GET/PUT statements: 5008 # PUT <file> <internalStage> <properties> 5009 # GET <internalStage> <file> <properties> 5010 props = expression.args.get("properties") 5011 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5012 this = self.sql(expression, "this") 5013 target = self.sql(expression, "target") 5014 5015 if isinstance(expression, exp.Put): 5016 return f"PUT {this} {target}{props_sql}" 5017 else: 5018 return f"GET {target} {this}{props_sql}" 5019 5020 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5021 this = self.sql(expression, "this") 5022 expr = self.sql(expression, "expression") 5023 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5024 return f"TRANSLATE({this} USING {expr}{with_error})" 5025 5026 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5027 if self.SUPPORTS_DECODE_CASE: 5028 return self.func("DECODE", *expression.expressions) 5029 5030 expression, *expressions = expression.expressions 5031 5032 ifs = [] 5033 for search, result in zip(expressions[::2], expressions[1::2]): 5034 if isinstance(search, exp.Literal): 5035 ifs.append(exp.If(this=expression.eq(search), true=result)) 5036 elif isinstance(search, exp.Null): 5037 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5038 else: 5039 if isinstance(search, exp.Binary): 5040 search = exp.paren(search) 5041 5042 cond = exp.or_( 5043 expression.eq(search), 5044 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5045 copy=False, 5046 ) 5047 ifs.append(exp.If(this=cond, true=result)) 5048 5049 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5050 return self.sql(case)
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
WHERE
clause. 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)
712 def __init__( 713 self, 714 pretty: t.Optional[bool] = None, 715 identify: str | bool = False, 716 normalize: bool = False, 717 pad: int = 2, 718 indent: int = 2, 719 normalize_functions: t.Optional[str | bool] = None, 720 unsupported_level: ErrorLevel = ErrorLevel.WARN, 721 max_unsupported: int = 3, 722 leading_comma: bool = False, 723 max_text_width: int = 80, 724 comments: bool = True, 725 dialect: DialectType = None, 726 ): 727 import sqlglot 728 from sqlglot.dialects import Dialect 729 730 self.pretty = pretty if pretty is not None else sqlglot.pretty 731 self.identify = identify 732 self.normalize = normalize 733 self.pad = pad 734 self._indent = indent 735 self.unsupported_level = unsupported_level 736 self.max_unsupported = max_unsupported 737 self.leading_comma = leading_comma 738 self.max_text_width = max_text_width 739 self.comments = comments 740 self.dialect = Dialect.get_or_raise(dialect) 741 742 # This is both a Dialect property and a Generator argument, so we prioritize the latter 743 self.normalize_functions = ( 744 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 745 ) 746 747 self.unsupported_messages: t.List[str] = [] 748 self._escaped_quote_end: str = ( 749 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 750 ) 751 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 752 753 self._next_name = name_sequence("_t") 754 755 self._identifier_start = self.dialect.IDENTIFIER_START 756 self._identifier_end = self.dialect.IDENTIFIER_END 757 758 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.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.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.JSONPathScript'>, <class 'sqlglot.expressions.JSONPathRoot'>, <class 'sqlglot.expressions.JSONPathSubscript'>, <class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathKey'>, <class 'sqlglot.expressions.JSONPathUnion'>, <class 'sqlglot.expressions.JSONPathFilter'>, <class 'sqlglot.expressions.JSONPathSelector'>, <class 'sqlglot.expressions.JSONPathSlice'>}
TYPE_MAPPING =
{<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Describe'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Order'>, <class 'sqlglot.expressions.Group'>, <class 'sqlglot.expressions.Having'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.VARCHAR: 'VARCHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NCHAR: 'NCHAR'>}
760 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 761 """ 762 Generates the SQL string corresponding to the given syntax tree. 763 764 Args: 765 expression: The syntax tree. 766 copy: Whether to copy the expression. The generator performs mutations so 767 it is safer to copy. 768 769 Returns: 770 The SQL string corresponding to `expression`. 771 """ 772 if copy: 773 expression = expression.copy() 774 775 expression = self.preprocess(expression) 776 777 self.unsupported_messages = [] 778 sql = self.sql(expression).strip() 779 780 if self.pretty: 781 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 782 783 if self.unsupported_level == ErrorLevel.IGNORE: 784 return sql 785 786 if self.unsupported_level == ErrorLevel.WARN: 787 for msg in self.unsupported_messages: 788 logger.warning(msg) 789 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 790 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 791 792 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:
794 def preprocess(self, expression: exp.Expression) -> exp.Expression: 795 """Apply generic preprocessing transformations to a given expression.""" 796 expression = self._move_ctes_to_top_level(expression) 797 798 if self.ENSURE_BOOLS: 799 from sqlglot.transforms import ensure_bools 800 801 expression = ensure_bools(expression) 802 803 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
827 def sanitize_comment(self, comment: str) -> str: 828 comment = " " + comment if comment[0].strip() else comment 829 comment = comment + " " if comment[-1].strip() else comment 830 831 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 832 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 833 comment = comment.replace("*/", "* /") 834 835 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
837 def maybe_comment( 838 self, 839 sql: str, 840 expression: t.Optional[exp.Expression] = None, 841 comments: t.Optional[t.List[str]] = None, 842 separated: bool = False, 843 ) -> str: 844 comments = ( 845 ((expression and expression.comments) if comments is None else comments) # type: ignore 846 if self.comments 847 else None 848 ) 849 850 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 851 return sql 852 853 comments_sql = " ".join( 854 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 855 ) 856 857 if not comments_sql: 858 return sql 859 860 comments_sql = self._replace_line_breaks(comments_sql) 861 862 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 863 return ( 864 f"{self.sep()}{comments_sql}{sql}" 865 if not sql or sql[0].isspace() 866 else f"{comments_sql}{self.sep()}{sql}" 867 ) 868 869 return f"{sql} {comments_sql}"
871 def wrap(self, expression: exp.Expression | str) -> str: 872 this_sql = ( 873 self.sql(expression) 874 if isinstance(expression, exp.UNWRAPPED_QUERIES) 875 else self.sql(expression, "this") 876 ) 877 if not this_sql: 878 return "()" 879 880 this_sql = self.indent(this_sql, level=1, pad=0) 881 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:
897 def indent( 898 self, 899 sql: str, 900 level: int = 0, 901 pad: t.Optional[int] = None, 902 skip_first: bool = False, 903 skip_last: bool = False, 904 ) -> str: 905 if not self.pretty or not sql: 906 return sql 907 908 pad = self.pad if pad is None else pad 909 lines = sql.split("\n") 910 911 return "\n".join( 912 ( 913 line 914 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 915 else f"{' ' * (level * self._indent + pad)}{line}" 916 ) 917 for i, line in enumerate(lines) 918 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
920 def sql( 921 self, 922 expression: t.Optional[str | exp.Expression], 923 key: t.Optional[str] = None, 924 comment: bool = True, 925 ) -> str: 926 if not expression: 927 return "" 928 929 if isinstance(expression, str): 930 return expression 931 932 if key: 933 value = expression.args.get(key) 934 if value: 935 return self.sql(value) 936 return "" 937 938 transform = self.TRANSFORMS.get(expression.__class__) 939 940 if callable(transform): 941 sql = transform(self, expression) 942 elif isinstance(expression, exp.Expression): 943 exp_handler_name = f"{expression.key}_sql" 944 945 if hasattr(self, exp_handler_name): 946 sql = getattr(self, exp_handler_name)(expression) 947 elif isinstance(expression, exp.Func): 948 sql = self.function_fallback_sql(expression) 949 elif isinstance(expression, exp.Property): 950 sql = self.property_sql(expression) 951 else: 952 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 953 else: 954 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 955 956 return self.maybe_comment(sql, expression) if self.comments and comment else sql
963 def cache_sql(self, expression: exp.Cache) -> str: 964 lazy = " LAZY" if expression.args.get("lazy") else "" 965 table = self.sql(expression, "this") 966 options = expression.args.get("options") 967 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 968 sql = self.sql(expression, "expression") 969 sql = f" AS{self.sep()}{sql}" if sql else "" 970 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 971 return self.prepend_ctes(expression, sql)
973 def characterset_sql(self, expression: exp.CharacterSet) -> str: 974 if isinstance(expression.parent, exp.Cast): 975 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 976 default = "DEFAULT " if expression.args.get("default") else "" 977 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
991 def column_sql(self, expression: exp.Column) -> str: 992 join_mark = " (+)" if expression.args.get("join_mark") else "" 993 994 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 995 join_mark = "" 996 self.unsupported("Outer join syntax using the (+) operator is not supported.") 997 998 return f"{self.column_parts(expression)}{join_mark}"
1006 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1007 column = self.sql(expression, "this") 1008 kind = self.sql(expression, "kind") 1009 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1010 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1011 kind = f"{sep}{kind}" if kind else "" 1012 constraints = f" {constraints}" if constraints else "" 1013 position = self.sql(expression, "position") 1014 position = f" {position}" if position else "" 1015 1016 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1017 kind = "" 1018 1019 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1026 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1027 this = self.sql(expression, "this") 1028 if expression.args.get("not_null"): 1029 persisted = " PERSISTED NOT NULL" 1030 elif expression.args.get("persisted"): 1031 persisted = " PERSISTED" 1032 else: 1033 persisted = "" 1034 1035 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1048 def generatedasidentitycolumnconstraint_sql( 1049 self, expression: exp.GeneratedAsIdentityColumnConstraint 1050 ) -> str: 1051 this = "" 1052 if expression.this is not None: 1053 on_null = " ON NULL" if expression.args.get("on_null") else "" 1054 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1055 1056 start = expression.args.get("start") 1057 start = f"START WITH {start}" if start else "" 1058 increment = expression.args.get("increment") 1059 increment = f" INCREMENT BY {increment}" if increment else "" 1060 minvalue = expression.args.get("minvalue") 1061 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1062 maxvalue = expression.args.get("maxvalue") 1063 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1064 cycle = expression.args.get("cycle") 1065 cycle_sql = "" 1066 1067 if cycle is not None: 1068 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1069 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1070 1071 sequence_opts = "" 1072 if start or increment or cycle_sql: 1073 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1074 sequence_opts = f" ({sequence_opts.strip()})" 1075 1076 expr = self.sql(expression, "expression") 1077 expr = f"({expr})" if expr else "IDENTITY" 1078 1079 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1081 def generatedasrowcolumnconstraint_sql( 1082 self, expression: exp.GeneratedAsRowColumnConstraint 1083 ) -> str: 1084 start = "START" if expression.args.get("start") else "END" 1085 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1086 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:
1096 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1097 desc = expression.args.get("desc") 1098 if desc is not None: 1099 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1100 options = self.expressions(expression, key="options", flat=True, sep=" ") 1101 options = f" {options}" if options else "" 1102 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1104 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1105 this = self.sql(expression, "this") 1106 this = f" {this}" if this else "" 1107 index_type = expression.args.get("index_type") 1108 index_type = f" USING {index_type}" if index_type else "" 1109 on_conflict = self.sql(expression, "on_conflict") 1110 on_conflict = f" {on_conflict}" if on_conflict else "" 1111 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1112 options = self.expressions(expression, key="options", flat=True, sep=" ") 1113 options = f" {options}" if options else "" 1114 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1119 def create_sql(self, expression: exp.Create) -> str: 1120 kind = self.sql(expression, "kind") 1121 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1122 properties = expression.args.get("properties") 1123 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1124 1125 this = self.createable_sql(expression, properties_locs) 1126 1127 properties_sql = "" 1128 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1129 exp.Properties.Location.POST_WITH 1130 ): 1131 properties_sql = self.sql( 1132 exp.Properties( 1133 expressions=[ 1134 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1135 *properties_locs[exp.Properties.Location.POST_WITH], 1136 ] 1137 ) 1138 ) 1139 1140 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1141 properties_sql = self.sep() + properties_sql 1142 elif not self.pretty: 1143 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1144 properties_sql = f" {properties_sql}" 1145 1146 begin = " BEGIN" if expression.args.get("begin") else "" 1147 end = " END" if expression.args.get("end") else "" 1148 1149 expression_sql = self.sql(expression, "expression") 1150 if expression_sql: 1151 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1152 1153 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1154 postalias_props_sql = "" 1155 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1156 postalias_props_sql = self.properties( 1157 exp.Properties( 1158 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1159 ), 1160 wrapped=False, 1161 ) 1162 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1163 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1164 1165 postindex_props_sql = "" 1166 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1167 postindex_props_sql = self.properties( 1168 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1169 wrapped=False, 1170 prefix=" ", 1171 ) 1172 1173 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1174 indexes = f" {indexes}" if indexes else "" 1175 index_sql = indexes + postindex_props_sql 1176 1177 replace = " OR REPLACE" if expression.args.get("replace") else "" 1178 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1179 unique = " UNIQUE" if expression.args.get("unique") else "" 1180 1181 clustered = expression.args.get("clustered") 1182 if clustered is None: 1183 clustered_sql = "" 1184 elif clustered: 1185 clustered_sql = " CLUSTERED COLUMNSTORE" 1186 else: 1187 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1188 1189 postcreate_props_sql = "" 1190 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1191 postcreate_props_sql = self.properties( 1192 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1193 sep=" ", 1194 prefix=" ", 1195 wrapped=False, 1196 ) 1197 1198 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1199 1200 postexpression_props_sql = "" 1201 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1202 postexpression_props_sql = self.properties( 1203 exp.Properties( 1204 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1205 ), 1206 sep=" ", 1207 prefix=" ", 1208 wrapped=False, 1209 ) 1210 1211 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1212 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1213 no_schema_binding = ( 1214 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1215 ) 1216 1217 clone = self.sql(expression, "clone") 1218 clone = f" {clone}" if clone else "" 1219 1220 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1221 properties_expression = f"{expression_sql}{properties_sql}" 1222 else: 1223 properties_expression = f"{properties_sql}{expression_sql}" 1224 1225 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1226 return self.prepend_ctes(expression, expression_sql)
1228 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1229 start = self.sql(expression, "start") 1230 start = f"START WITH {start}" if start else "" 1231 increment = self.sql(expression, "increment") 1232 increment = f" INCREMENT BY {increment}" if increment else "" 1233 minvalue = self.sql(expression, "minvalue") 1234 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1235 maxvalue = self.sql(expression, "maxvalue") 1236 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1237 owned = self.sql(expression, "owned") 1238 owned = f" OWNED BY {owned}" if owned else "" 1239 1240 cache = expression.args.get("cache") 1241 if cache is None: 1242 cache_str = "" 1243 elif cache is True: 1244 cache_str = " CACHE" 1245 else: 1246 cache_str = f" CACHE {cache}" 1247 1248 options = self.expressions(expression, key="options", flat=True, sep=" ") 1249 options = f" {options}" if options else "" 1250 1251 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1253 def clone_sql(self, expression: exp.Clone) -> str: 1254 this = self.sql(expression, "this") 1255 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1256 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1257 return f"{shallow}{keyword} {this}"
1259 def describe_sql(self, expression: exp.Describe) -> str: 1260 style = expression.args.get("style") 1261 style = f" {style}" if style else "" 1262 partition = self.sql(expression, "partition") 1263 partition = f" {partition}" if partition else "" 1264 format = self.sql(expression, "format") 1265 format = f" {format}" if format else "" 1266 1267 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1279 def with_sql(self, expression: exp.With) -> str: 1280 sql = self.expressions(expression, flat=True) 1281 recursive = ( 1282 "RECURSIVE " 1283 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1284 else "" 1285 ) 1286 search = self.sql(expression, "search") 1287 search = f" {search}" if search else "" 1288 1289 return f"WITH {recursive}{sql}{search}"
1291 def cte_sql(self, expression: exp.CTE) -> str: 1292 alias = expression.args.get("alias") 1293 if alias: 1294 alias.add_comments(expression.pop_comments()) 1295 1296 alias_sql = self.sql(expression, "alias") 1297 1298 materialized = expression.args.get("materialized") 1299 if materialized is False: 1300 materialized = "NOT MATERIALIZED " 1301 elif materialized: 1302 materialized = "MATERIALIZED " 1303 1304 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1306 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1307 alias = self.sql(expression, "this") 1308 columns = self.expressions(expression, key="columns", flat=True) 1309 columns = f"({columns})" if columns else "" 1310 1311 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1312 columns = "" 1313 self.unsupported("Named columns are not supported in table alias.") 1314 1315 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1316 alias = self._next_name() 1317 1318 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1326 def hexstring_sql( 1327 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1328 ) -> str: 1329 this = self.sql(expression, "this") 1330 is_integer_type = expression.args.get("is_integer") 1331 1332 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1333 not self.dialect.HEX_START and not binary_function_repr 1334 ): 1335 # Integer representation will be returned if: 1336 # - The read dialect treats the hex value as integer literal but not the write 1337 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1338 return f"{int(this, 16)}" 1339 1340 if not is_integer_type: 1341 # Read dialect treats the hex value as BINARY/BLOB 1342 if binary_function_repr: 1343 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1344 return self.func(binary_function_repr, exp.Literal.string(this)) 1345 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1346 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1347 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1348 1349 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1357 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1358 this = self.sql(expression, "this") 1359 escape = expression.args.get("escape") 1360 1361 if self.dialect.UNICODE_START: 1362 escape_substitute = r"\\\1" 1363 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1364 else: 1365 escape_substitute = r"\\u\1" 1366 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1367 1368 if escape: 1369 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1370 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1371 else: 1372 escape_pattern = ESCAPED_UNICODE_RE 1373 escape_sql = "" 1374 1375 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1376 this = escape_pattern.sub(escape_substitute, this) 1377 1378 return f"{left_quote}{this}{right_quote}{escape_sql}"
1380 def rawstring_sql(self, expression: exp.RawString) -> str: 1381 string = expression.this 1382 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1383 string = string.replace("\\", "\\\\") 1384 1385 string = self.escape_str(string, escape_backslash=False) 1386 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1394 def datatype_sql(self, expression: exp.DataType) -> str: 1395 nested = "" 1396 values = "" 1397 interior = self.expressions(expression, flat=True) 1398 1399 type_value = expression.this 1400 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1401 type_sql = self.sql(expression, "kind") 1402 else: 1403 type_sql = ( 1404 self.TYPE_MAPPING.get(type_value, type_value.value) 1405 if isinstance(type_value, exp.DataType.Type) 1406 else type_value 1407 ) 1408 1409 if interior: 1410 if expression.args.get("nested"): 1411 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1412 if expression.args.get("values") is not None: 1413 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1414 values = self.expressions(expression, key="values", flat=True) 1415 values = f"{delimiters[0]}{values}{delimiters[1]}" 1416 elif type_value == exp.DataType.Type.INTERVAL: 1417 nested = f" {interior}" 1418 else: 1419 nested = f"({interior})" 1420 1421 type_sql = f"{type_sql}{nested}{values}" 1422 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1423 exp.DataType.Type.TIMETZ, 1424 exp.DataType.Type.TIMESTAMPTZ, 1425 ): 1426 type_sql = f"{type_sql} WITH TIME ZONE" 1427 1428 return type_sql
1430 def directory_sql(self, expression: exp.Directory) -> str: 1431 local = "LOCAL " if expression.args.get("local") else "" 1432 row_format = self.sql(expression, "row_format") 1433 row_format = f" {row_format}" if row_format else "" 1434 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1436 def delete_sql(self, expression: exp.Delete) -> str: 1437 this = self.sql(expression, "this") 1438 this = f" FROM {this}" if this else "" 1439 using = self.sql(expression, "using") 1440 using = f" USING {using}" if using else "" 1441 cluster = self.sql(expression, "cluster") 1442 cluster = f" {cluster}" if cluster else "" 1443 where = self.sql(expression, "where") 1444 returning = self.sql(expression, "returning") 1445 limit = self.sql(expression, "limit") 1446 tables = self.expressions(expression, key="tables") 1447 tables = f" {tables}" if tables else "" 1448 if self.RETURNING_END: 1449 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1450 else: 1451 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1452 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1454 def drop_sql(self, expression: exp.Drop) -> str: 1455 this = self.sql(expression, "this") 1456 expressions = self.expressions(expression, flat=True) 1457 expressions = f" ({expressions})" if expressions else "" 1458 kind = expression.args["kind"] 1459 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1460 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1461 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1462 on_cluster = self.sql(expression, "cluster") 1463 on_cluster = f" {on_cluster}" if on_cluster else "" 1464 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1465 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1466 cascade = " CASCADE" if expression.args.get("cascade") else "" 1467 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1468 purge = " PURGE" if expression.args.get("purge") else "" 1469 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1471 def set_operation(self, expression: exp.SetOperation) -> str: 1472 op_type = type(expression) 1473 op_name = op_type.key.upper() 1474 1475 distinct = expression.args.get("distinct") 1476 if ( 1477 distinct is False 1478 and op_type in (exp.Except, exp.Intersect) 1479 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1480 ): 1481 self.unsupported(f"{op_name} ALL is not supported") 1482 1483 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1484 1485 if distinct is None: 1486 distinct = default_distinct 1487 if distinct is None: 1488 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1489 1490 if distinct is default_distinct: 1491 distinct_or_all = "" 1492 else: 1493 distinct_or_all = " DISTINCT" if distinct else " ALL" 1494 1495 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1496 side_kind = f"{side_kind} " if side_kind else "" 1497 1498 by_name = " BY NAME" if expression.args.get("by_name") else "" 1499 on = self.expressions(expression, key="on", flat=True) 1500 on = f" ON ({on})" if on else "" 1501 1502 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1504 def set_operations(self, expression: exp.SetOperation) -> str: 1505 if not self.SET_OP_MODIFIERS: 1506 limit = expression.args.get("limit") 1507 order = expression.args.get("order") 1508 1509 if limit or order: 1510 select = self._move_ctes_to_top_level( 1511 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1512 ) 1513 1514 if limit: 1515 select = select.limit(limit.pop(), copy=False) 1516 if order: 1517 select = select.order_by(order.pop(), copy=False) 1518 return self.sql(select) 1519 1520 sqls: t.List[str] = [] 1521 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1522 1523 while stack: 1524 node = stack.pop() 1525 1526 if isinstance(node, exp.SetOperation): 1527 stack.append(node.expression) 1528 stack.append( 1529 self.maybe_comment( 1530 self.set_operation(node), comments=node.comments, separated=True 1531 ) 1532 ) 1533 stack.append(node.this) 1534 else: 1535 sqls.append(self.sql(node)) 1536 1537 this = self.sep().join(sqls) 1538 this = self.query_modifiers(expression, this) 1539 return self.prepend_ctes(expression, this)
1541 def fetch_sql(self, expression: exp.Fetch) -> str: 1542 direction = expression.args.get("direction") 1543 direction = f" {direction}" if direction else "" 1544 count = self.sql(expression, "count") 1545 count = f" {count}" if count else "" 1546 limit_options = self.sql(expression, "limit_options") 1547 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1548 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1550 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1551 percent = " PERCENT" if expression.args.get("percent") else "" 1552 rows = " ROWS" if expression.args.get("rows") else "" 1553 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1554 if not with_ties and rows: 1555 with_ties = " ONLY" 1556 return f"{percent}{rows}{with_ties}"
1558 def filter_sql(self, expression: exp.Filter) -> str: 1559 if self.AGGREGATE_FILTER_SUPPORTED: 1560 this = self.sql(expression, "this") 1561 where = self.sql(expression, "expression").strip() 1562 return f"{this} FILTER({where})" 1563 1564 agg = expression.this 1565 agg_arg = agg.this 1566 cond = expression.expression.this 1567 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1568 return self.sql(agg)
1577 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1578 using = self.sql(expression, "using") 1579 using = f" USING {using}" if using else "" 1580 columns = self.expressions(expression, key="columns", flat=True) 1581 columns = f"({columns})" if columns else "" 1582 partition_by = self.expressions(expression, key="partition_by", flat=True) 1583 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1584 where = self.sql(expression, "where") 1585 include = self.expressions(expression, key="include", flat=True) 1586 if include: 1587 include = f" INCLUDE ({include})" 1588 with_storage = self.expressions(expression, key="with_storage", flat=True) 1589 with_storage = f" WITH ({with_storage})" if with_storage else "" 1590 tablespace = self.sql(expression, "tablespace") 1591 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1592 on = self.sql(expression, "on") 1593 on = f" ON {on}" if on else "" 1594 1595 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1597 def index_sql(self, expression: exp.Index) -> str: 1598 unique = "UNIQUE " if expression.args.get("unique") else "" 1599 primary = "PRIMARY " if expression.args.get("primary") else "" 1600 amp = "AMP " if expression.args.get("amp") else "" 1601 name = self.sql(expression, "this") 1602 name = f"{name} " if name else "" 1603 table = self.sql(expression, "table") 1604 table = f"{self.INDEX_ON} {table}" if table else "" 1605 1606 index = "INDEX " if not table else "" 1607 1608 params = self.sql(expression, "params") 1609 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1611 def identifier_sql(self, expression: exp.Identifier) -> str: 1612 text = expression.name 1613 lower = text.lower() 1614 text = lower if self.normalize and not expression.quoted else text 1615 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1616 if ( 1617 expression.quoted 1618 or self.dialect.can_identify(text, self.identify) 1619 or lower in self.RESERVED_KEYWORDS 1620 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1621 ): 1622 text = f"{self._identifier_start}{text}{self._identifier_end}" 1623 return text
1638 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1639 input_format = self.sql(expression, "input_format") 1640 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1641 output_format = self.sql(expression, "output_format") 1642 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1643 return self.sep().join((input_format, output_format))
1653 def properties_sql(self, expression: exp.Properties) -> str: 1654 root_properties = [] 1655 with_properties = [] 1656 1657 for p in expression.expressions: 1658 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1659 if p_loc == exp.Properties.Location.POST_WITH: 1660 with_properties.append(p) 1661 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1662 root_properties.append(p) 1663 1664 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1665 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1666 1667 if root_props and with_props and not self.pretty: 1668 with_props = " " + with_props 1669 1670 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1677 def properties( 1678 self, 1679 properties: exp.Properties, 1680 prefix: str = "", 1681 sep: str = ", ", 1682 suffix: str = "", 1683 wrapped: bool = True, 1684 ) -> str: 1685 if properties.expressions: 1686 expressions = self.expressions(properties, sep=sep, indent=False) 1687 if expressions: 1688 expressions = self.wrap(expressions) if wrapped else expressions 1689 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1690 return ""
1695 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1696 properties_locs = defaultdict(list) 1697 for p in properties.expressions: 1698 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1699 if p_loc != exp.Properties.Location.UNSUPPORTED: 1700 properties_locs[p_loc].append(p) 1701 else: 1702 self.unsupported(f"Unsupported property {p.key}") 1703 1704 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1711 def property_sql(self, expression: exp.Property) -> str: 1712 property_cls = expression.__class__ 1713 if property_cls == exp.Property: 1714 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1715 1716 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1717 if not property_name: 1718 self.unsupported(f"Unsupported property {expression.key}") 1719 1720 return f"{property_name}={self.sql(expression, 'this')}"
1722 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1723 if self.SUPPORTS_CREATE_TABLE_LIKE: 1724 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1725 options = f" {options}" if options else "" 1726 1727 like = f"LIKE {self.sql(expression, 'this')}{options}" 1728 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1729 like = f"({like})" 1730 1731 return like 1732 1733 if expression.expressions: 1734 self.unsupported("Transpilation of LIKE property options is unsupported") 1735 1736 select = exp.select("*").from_(expression.this).limit(0) 1737 return f"AS {self.sql(select)}"
1744 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1745 no = "NO " if expression.args.get("no") else "" 1746 local = expression.args.get("local") 1747 local = f"{local} " if local else "" 1748 dual = "DUAL " if expression.args.get("dual") else "" 1749 before = "BEFORE " if expression.args.get("before") else "" 1750 after = "AFTER " if expression.args.get("after") else "" 1751 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1767 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1768 if expression.args.get("no"): 1769 return "NO MERGEBLOCKRATIO" 1770 if expression.args.get("default"): 1771 return "DEFAULT MERGEBLOCKRATIO" 1772 1773 percent = " PERCENT" if expression.args.get("percent") else "" 1774 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1776 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1777 default = expression.args.get("default") 1778 minimum = expression.args.get("minimum") 1779 maximum = expression.args.get("maximum") 1780 if default or minimum or maximum: 1781 if default: 1782 prop = "DEFAULT" 1783 elif minimum: 1784 prop = "MINIMUM" 1785 else: 1786 prop = "MAXIMUM" 1787 return f"{prop} DATABLOCKSIZE" 1788 units = expression.args.get("units") 1789 units = f" {units}" if units else "" 1790 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1792 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1793 autotemp = expression.args.get("autotemp") 1794 always = expression.args.get("always") 1795 default = expression.args.get("default") 1796 manual = expression.args.get("manual") 1797 never = expression.args.get("never") 1798 1799 if autotemp is not None: 1800 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1801 elif always: 1802 prop = "ALWAYS" 1803 elif default: 1804 prop = "DEFAULT" 1805 elif manual: 1806 prop = "MANUAL" 1807 elif never: 1808 prop = "NEVER" 1809 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1811 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1812 no = expression.args.get("no") 1813 no = " NO" if no else "" 1814 concurrent = expression.args.get("concurrent") 1815 concurrent = " CONCURRENT" if concurrent else "" 1816 target = self.sql(expression, "target") 1817 target = f" {target}" if target else "" 1818 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1820 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1821 if isinstance(expression.this, list): 1822 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1823 if expression.this: 1824 modulus = self.sql(expression, "this") 1825 remainder = self.sql(expression, "expression") 1826 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1827 1828 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1829 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1830 return f"FROM ({from_expressions}) TO ({to_expressions})"
1832 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1833 this = self.sql(expression, "this") 1834 1835 for_values_or_default = expression.expression 1836 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1837 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1838 else: 1839 for_values_or_default = " DEFAULT" 1840 1841 return f"PARTITION OF {this}{for_values_or_default}"
1843 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1844 kind = expression.args.get("kind") 1845 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1846 for_or_in = expression.args.get("for_or_in") 1847 for_or_in = f" {for_or_in}" if for_or_in else "" 1848 lock_type = expression.args.get("lock_type") 1849 override = " OVERRIDE" if expression.args.get("override") else "" 1850 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1852 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1853 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1854 statistics = expression.args.get("statistics") 1855 statistics_sql = "" 1856 if statistics is not None: 1857 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1858 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1860 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1861 this = self.sql(expression, "this") 1862 this = f"HISTORY_TABLE={this}" if this else "" 1863 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1864 data_consistency = ( 1865 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1866 ) 1867 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1868 retention_period = ( 1869 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1870 ) 1871 1872 if this: 1873 on_sql = self.func("ON", this, data_consistency, retention_period) 1874 else: 1875 on_sql = "ON" if expression.args.get("on") else "OFF" 1876 1877 sql = f"SYSTEM_VERSIONING={on_sql}" 1878 1879 return f"WITH({sql})" if expression.args.get("with") else sql
1881 def insert_sql(self, expression: exp.Insert) -> str: 1882 hint = self.sql(expression, "hint") 1883 overwrite = expression.args.get("overwrite") 1884 1885 if isinstance(expression.this, exp.Directory): 1886 this = " OVERWRITE" if overwrite else " INTO" 1887 else: 1888 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1889 1890 stored = self.sql(expression, "stored") 1891 stored = f" {stored}" if stored else "" 1892 alternative = expression.args.get("alternative") 1893 alternative = f" OR {alternative}" if alternative else "" 1894 ignore = " IGNORE" if expression.args.get("ignore") else "" 1895 is_function = expression.args.get("is_function") 1896 if is_function: 1897 this = f"{this} FUNCTION" 1898 this = f"{this} {self.sql(expression, 'this')}" 1899 1900 exists = " IF EXISTS" if expression.args.get("exists") else "" 1901 where = self.sql(expression, "where") 1902 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1903 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1904 on_conflict = self.sql(expression, "conflict") 1905 on_conflict = f" {on_conflict}" if on_conflict else "" 1906 by_name = " BY NAME" if expression.args.get("by_name") else "" 1907 returning = self.sql(expression, "returning") 1908 1909 if self.RETURNING_END: 1910 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1911 else: 1912 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1913 1914 partition_by = self.sql(expression, "partition") 1915 partition_by = f" {partition_by}" if partition_by else "" 1916 settings = self.sql(expression, "settings") 1917 settings = f" {settings}" if settings else "" 1918 1919 source = self.sql(expression, "source") 1920 source = f"TABLE {source}" if source else "" 1921 1922 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1923 return self.prepend_ctes(expression, sql)
1941 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1942 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1943 1944 constraint = self.sql(expression, "constraint") 1945 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1946 1947 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1948 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1949 action = self.sql(expression, "action") 1950 1951 expressions = self.expressions(expression, flat=True) 1952 if expressions: 1953 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1954 expressions = f" {set_keyword}{expressions}" 1955 1956 where = self.sql(expression, "where") 1957 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1962 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1963 fields = self.sql(expression, "fields") 1964 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1965 escaped = self.sql(expression, "escaped") 1966 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1967 items = self.sql(expression, "collection_items") 1968 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1969 keys = self.sql(expression, "map_keys") 1970 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1971 lines = self.sql(expression, "lines") 1972 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1973 null = self.sql(expression, "null") 1974 null = f" NULL DEFINED AS {null}" if null else "" 1975 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2003 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2004 table = self.table_parts(expression) 2005 only = "ONLY " if expression.args.get("only") else "" 2006 partition = self.sql(expression, "partition") 2007 partition = f" {partition}" if partition else "" 2008 version = self.sql(expression, "version") 2009 version = f" {version}" if version else "" 2010 alias = self.sql(expression, "alias") 2011 alias = f"{sep}{alias}" if alias else "" 2012 2013 sample = self.sql(expression, "sample") 2014 if self.dialect.ALIAS_POST_TABLESAMPLE: 2015 sample_pre_alias = sample 2016 sample_post_alias = "" 2017 else: 2018 sample_pre_alias = "" 2019 sample_post_alias = sample 2020 2021 hints = self.expressions(expression, key="hints", sep=" ") 2022 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2023 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2024 joins = self.indent( 2025 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2026 ) 2027 laterals = self.expressions(expression, key="laterals", sep="") 2028 2029 file_format = self.sql(expression, "format") 2030 if file_format: 2031 pattern = self.sql(expression, "pattern") 2032 pattern = f", PATTERN => {pattern}" if pattern else "" 2033 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2034 2035 ordinality = expression.args.get("ordinality") or "" 2036 if ordinality: 2037 ordinality = f" WITH ORDINALITY{alias}" 2038 alias = "" 2039 2040 when = self.sql(expression, "when") 2041 if when: 2042 table = f"{table} {when}" 2043 2044 changes = self.sql(expression, "changes") 2045 changes = f" {changes}" if changes else "" 2046 2047 rows_from = self.expressions(expression, key="rows_from") 2048 if rows_from: 2049 table = f"ROWS FROM {self.wrap(rows_from)}" 2050 2051 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2053 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2054 table = self.func("TABLE", expression.this) 2055 alias = self.sql(expression, "alias") 2056 alias = f" AS {alias}" if alias else "" 2057 sample = self.sql(expression, "sample") 2058 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2059 joins = self.indent( 2060 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2061 ) 2062 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2064 def tablesample_sql( 2065 self, 2066 expression: exp.TableSample, 2067 tablesample_keyword: t.Optional[str] = None, 2068 ) -> str: 2069 method = self.sql(expression, "method") 2070 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2071 numerator = self.sql(expression, "bucket_numerator") 2072 denominator = self.sql(expression, "bucket_denominator") 2073 field = self.sql(expression, "bucket_field") 2074 field = f" ON {field}" if field else "" 2075 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2076 seed = self.sql(expression, "seed") 2077 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2078 2079 size = self.sql(expression, "size") 2080 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2081 size = f"{size} ROWS" 2082 2083 percent = self.sql(expression, "percent") 2084 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2085 percent = f"{percent} PERCENT" 2086 2087 expr = f"{bucket}{percent}{size}" 2088 if self.TABLESAMPLE_REQUIRES_PARENS: 2089 expr = f"({expr})" 2090 2091 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2093 def pivot_sql(self, expression: exp.Pivot) -> str: 2094 expressions = self.expressions(expression, flat=True) 2095 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2096 2097 group = self.sql(expression, "group") 2098 2099 if expression.this: 2100 this = self.sql(expression, "this") 2101 if not expressions: 2102 return f"UNPIVOT {this}" 2103 2104 on = f"{self.seg('ON')} {expressions}" 2105 into = self.sql(expression, "into") 2106 into = f"{self.seg('INTO')} {into}" if into else "" 2107 using = self.expressions(expression, key="using", flat=True) 2108 using = f"{self.seg('USING')} {using}" if using else "" 2109 return f"{direction} {this}{on}{into}{using}{group}" 2110 2111 alias = self.sql(expression, "alias") 2112 alias = f" AS {alias}" if alias else "" 2113 2114 fields = self.expressions( 2115 expression, 2116 "fields", 2117 sep=" ", 2118 dynamic=True, 2119 new_line=True, 2120 skip_first=True, 2121 skip_last=True, 2122 ) 2123 2124 include_nulls = expression.args.get("include_nulls") 2125 if include_nulls is not None: 2126 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2127 else: 2128 nulls = "" 2129 2130 default_on_null = self.sql(expression, "default_on_null") 2131 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2132 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2143 def update_sql(self, expression: exp.Update) -> str: 2144 this = self.sql(expression, "this") 2145 set_sql = self.expressions(expression, flat=True) 2146 from_sql = self.sql(expression, "from") 2147 where_sql = self.sql(expression, "where") 2148 returning = self.sql(expression, "returning") 2149 order = self.sql(expression, "order") 2150 limit = self.sql(expression, "limit") 2151 if self.RETURNING_END: 2152 expression_sql = f"{from_sql}{where_sql}{returning}" 2153 else: 2154 expression_sql = f"{returning}{from_sql}{where_sql}" 2155 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2156 return self.prepend_ctes(expression, sql)
2158 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2159 values_as_table = values_as_table and self.VALUES_AS_TABLE 2160 2161 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2162 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2163 args = self.expressions(expression) 2164 alias = self.sql(expression, "alias") 2165 values = f"VALUES{self.seg('')}{args}" 2166 values = ( 2167 f"({values})" 2168 if self.WRAP_DERIVED_VALUES 2169 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2170 else values 2171 ) 2172 return f"{values} AS {alias}" if alias else values 2173 2174 # Converts `VALUES...` expression into a series of select unions. 2175 alias_node = expression.args.get("alias") 2176 column_names = alias_node and alias_node.columns 2177 2178 selects: t.List[exp.Query] = [] 2179 2180 for i, tup in enumerate(expression.expressions): 2181 row = tup.expressions 2182 2183 if i == 0 and column_names: 2184 row = [ 2185 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2186 ] 2187 2188 selects.append(exp.Select(expressions=row)) 2189 2190 if self.pretty: 2191 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2192 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2193 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2194 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2195 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2196 2197 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2198 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2199 return f"({unions}){alias}"
2204 @unsupported_args("expressions") 2205 def into_sql(self, expression: exp.Into) -> str: 2206 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2207 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2208 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2225 def group_sql(self, expression: exp.Group) -> str: 2226 group_by_all = expression.args.get("all") 2227 if group_by_all is True: 2228 modifier = " ALL" 2229 elif group_by_all is False: 2230 modifier = " DISTINCT" 2231 else: 2232 modifier = "" 2233 2234 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2235 2236 grouping_sets = self.expressions(expression, key="grouping_sets") 2237 cube = self.expressions(expression, key="cube") 2238 rollup = self.expressions(expression, key="rollup") 2239 2240 groupings = csv( 2241 self.seg(grouping_sets) if grouping_sets else "", 2242 self.seg(cube) if cube else "", 2243 self.seg(rollup) if rollup else "", 2244 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2245 sep=self.GROUPINGS_SEP, 2246 ) 2247 2248 if ( 2249 expression.expressions 2250 and groupings 2251 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2252 ): 2253 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2254 2255 return f"{group_by}{groupings}"
2261 def connect_sql(self, expression: exp.Connect) -> str: 2262 start = self.sql(expression, "start") 2263 start = self.seg(f"START WITH {start}") if start else "" 2264 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2265 connect = self.sql(expression, "connect") 2266 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2267 return start + connect
2272 def join_sql(self, expression: exp.Join) -> str: 2273 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2274 side = None 2275 else: 2276 side = expression.side 2277 2278 op_sql = " ".join( 2279 op 2280 for op in ( 2281 expression.method, 2282 "GLOBAL" if expression.args.get("global") else None, 2283 side, 2284 expression.kind, 2285 expression.hint if self.JOIN_HINTS else None, 2286 ) 2287 if op 2288 ) 2289 match_cond = self.sql(expression, "match_condition") 2290 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2291 on_sql = self.sql(expression, "on") 2292 using = expression.args.get("using") 2293 2294 if not on_sql and using: 2295 on_sql = csv(*(self.sql(column) for column in using)) 2296 2297 this = expression.this 2298 this_sql = self.sql(this) 2299 2300 exprs = self.expressions(expression) 2301 if exprs: 2302 this_sql = f"{this_sql},{self.seg(exprs)}" 2303 2304 if on_sql: 2305 on_sql = self.indent(on_sql, skip_first=True) 2306 space = self.seg(" " * self.pad) if self.pretty else " " 2307 if using: 2308 on_sql = f"{space}USING ({on_sql})" 2309 else: 2310 on_sql = f"{space}ON {on_sql}" 2311 elif not op_sql: 2312 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2313 return f" {this_sql}" 2314 2315 return f", {this_sql}" 2316 2317 if op_sql != "STRAIGHT_JOIN": 2318 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2319 2320 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2321 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:
2328 def lateral_op(self, expression: exp.Lateral) -> str: 2329 cross_apply = expression.args.get("cross_apply") 2330 2331 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2332 if cross_apply is True: 2333 op = "INNER JOIN " 2334 elif cross_apply is False: 2335 op = "LEFT JOIN " 2336 else: 2337 op = "" 2338 2339 return f"{op}LATERAL"
2341 def lateral_sql(self, expression: exp.Lateral) -> str: 2342 this = self.sql(expression, "this") 2343 2344 if expression.args.get("view"): 2345 alias = expression.args["alias"] 2346 columns = self.expressions(alias, key="columns", flat=True) 2347 table = f" {alias.name}" if alias.name else "" 2348 columns = f" AS {columns}" if columns else "" 2349 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2350 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2351 2352 alias = self.sql(expression, "alias") 2353 alias = f" AS {alias}" if alias else "" 2354 2355 ordinality = expression.args.get("ordinality") or "" 2356 if ordinality: 2357 ordinality = f" WITH ORDINALITY{alias}" 2358 alias = "" 2359 2360 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2362 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2363 this = self.sql(expression, "this") 2364 2365 args = [ 2366 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2367 for e in (expression.args.get(k) for k in ("offset", "expression")) 2368 if e 2369 ] 2370 2371 args_sql = ", ".join(self.sql(e) for e in args) 2372 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2373 expressions = self.expressions(expression, flat=True) 2374 limit_options = self.sql(expression, "limit_options") 2375 expressions = f" BY {expressions}" if expressions else "" 2376 2377 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2379 def offset_sql(self, expression: exp.Offset) -> str: 2380 this = self.sql(expression, "this") 2381 value = expression.expression 2382 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2383 expressions = self.expressions(expression, flat=True) 2384 expressions = f" BY {expressions}" if expressions else "" 2385 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2387 def setitem_sql(self, expression: exp.SetItem) -> str: 2388 kind = self.sql(expression, "kind") 2389 kind = f"{kind} " if kind else "" 2390 this = self.sql(expression, "this") 2391 expressions = self.expressions(expression) 2392 collate = self.sql(expression, "collate") 2393 collate = f" COLLATE {collate}" if collate else "" 2394 global_ = "GLOBAL " if expression.args.get("global") else "" 2395 return f"{global_}{kind}{this}{expressions}{collate}"
2405 def lock_sql(self, expression: exp.Lock) -> str: 2406 if not self.LOCKING_READS_SUPPORTED: 2407 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2408 return "" 2409 2410 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2411 expressions = self.expressions(expression, flat=True) 2412 expressions = f" OF {expressions}" if expressions else "" 2413 wait = expression.args.get("wait") 2414 2415 if wait is not None: 2416 if isinstance(wait, exp.Literal): 2417 wait = f" WAIT {self.sql(wait)}" 2418 else: 2419 wait = " NOWAIT" if wait else " SKIP LOCKED" 2420 2421 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2429 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2430 if self.dialect.ESCAPED_SEQUENCES: 2431 to_escaped = self.dialect.ESCAPED_SEQUENCES 2432 text = "".join( 2433 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2434 ) 2435 2436 return self._replace_line_breaks(text).replace( 2437 self.dialect.QUOTE_END, self._escaped_quote_end 2438 )
2440 def loaddata_sql(self, expression: exp.LoadData) -> str: 2441 local = " LOCAL" if expression.args.get("local") else "" 2442 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2443 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2444 this = f" INTO TABLE {self.sql(expression, 'this')}" 2445 partition = self.sql(expression, "partition") 2446 partition = f" {partition}" if partition else "" 2447 input_format = self.sql(expression, "input_format") 2448 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2449 serde = self.sql(expression, "serde") 2450 serde = f" SERDE {serde}" if serde else "" 2451 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2459 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2460 this = self.sql(expression, "this") 2461 this = f"{this} " if this else this 2462 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2463 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2465 def withfill_sql(self, expression: exp.WithFill) -> str: 2466 from_sql = self.sql(expression, "from") 2467 from_sql = f" FROM {from_sql}" if from_sql else "" 2468 to_sql = self.sql(expression, "to") 2469 to_sql = f" TO {to_sql}" if to_sql else "" 2470 step_sql = self.sql(expression, "step") 2471 step_sql = f" STEP {step_sql}" if step_sql else "" 2472 interpolated_values = [ 2473 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2474 if isinstance(e, exp.Alias) 2475 else self.sql(e, "this") 2476 for e in expression.args.get("interpolate") or [] 2477 ] 2478 interpolate = ( 2479 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2480 ) 2481 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2492 def ordered_sql(self, expression: exp.Ordered) -> str: 2493 desc = expression.args.get("desc") 2494 asc = not desc 2495 2496 nulls_first = expression.args.get("nulls_first") 2497 nulls_last = not nulls_first 2498 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2499 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2500 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2501 2502 this = self.sql(expression, "this") 2503 2504 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2505 nulls_sort_change = "" 2506 if nulls_first and ( 2507 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2508 ): 2509 nulls_sort_change = " NULLS FIRST" 2510 elif ( 2511 nulls_last 2512 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2513 and not nulls_are_last 2514 ): 2515 nulls_sort_change = " NULLS LAST" 2516 2517 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2518 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2519 window = expression.find_ancestor(exp.Window, exp.Select) 2520 if isinstance(window, exp.Window) and window.args.get("spec"): 2521 self.unsupported( 2522 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2523 ) 2524 nulls_sort_change = "" 2525 elif self.NULL_ORDERING_SUPPORTED is False and ( 2526 (asc and nulls_sort_change == " NULLS LAST") 2527 or (desc and nulls_sort_change == " NULLS FIRST") 2528 ): 2529 # BigQuery does not allow these ordering/nulls combinations when used under 2530 # an aggregation func or under a window containing one 2531 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2532 2533 if isinstance(ancestor, exp.Window): 2534 ancestor = ancestor.this 2535 if isinstance(ancestor, exp.AggFunc): 2536 self.unsupported( 2537 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2538 ) 2539 nulls_sort_change = "" 2540 elif self.NULL_ORDERING_SUPPORTED is None: 2541 if expression.this.is_int: 2542 self.unsupported( 2543 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2544 ) 2545 elif not isinstance(expression.this, exp.Rand): 2546 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2547 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2548 nulls_sort_change = "" 2549 2550 with_fill = self.sql(expression, "with_fill") 2551 with_fill = f" {with_fill}" if with_fill else "" 2552 2553 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2563 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2564 partition = self.partition_by_sql(expression) 2565 order = self.sql(expression, "order") 2566 measures = self.expressions(expression, key="measures") 2567 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2568 rows = self.sql(expression, "rows") 2569 rows = self.seg(rows) if rows else "" 2570 after = self.sql(expression, "after") 2571 after = self.seg(after) if after else "" 2572 pattern = self.sql(expression, "pattern") 2573 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2574 definition_sqls = [ 2575 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2576 for definition in expression.args.get("define", []) 2577 ] 2578 definitions = self.expressions(sqls=definition_sqls) 2579 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2580 body = "".join( 2581 ( 2582 partition, 2583 order, 2584 measures, 2585 rows, 2586 after, 2587 pattern, 2588 define, 2589 ) 2590 ) 2591 alias = self.sql(expression, "alias") 2592 alias = f" {alias}" if alias else "" 2593 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2595 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2596 limit = expression.args.get("limit") 2597 2598 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2599 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2600 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2601 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2602 2603 return csv( 2604 *sqls, 2605 *[self.sql(join) for join in expression.args.get("joins") or []], 2606 self.sql(expression, "match"), 2607 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2608 self.sql(expression, "prewhere"), 2609 self.sql(expression, "where"), 2610 self.sql(expression, "connect"), 2611 self.sql(expression, "group"), 2612 self.sql(expression, "having"), 2613 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2614 self.sql(expression, "order"), 2615 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2616 *self.after_limit_modifiers(expression), 2617 self.options_modifier(expression), 2618 self.for_modifiers(expression), 2619 sep="", 2620 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2634 def offset_limit_modifiers( 2635 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2636 ) -> t.List[str]: 2637 return [ 2638 self.sql(expression, "offset") if fetch else self.sql(limit), 2639 self.sql(limit) if fetch else self.sql(expression, "offset"), 2640 ]
2647 def select_sql(self, expression: exp.Select) -> str: 2648 into = expression.args.get("into") 2649 if not self.SUPPORTS_SELECT_INTO and into: 2650 into.pop() 2651 2652 hint = self.sql(expression, "hint") 2653 distinct = self.sql(expression, "distinct") 2654 distinct = f" {distinct}" if distinct else "" 2655 kind = self.sql(expression, "kind") 2656 2657 limit = expression.args.get("limit") 2658 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2659 top = self.limit_sql(limit, top=True) 2660 limit.pop() 2661 else: 2662 top = "" 2663 2664 expressions = self.expressions(expression) 2665 2666 if kind: 2667 if kind in self.SELECT_KINDS: 2668 kind = f" AS {kind}" 2669 else: 2670 if kind == "STRUCT": 2671 expressions = self.expressions( 2672 sqls=[ 2673 self.sql( 2674 exp.Struct( 2675 expressions=[ 2676 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2677 if isinstance(e, exp.Alias) 2678 else e 2679 for e in expression.expressions 2680 ] 2681 ) 2682 ) 2683 ] 2684 ) 2685 kind = "" 2686 2687 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2688 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2689 2690 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2691 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2692 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2693 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2694 sql = self.query_modifiers( 2695 expression, 2696 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2697 self.sql(expression, "into", comment=False), 2698 self.sql(expression, "from", comment=False), 2699 ) 2700 2701 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2702 if expression.args.get("with"): 2703 sql = self.maybe_comment(sql, expression) 2704 expression.pop_comments() 2705 2706 sql = self.prepend_ctes(expression, sql) 2707 2708 if not self.SUPPORTS_SELECT_INTO and into: 2709 if into.args.get("temporary"): 2710 table_kind = " TEMPORARY" 2711 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2712 table_kind = " UNLOGGED" 2713 else: 2714 table_kind = "" 2715 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2716 2717 return sql
2729 def star_sql(self, expression: exp.Star) -> str: 2730 except_ = self.expressions(expression, key="except", flat=True) 2731 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2732 replace = self.expressions(expression, key="replace", flat=True) 2733 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2734 rename = self.expressions(expression, key="rename", flat=True) 2735 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2736 return f"*{except_}{replace}{rename}"
2752 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2753 alias = self.sql(expression, "alias") 2754 alias = f"{sep}{alias}" if alias else "" 2755 sample = self.sql(expression, "sample") 2756 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2757 alias = f"{sample}{alias}" 2758 2759 # Set to None so it's not generated again by self.query_modifiers() 2760 expression.set("sample", None) 2761 2762 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2763 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2764 return self.prepend_ctes(expression, sql)
2770 def unnest_sql(self, expression: exp.Unnest) -> str: 2771 args = self.expressions(expression, flat=True) 2772 2773 alias = expression.args.get("alias") 2774 offset = expression.args.get("offset") 2775 2776 if self.UNNEST_WITH_ORDINALITY: 2777 if alias and isinstance(offset, exp.Expression): 2778 alias.append("columns", offset) 2779 2780 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2781 columns = alias.columns 2782 alias = self.sql(columns[0]) if columns else "" 2783 else: 2784 alias = self.sql(alias) 2785 2786 alias = f" AS {alias}" if alias else alias 2787 if self.UNNEST_WITH_ORDINALITY: 2788 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2789 else: 2790 if isinstance(offset, exp.Expression): 2791 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2792 elif offset: 2793 suffix = f"{alias} WITH OFFSET" 2794 else: 2795 suffix = alias 2796 2797 return f"UNNEST({args}){suffix}"
2806 def window_sql(self, expression: exp.Window) -> str: 2807 this = self.sql(expression, "this") 2808 partition = self.partition_by_sql(expression) 2809 order = expression.args.get("order") 2810 order = self.order_sql(order, flat=True) if order else "" 2811 spec = self.sql(expression, "spec") 2812 alias = self.sql(expression, "alias") 2813 over = self.sql(expression, "over") or "OVER" 2814 2815 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2816 2817 first = expression.args.get("first") 2818 if first is None: 2819 first = "" 2820 else: 2821 first = "FIRST" if first else "LAST" 2822 2823 if not partition and not order and not spec and alias: 2824 return f"{this} {alias}" 2825 2826 args = self.format_args( 2827 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2828 ) 2829 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2835 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2836 kind = self.sql(expression, "kind") 2837 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2838 end = ( 2839 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2840 or "CURRENT ROW" 2841 ) 2842 2843 window_spec = f"{kind} BETWEEN {start} AND {end}" 2844 2845 exclude = self.sql(expression, "exclude") 2846 if exclude: 2847 if self.SUPPORTS_WINDOW_EXCLUDE: 2848 window_spec += f" EXCLUDE {exclude}" 2849 else: 2850 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2851 2852 return window_spec
def
bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket, index_offset: Optional[int] = None) -> List[sqlglot.expressions.Expression]:
2865 def bracket_offset_expressions( 2866 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2867 ) -> t.List[exp.Expression]: 2868 return apply_index_offset( 2869 expression.this, 2870 expression.expressions, 2871 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2872 dialect=self.dialect, 2873 )
2883 def any_sql(self, expression: exp.Any) -> str: 2884 this = self.sql(expression, "this") 2885 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2886 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2887 this = self.wrap(this) 2888 return f"ANY{this}" 2889 return f"ANY {this}"
2894 def case_sql(self, expression: exp.Case) -> str: 2895 this = self.sql(expression, "this") 2896 statements = [f"CASE {this}" if this else "CASE"] 2897 2898 for e in expression.args["ifs"]: 2899 statements.append(f"WHEN {self.sql(e, 'this')}") 2900 statements.append(f"THEN {self.sql(e, 'true')}") 2901 2902 default = self.sql(expression, "default") 2903 2904 if default: 2905 statements.append(f"ELSE {default}") 2906 2907 statements.append("END") 2908 2909 if self.pretty and self.too_wide(statements): 2910 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2911 2912 return " ".join(statements)
2924 def extract_sql(self, expression: exp.Extract) -> str: 2925 from sqlglot.dialects.dialect import map_date_part 2926 2927 this = ( 2928 map_date_part(expression.this, self.dialect) 2929 if self.NORMALIZE_EXTRACT_DATE_PARTS 2930 else expression.this 2931 ) 2932 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2933 expression_sql = self.sql(expression, "expression") 2934 2935 return f"EXTRACT({this_sql} FROM {expression_sql})"
2937 def trim_sql(self, expression: exp.Trim) -> str: 2938 trim_type = self.sql(expression, "position") 2939 2940 if trim_type == "LEADING": 2941 func_name = "LTRIM" 2942 elif trim_type == "TRAILING": 2943 func_name = "RTRIM" 2944 else: 2945 func_name = "TRIM" 2946 2947 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]:
2949 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2950 args = expression.expressions 2951 if isinstance(expression, exp.ConcatWs): 2952 args = args[1:] # Skip the delimiter 2953 2954 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2955 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2956 2957 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2958 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2959 2960 return args
2962 def concat_sql(self, expression: exp.Concat) -> str: 2963 expressions = self.convert_concat_args(expression) 2964 2965 # Some dialects don't allow a single-argument CONCAT call 2966 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2967 return self.sql(expressions[0]) 2968 2969 return self.func("CONCAT", *expressions)
2980 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2981 expressions = self.expressions(expression, flat=True) 2982 expressions = f" ({expressions})" if expressions else "" 2983 reference = self.sql(expression, "reference") 2984 reference = f" {reference}" if reference else "" 2985 delete = self.sql(expression, "delete") 2986 delete = f" ON DELETE {delete}" if delete else "" 2987 update = self.sql(expression, "update") 2988 update = f" ON UPDATE {update}" if update else "" 2989 options = self.expressions(expression, key="options", flat=True, sep=" ") 2990 options = f" {options}" if options else "" 2991 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
2993 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2994 expressions = self.expressions(expression, flat=True) 2995 options = self.expressions(expression, key="options", flat=True, sep=" ") 2996 options = f" {options}" if options else "" 2997 return f"PRIMARY KEY ({expressions}){options}"
3010 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3011 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3012 3013 if expression.args.get("escape"): 3014 path = self.escape_str(path) 3015 3016 if self.QUOTE_JSON_PATH: 3017 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3018 3019 return path
3021 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3022 if isinstance(expression, exp.JSONPathPart): 3023 transform = self.TRANSFORMS.get(expression.__class__) 3024 if not callable(transform): 3025 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3026 return "" 3027 3028 return transform(self, expression) 3029 3030 if isinstance(expression, int): 3031 return str(expression) 3032 3033 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3034 escaped = expression.replace("'", "\\'") 3035 escaped = f"\\'{expression}\\'" 3036 else: 3037 escaped = expression.replace('"', '\\"') 3038 escaped = f'"{escaped}"' 3039 3040 return escaped
3045 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3046 # Output the Teradata column FORMAT override. 3047 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3048 this = self.sql(expression, "this") 3049 fmt = self.sql(expression, "format") 3050 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3052 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3053 null_handling = expression.args.get("null_handling") 3054 null_handling = f" {null_handling}" if null_handling else "" 3055 3056 unique_keys = expression.args.get("unique_keys") 3057 if unique_keys is not None: 3058 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3059 else: 3060 unique_keys = "" 3061 3062 return_type = self.sql(expression, "return_type") 3063 return_type = f" RETURNING {return_type}" if return_type else "" 3064 encoding = self.sql(expression, "encoding") 3065 encoding = f" ENCODING {encoding}" if encoding else "" 3066 3067 return self.func( 3068 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3069 *expression.expressions, 3070 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3071 )
3076 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3077 null_handling = expression.args.get("null_handling") 3078 null_handling = f" {null_handling}" if null_handling else "" 3079 return_type = self.sql(expression, "return_type") 3080 return_type = f" RETURNING {return_type}" if return_type else "" 3081 strict = " STRICT" if expression.args.get("strict") else "" 3082 return self.func( 3083 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3084 )
3086 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3087 this = self.sql(expression, "this") 3088 order = self.sql(expression, "order") 3089 null_handling = expression.args.get("null_handling") 3090 null_handling = f" {null_handling}" if null_handling else "" 3091 return_type = self.sql(expression, "return_type") 3092 return_type = f" RETURNING {return_type}" if return_type else "" 3093 strict = " STRICT" if expression.args.get("strict") else "" 3094 return self.func( 3095 "JSON_ARRAYAGG", 3096 this, 3097 suffix=f"{order}{null_handling}{return_type}{strict})", 3098 )
3100 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3101 path = self.sql(expression, "path") 3102 path = f" PATH {path}" if path else "" 3103 nested_schema = self.sql(expression, "nested_schema") 3104 3105 if nested_schema: 3106 return f"NESTED{path} {nested_schema}" 3107 3108 this = self.sql(expression, "this") 3109 kind = self.sql(expression, "kind") 3110 kind = f" {kind}" if kind else "" 3111 return f"{this}{kind}{path}"
3116 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3117 this = self.sql(expression, "this") 3118 path = self.sql(expression, "path") 3119 path = f", {path}" if path else "" 3120 error_handling = expression.args.get("error_handling") 3121 error_handling = f" {error_handling}" if error_handling else "" 3122 empty_handling = expression.args.get("empty_handling") 3123 empty_handling = f" {empty_handling}" if empty_handling else "" 3124 schema = self.sql(expression, "schema") 3125 return self.func( 3126 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3127 )
3129 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3130 this = self.sql(expression, "this") 3131 kind = self.sql(expression, "kind") 3132 path = self.sql(expression, "path") 3133 path = f" {path}" if path else "" 3134 as_json = " AS JSON" if expression.args.get("as_json") else "" 3135 return f"{this} {kind}{path}{as_json}"
3137 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3138 this = self.sql(expression, "this") 3139 path = self.sql(expression, "path") 3140 path = f", {path}" if path else "" 3141 expressions = self.expressions(expression) 3142 with_ = ( 3143 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3144 if expressions 3145 else "" 3146 ) 3147 return f"OPENJSON({this}{path}){with_}"
3149 def in_sql(self, expression: exp.In) -> str: 3150 query = expression.args.get("query") 3151 unnest = expression.args.get("unnest") 3152 field = expression.args.get("field") 3153 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3154 3155 if query: 3156 in_sql = self.sql(query) 3157 elif unnest: 3158 in_sql = self.in_unnest_op(unnest) 3159 elif field: 3160 in_sql = self.sql(field) 3161 else: 3162 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3163 3164 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3169 def interval_sql(self, expression: exp.Interval) -> str: 3170 unit = self.sql(expression, "unit") 3171 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3172 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3173 unit = f" {unit}" if unit else "" 3174 3175 if self.SINGLE_STRING_INTERVAL: 3176 this = expression.this.name if expression.this else "" 3177 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3178 3179 this = self.sql(expression, "this") 3180 if this: 3181 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3182 this = f" {this}" if unwrapped else f" ({this})" 3183 3184 return f"INTERVAL{this}{unit}"
3189 def reference_sql(self, expression: exp.Reference) -> str: 3190 this = self.sql(expression, "this") 3191 expressions = self.expressions(expression, flat=True) 3192 expressions = f"({expressions})" if expressions else "" 3193 options = self.expressions(expression, key="options", flat=True, sep=" ") 3194 options = f" {options}" if options else "" 3195 return f"REFERENCES {this}{expressions}{options}"
3197 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3198 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3199 parent = expression.parent 3200 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3201 return self.func( 3202 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3203 )
3223 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3224 alias = expression.args["alias"] 3225 3226 parent = expression.parent 3227 pivot = parent and parent.parent 3228 3229 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3230 identifier_alias = isinstance(alias, exp.Identifier) 3231 literal_alias = isinstance(alias, exp.Literal) 3232 3233 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3234 alias.replace(exp.Literal.string(alias.output_name)) 3235 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3236 alias.replace(exp.to_identifier(alias.output_name)) 3237 3238 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:
3276 def connector_sql( 3277 self, 3278 expression: exp.Connector, 3279 op: str, 3280 stack: t.Optional[t.List[str | exp.Expression]] = None, 3281 ) -> str: 3282 if stack is not None: 3283 if expression.expressions: 3284 stack.append(self.expressions(expression, sep=f" {op} ")) 3285 else: 3286 stack.append(expression.right) 3287 if expression.comments and self.comments: 3288 for comment in expression.comments: 3289 if comment: 3290 op += f" /*{self.sanitize_comment(comment)}*/" 3291 stack.extend((op, expression.left)) 3292 return op 3293 3294 stack = [expression] 3295 sqls: t.List[str] = [] 3296 ops = set() 3297 3298 while stack: 3299 node = stack.pop() 3300 if isinstance(node, exp.Connector): 3301 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3302 else: 3303 sql = self.sql(node) 3304 if sqls and sqls[-1] in ops: 3305 sqls[-1] += f" {sql}" 3306 else: 3307 sqls.append(sql) 3308 3309 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3310 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3330 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3331 format_sql = self.sql(expression, "format") 3332 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3333 to_sql = self.sql(expression, "to") 3334 to_sql = f" {to_sql}" if to_sql else "" 3335 action = self.sql(expression, "action") 3336 action = f" {action}" if action else "" 3337 default = self.sql(expression, "default") 3338 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3339 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3353 def comment_sql(self, expression: exp.Comment) -> str: 3354 this = self.sql(expression, "this") 3355 kind = expression.args["kind"] 3356 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3357 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3358 expression_sql = self.sql(expression, "expression") 3359 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3361 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3362 this = self.sql(expression, "this") 3363 delete = " DELETE" if expression.args.get("delete") else "" 3364 recompress = self.sql(expression, "recompress") 3365 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3366 to_disk = self.sql(expression, "to_disk") 3367 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3368 to_volume = self.sql(expression, "to_volume") 3369 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3370 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3372 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3373 where = self.sql(expression, "where") 3374 group = self.sql(expression, "group") 3375 aggregates = self.expressions(expression, key="aggregates") 3376 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3377 3378 if not (where or group or aggregates) and len(expression.expressions) == 1: 3379 return f"TTL {self.expressions(expression, flat=True)}" 3380 3381 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3398 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3399 this = self.sql(expression, "this") 3400 3401 dtype = self.sql(expression, "dtype") 3402 if dtype: 3403 collate = self.sql(expression, "collate") 3404 collate = f" COLLATE {collate}" if collate else "" 3405 using = self.sql(expression, "using") 3406 using = f" USING {using}" if using else "" 3407 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3408 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3409 3410 default = self.sql(expression, "default") 3411 if default: 3412 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3413 3414 comment = self.sql(expression, "comment") 3415 if comment: 3416 return f"ALTER COLUMN {this} COMMENT {comment}" 3417 3418 visible = expression.args.get("visible") 3419 if visible: 3420 return f"ALTER COLUMN {this} SET {visible}" 3421 3422 allow_null = expression.args.get("allow_null") 3423 drop = expression.args.get("drop") 3424 3425 if not drop and not allow_null: 3426 self.unsupported("Unsupported ALTER COLUMN syntax") 3427 3428 if allow_null is not None: 3429 keyword = "DROP" if drop else "SET" 3430 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3431 3432 return f"ALTER COLUMN {this} DROP DEFAULT"
3448 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3449 compound = " COMPOUND" if expression.args.get("compound") else "" 3450 this = self.sql(expression, "this") 3451 expressions = self.expressions(expression, flat=True) 3452 expressions = f"({expressions})" if expressions else "" 3453 return f"ALTER{compound} SORTKEY {this or expressions}"
3455 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3456 if not self.RENAME_TABLE_WITH_DB: 3457 # Remove db from tables 3458 expression = expression.transform( 3459 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3460 ).assert_is(exp.AlterRename) 3461 this = self.sql(expression, "this") 3462 return f"RENAME TO {this}"
3477 def alter_sql(self, expression: exp.Alter) -> str: 3478 actions = expression.args["actions"] 3479 3480 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3481 actions[0], exp.ColumnDef 3482 ): 3483 actions_sql = self.expressions(expression, key="actions", flat=True) 3484 actions_sql = f"ADD {actions_sql}" 3485 else: 3486 actions_list = [] 3487 for action in actions: 3488 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3489 action_sql = self.add_column_sql(action) 3490 else: 3491 action_sql = self.sql(action) 3492 if isinstance(action, exp.Query): 3493 action_sql = f"AS {action_sql}" 3494 3495 actions_list.append(action_sql) 3496 3497 actions_sql = self.format_args(*actions_list).lstrip("\n") 3498 3499 exists = " IF EXISTS" if expression.args.get("exists") else "" 3500 on_cluster = self.sql(expression, "cluster") 3501 on_cluster = f" {on_cluster}" if on_cluster else "" 3502 only = " ONLY" if expression.args.get("only") else "" 3503 options = self.expressions(expression, key="options") 3504 options = f", {options}" if options else "" 3505 kind = self.sql(expression, "kind") 3506 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3507 3508 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3510 def add_column_sql(self, expression: exp.Expression) -> str: 3511 sql = self.sql(expression) 3512 if isinstance(expression, exp.Schema): 3513 column_text = " COLUMNS" 3514 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3515 column_text = " COLUMN" 3516 else: 3517 column_text = "" 3518 3519 return f"ADD{column_text} {sql}"
3533 def distinct_sql(self, expression: exp.Distinct) -> str: 3534 this = self.expressions(expression, flat=True) 3535 3536 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3537 case = exp.case() 3538 for arg in expression.expressions: 3539 case = case.when(arg.is_(exp.null()), exp.null()) 3540 this = self.sql(case.else_(f"({this})")) 3541 3542 this = f" {this}" if this else "" 3543 3544 on = self.sql(expression, "on") 3545 on = f" ON {on}" if on else "" 3546 return f"DISTINCT{this}{on}"
3575 def div_sql(self, expression: exp.Div) -> str: 3576 l, r = expression.left, expression.right 3577 3578 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3579 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3580 3581 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3582 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3583 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3584 3585 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3586 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3587 return self.sql( 3588 exp.cast( 3589 l / r, 3590 to=exp.DataType.Type.BIGINT, 3591 ) 3592 ) 3593 3594 return self.binary(expression, "/")
3690 def log_sql(self, expression: exp.Log) -> str: 3691 this = expression.this 3692 expr = expression.expression 3693 3694 if self.dialect.LOG_BASE_FIRST is False: 3695 this, expr = expr, this 3696 elif self.dialect.LOG_BASE_FIRST is None and expr: 3697 if this.name in ("2", "10"): 3698 return self.func(f"LOG{this.name}", expr) 3699 3700 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3701 3702 return self.func("LOG", this, expr)
3711 def binary(self, expression: exp.Binary, op: str) -> str: 3712 sqls: t.List[str] = [] 3713 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3714 binary_type = type(expression) 3715 3716 while stack: 3717 node = stack.pop() 3718 3719 if type(node) is binary_type: 3720 op_func = node.args.get("operator") 3721 if op_func: 3722 op = f"OPERATOR({self.sql(op_func)})" 3723 3724 stack.append(node.right) 3725 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3726 stack.append(node.left) 3727 else: 3728 sqls.append(self.sql(node)) 3729 3730 return "".join(sqls)
3739 def function_fallback_sql(self, expression: exp.Func) -> str: 3740 args = [] 3741 3742 for key in expression.arg_types: 3743 arg_value = expression.args.get(key) 3744 3745 if isinstance(arg_value, list): 3746 for value in arg_value: 3747 args.append(value) 3748 elif arg_value is not None: 3749 args.append(arg_value) 3750 3751 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3752 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3753 else: 3754 name = expression.sql_name() 3755 3756 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:
3758 def func( 3759 self, 3760 name: str, 3761 *args: t.Optional[exp.Expression | str], 3762 prefix: str = "(", 3763 suffix: str = ")", 3764 normalize: bool = True, 3765 ) -> str: 3766 name = self.normalize_func(name) if normalize else name 3767 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3769 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3770 arg_sqls = tuple( 3771 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3772 ) 3773 if self.pretty and self.too_wide(arg_sqls): 3774 return self.indent( 3775 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3776 ) 3777 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]:
3782 def format_time( 3783 self, 3784 expression: exp.Expression, 3785 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3786 inverse_time_trie: t.Optional[t.Dict] = None, 3787 ) -> t.Optional[str]: 3788 return format_time( 3789 self.sql(expression, "format"), 3790 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3791 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3792 )
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:
3794 def expressions( 3795 self, 3796 expression: t.Optional[exp.Expression] = None, 3797 key: t.Optional[str] = None, 3798 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3799 flat: bool = False, 3800 indent: bool = True, 3801 skip_first: bool = False, 3802 skip_last: bool = False, 3803 sep: str = ", ", 3804 prefix: str = "", 3805 dynamic: bool = False, 3806 new_line: bool = False, 3807 ) -> str: 3808 expressions = expression.args.get(key or "expressions") if expression else sqls 3809 3810 if not expressions: 3811 return "" 3812 3813 if flat: 3814 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3815 3816 num_sqls = len(expressions) 3817 result_sqls = [] 3818 3819 for i, e in enumerate(expressions): 3820 sql = self.sql(e, comment=False) 3821 if not sql: 3822 continue 3823 3824 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3825 3826 if self.pretty: 3827 if self.leading_comma: 3828 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3829 else: 3830 result_sqls.append( 3831 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3832 ) 3833 else: 3834 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3835 3836 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3837 if new_line: 3838 result_sqls.insert(0, "") 3839 result_sqls.append("") 3840 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3841 else: 3842 result_sql = "".join(result_sqls) 3843 3844 return ( 3845 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3846 if indent 3847 else result_sql 3848 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3850 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3851 flat = flat or isinstance(expression.parent, exp.Properties) 3852 expressions_sql = self.expressions(expression, flat=flat) 3853 if flat: 3854 return f"{op} {expressions_sql}" 3855 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3857 def naked_property(self, expression: exp.Property) -> str: 3858 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3859 if not property_name: 3860 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3861 return f"{property_name} {self.sql(expression, 'this')}"
3869 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3870 this = self.sql(expression, "this") 3871 expressions = self.no_identify(self.expressions, expression) 3872 expressions = ( 3873 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3874 ) 3875 return f"{this}{expressions}" if expressions.strip() != "" else this
3885 def when_sql(self, expression: exp.When) -> str: 3886 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3887 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3888 condition = self.sql(expression, "condition") 3889 condition = f" AND {condition}" if condition else "" 3890 3891 then_expression = expression.args.get("then") 3892 if isinstance(then_expression, exp.Insert): 3893 this = self.sql(then_expression, "this") 3894 this = f"INSERT {this}" if this else "INSERT" 3895 then = self.sql(then_expression, "expression") 3896 then = f"{this} VALUES {then}" if then else this 3897 elif isinstance(then_expression, exp.Update): 3898 if isinstance(then_expression.args.get("expressions"), exp.Star): 3899 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3900 else: 3901 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3902 else: 3903 then = self.sql(then_expression) 3904 return f"WHEN {matched}{source}{condition} THEN {then}"
3909 def merge_sql(self, expression: exp.Merge) -> str: 3910 table = expression.this 3911 table_alias = "" 3912 3913 hints = table.args.get("hints") 3914 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3915 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3916 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3917 3918 this = self.sql(table) 3919 using = f"USING {self.sql(expression, 'using')}" 3920 on = f"ON {self.sql(expression, 'on')}" 3921 whens = self.sql(expression, "whens") 3922 3923 returning = self.sql(expression, "returning") 3924 if returning: 3925 whens = f"{whens}{returning}" 3926 3927 sep = self.sep() 3928 3929 return self.prepend_ctes( 3930 expression, 3931 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3932 )
3938 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3939 if not self.SUPPORTS_TO_NUMBER: 3940 self.unsupported("Unsupported TO_NUMBER function") 3941 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3942 3943 fmt = expression.args.get("format") 3944 if not fmt: 3945 self.unsupported("Conversion format is required for TO_NUMBER") 3946 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3947 3948 return self.func("TO_NUMBER", expression.this, fmt)
3950 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3951 this = self.sql(expression, "this") 3952 kind = self.sql(expression, "kind") 3953 settings_sql = self.expressions(expression, key="settings", sep=" ") 3954 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3955 return f"{this}({kind}{args})"
3974 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3975 expressions = self.expressions(expression, flat=True) 3976 expressions = f" {self.wrap(expressions)}" if expressions else "" 3977 buckets = self.sql(expression, "buckets") 3978 kind = self.sql(expression, "kind") 3979 buckets = f" BUCKETS {buckets}" if buckets else "" 3980 order = self.sql(expression, "order") 3981 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
3986 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3987 expressions = self.expressions(expression, key="expressions", flat=True) 3988 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3989 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3990 buckets = self.sql(expression, "buckets") 3991 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
3993 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3994 this = self.sql(expression, "this") 3995 having = self.sql(expression, "having") 3996 3997 if having: 3998 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3999 4000 return self.func("ANY_VALUE", this)
4002 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4003 transform = self.func("TRANSFORM", *expression.expressions) 4004 row_format_before = self.sql(expression, "row_format_before") 4005 row_format_before = f" {row_format_before}" if row_format_before else "" 4006 record_writer = self.sql(expression, "record_writer") 4007 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4008 using = f" USING {self.sql(expression, 'command_script')}" 4009 schema = self.sql(expression, "schema") 4010 schema = f" AS {schema}" if schema else "" 4011 row_format_after = self.sql(expression, "row_format_after") 4012 row_format_after = f" {row_format_after}" if row_format_after else "" 4013 record_reader = self.sql(expression, "record_reader") 4014 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4015 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4017 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4018 key_block_size = self.sql(expression, "key_block_size") 4019 if key_block_size: 4020 return f"KEY_BLOCK_SIZE = {key_block_size}" 4021 4022 using = self.sql(expression, "using") 4023 if using: 4024 return f"USING {using}" 4025 4026 parser = self.sql(expression, "parser") 4027 if parser: 4028 return f"WITH PARSER {parser}" 4029 4030 comment = self.sql(expression, "comment") 4031 if comment: 4032 return f"COMMENT {comment}" 4033 4034 visible = expression.args.get("visible") 4035 if visible is not None: 4036 return "VISIBLE" if visible else "INVISIBLE" 4037 4038 engine_attr = self.sql(expression, "engine_attr") 4039 if engine_attr: 4040 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4041 4042 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4043 if secondary_engine_attr: 4044 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4045 4046 self.unsupported("Unsupported index constraint option.") 4047 return ""
4053 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4054 kind = self.sql(expression, "kind") 4055 kind = f"{kind} INDEX" if kind else "INDEX" 4056 this = self.sql(expression, "this") 4057 this = f" {this}" if this else "" 4058 index_type = self.sql(expression, "index_type") 4059 index_type = f" USING {index_type}" if index_type else "" 4060 expressions = self.expressions(expression, flat=True) 4061 expressions = f" ({expressions})" if expressions else "" 4062 options = self.expressions(expression, key="options", sep=" ") 4063 options = f" {options}" if options else "" 4064 return f"{kind}{this}{index_type}{expressions}{options}"
4066 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4067 if self.NVL2_SUPPORTED: 4068 return self.function_fallback_sql(expression) 4069 4070 case = exp.Case().when( 4071 expression.this.is_(exp.null()).not_(copy=False), 4072 expression.args["true"], 4073 copy=False, 4074 ) 4075 else_cond = expression.args.get("false") 4076 if else_cond: 4077 case.else_(else_cond, copy=False) 4078 4079 return self.sql(case)
4081 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4082 this = self.sql(expression, "this") 4083 expr = self.sql(expression, "expression") 4084 iterator = self.sql(expression, "iterator") 4085 condition = self.sql(expression, "condition") 4086 condition = f" IF {condition}" if condition else "" 4087 return f"{this} FOR {expr} IN {iterator}{condition}"
4095 def predict_sql(self, expression: exp.Predict) -> str: 4096 model = self.sql(expression, "this") 4097 model = f"MODEL {model}" 4098 table = self.sql(expression, "expression") 4099 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4100 parameters = self.sql(expression, "params_struct") 4101 return self.func("PREDICT", model, table, parameters or None)
4113 def toarray_sql(self, expression: exp.ToArray) -> str: 4114 arg = expression.this 4115 if not arg.type: 4116 from sqlglot.optimizer.annotate_types import annotate_types 4117 4118 arg = annotate_types(arg, dialect=self.dialect) 4119 4120 if arg.is_type(exp.DataType.Type.ARRAY): 4121 return self.sql(arg) 4122 4123 cond_for_null = arg.is_(exp.null()) 4124 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4126 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4127 this = expression.this 4128 time_format = self.format_time(expression) 4129 4130 if time_format: 4131 return self.sql( 4132 exp.cast( 4133 exp.StrToTime(this=this, format=expression.args["format"]), 4134 exp.DataType.Type.TIME, 4135 ) 4136 ) 4137 4138 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4139 return self.sql(this) 4140 4141 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4143 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4144 this = expression.this 4145 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4146 return self.sql(this) 4147 4148 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4150 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4151 this = expression.this 4152 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4153 return self.sql(this) 4154 4155 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4157 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4158 this = expression.this 4159 time_format = self.format_time(expression) 4160 4161 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4162 return self.sql( 4163 exp.cast( 4164 exp.StrToTime(this=this, format=expression.args["format"]), 4165 exp.DataType.Type.DATE, 4166 ) 4167 ) 4168 4169 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4170 return self.sql(this) 4171 4172 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4184 def lastday_sql(self, expression: exp.LastDay) -> str: 4185 if self.LAST_DAY_SUPPORTS_DATE_PART: 4186 return self.function_fallback_sql(expression) 4187 4188 unit = expression.text("unit") 4189 if unit and unit != "MONTH": 4190 self.unsupported("Date parts are not supported in LAST_DAY.") 4191 4192 return self.func("LAST_DAY", expression.this)
4201 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4202 if self.CAN_IMPLEMENT_ARRAY_ANY: 4203 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4204 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4205 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4206 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4207 4208 from sqlglot.dialects import Dialect 4209 4210 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4211 if self.dialect.__class__ != Dialect: 4212 self.unsupported("ARRAY_ANY is unsupported") 4213 4214 return self.function_fallback_sql(expression)
4216 def struct_sql(self, expression: exp.Struct) -> str: 4217 expression.set( 4218 "expressions", 4219 [ 4220 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4221 if isinstance(e, exp.PropertyEQ) 4222 else e 4223 for e in expression.expressions 4224 ], 4225 ) 4226 4227 return self.function_fallback_sql(expression)
4235 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4236 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4237 tables = f" {self.expressions(expression)}" 4238 4239 exists = " IF EXISTS" if expression.args.get("exists") else "" 4240 4241 on_cluster = self.sql(expression, "cluster") 4242 on_cluster = f" {on_cluster}" if on_cluster else "" 4243 4244 identity = self.sql(expression, "identity") 4245 identity = f" {identity} IDENTITY" if identity else "" 4246 4247 option = self.sql(expression, "option") 4248 option = f" {option}" if option else "" 4249 4250 partition = self.sql(expression, "partition") 4251 partition = f" {partition}" if partition else "" 4252 4253 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4257 def convert_sql(self, expression: exp.Convert) -> str: 4258 to = expression.this 4259 value = expression.expression 4260 style = expression.args.get("style") 4261 safe = expression.args.get("safe") 4262 strict = expression.args.get("strict") 4263 4264 if not to or not value: 4265 return "" 4266 4267 # Retrieve length of datatype and override to default if not specified 4268 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4269 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4270 4271 transformed: t.Optional[exp.Expression] = None 4272 cast = exp.Cast if strict else exp.TryCast 4273 4274 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4275 if isinstance(style, exp.Literal) and style.is_int: 4276 from sqlglot.dialects.tsql import TSQL 4277 4278 style_value = style.name 4279 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4280 if not converted_style: 4281 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4282 4283 fmt = exp.Literal.string(converted_style) 4284 4285 if to.this == exp.DataType.Type.DATE: 4286 transformed = exp.StrToDate(this=value, format=fmt) 4287 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4288 transformed = exp.StrToTime(this=value, format=fmt) 4289 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4290 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4291 elif to.this == exp.DataType.Type.TEXT: 4292 transformed = exp.TimeToStr(this=value, format=fmt) 4293 4294 if not transformed: 4295 transformed = cast(this=value, to=to, safe=safe) 4296 4297 return self.sql(transformed)
4365 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4366 option = self.sql(expression, "this") 4367 4368 if expression.expressions: 4369 upper = option.upper() 4370 4371 # Snowflake FILE_FORMAT options are separated by whitespace 4372 sep = " " if upper == "FILE_FORMAT" else ", " 4373 4374 # Databricks copy/format options do not set their list of values with EQ 4375 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4376 values = self.expressions(expression, flat=True, sep=sep) 4377 return f"{option}{op}({values})" 4378 4379 value = self.sql(expression, "expression") 4380 4381 if not value: 4382 return option 4383 4384 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4385 4386 return f"{option}{op}{value}"
4388 def credentials_sql(self, expression: exp.Credentials) -> str: 4389 cred_expr = expression.args.get("credentials") 4390 if isinstance(cred_expr, exp.Literal): 4391 # Redshift case: CREDENTIALS <string> 4392 credentials = self.sql(expression, "credentials") 4393 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4394 else: 4395 # Snowflake case: CREDENTIALS = (...) 4396 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4397 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4398 4399 storage = self.sql(expression, "storage") 4400 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4401 4402 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4403 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4404 4405 iam_role = self.sql(expression, "iam_role") 4406 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4407 4408 region = self.sql(expression, "region") 4409 region = f" REGION {region}" if region else "" 4410 4411 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4413 def copy_sql(self, expression: exp.Copy) -> str: 4414 this = self.sql(expression, "this") 4415 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4416 4417 credentials = self.sql(expression, "credentials") 4418 credentials = self.seg(credentials) if credentials else "" 4419 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4420 files = self.expressions(expression, key="files", flat=True) 4421 4422 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4423 params = self.expressions( 4424 expression, 4425 key="params", 4426 sep=sep, 4427 new_line=True, 4428 skip_last=True, 4429 skip_first=True, 4430 indent=self.COPY_PARAMS_ARE_WRAPPED, 4431 ) 4432 4433 if params: 4434 if self.COPY_PARAMS_ARE_WRAPPED: 4435 params = f" WITH ({params})" 4436 elif not self.pretty: 4437 params = f" {params}" 4438 4439 return f"COPY{this}{kind} {files}{credentials}{params}"
4444 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4445 on_sql = "ON" if expression.args.get("on") else "OFF" 4446 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4447 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4448 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4449 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4450 4451 if filter_col or retention_period: 4452 on_sql = self.func("ON", filter_col, retention_period) 4453 4454 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4456 def maskingpolicycolumnconstraint_sql( 4457 self, expression: exp.MaskingPolicyColumnConstraint 4458 ) -> str: 4459 this = self.sql(expression, "this") 4460 expressions = self.expressions(expression, flat=True) 4461 expressions = f" USING ({expressions})" if expressions else "" 4462 return f"MASKING POLICY {this}{expressions}"
4472 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4473 this = self.sql(expression, "this") 4474 expr = expression.expression 4475 4476 if isinstance(expr, exp.Func): 4477 # T-SQL's CLR functions are case sensitive 4478 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4479 else: 4480 expr = self.sql(expression, "expression") 4481 4482 return self.scope_resolution(expr, this)
4490 def rand_sql(self, expression: exp.Rand) -> str: 4491 lower = self.sql(expression, "lower") 4492 upper = self.sql(expression, "upper") 4493 4494 if lower and upper: 4495 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4496 return self.func("RAND", expression.this)
4498 def changes_sql(self, expression: exp.Changes) -> str: 4499 information = self.sql(expression, "information") 4500 information = f"INFORMATION => {information}" 4501 at_before = self.sql(expression, "at_before") 4502 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4503 end = self.sql(expression, "end") 4504 end = f"{self.seg('')}{end}" if end else "" 4505 4506 return f"CHANGES ({information}){at_before}{end}"
4508 def pad_sql(self, expression: exp.Pad) -> str: 4509 prefix = "L" if expression.args.get("is_left") else "R" 4510 4511 fill_pattern = self.sql(expression, "fill_pattern") or None 4512 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4513 fill_pattern = "' '" 4514 4515 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4521 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4522 generate_series = exp.GenerateSeries(**expression.args) 4523 4524 parent = expression.parent 4525 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4526 parent = parent.parent 4527 4528 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4529 return self.sql(exp.Unnest(expressions=[generate_series])) 4530 4531 if isinstance(parent, exp.Select): 4532 self.unsupported("GenerateSeries projection unnesting is not supported.") 4533 4534 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4536 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4537 exprs = expression.expressions 4538 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4539 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4540 else: 4541 rhs = self.expressions(expression) 4542 4543 return self.func(name, expression.this, rhs or None)
4545 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4546 if self.SUPPORTS_CONVERT_TIMEZONE: 4547 return self.function_fallback_sql(expression) 4548 4549 source_tz = expression.args.get("source_tz") 4550 target_tz = expression.args.get("target_tz") 4551 timestamp = expression.args.get("timestamp") 4552 4553 if source_tz and timestamp: 4554 timestamp = exp.AtTimeZone( 4555 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4556 ) 4557 4558 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4559 4560 return self.sql(expr)
4562 def json_sql(self, expression: exp.JSON) -> str: 4563 this = self.sql(expression, "this") 4564 this = f" {this}" if this else "" 4565 4566 _with = expression.args.get("with") 4567 4568 if _with is None: 4569 with_sql = "" 4570 elif not _with: 4571 with_sql = " WITHOUT" 4572 else: 4573 with_sql = " WITH" 4574 4575 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4576 4577 return f"JSON{this}{with_sql}{unique_sql}"
4579 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4580 def _generate_on_options(arg: t.Any) -> str: 4581 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4582 4583 path = self.sql(expression, "path") 4584 returning = self.sql(expression, "returning") 4585 returning = f" RETURNING {returning}" if returning else "" 4586 4587 on_condition = self.sql(expression, "on_condition") 4588 on_condition = f" {on_condition}" if on_condition else "" 4589 4590 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4592 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4593 else_ = "ELSE " if expression.args.get("else_") else "" 4594 condition = self.sql(expression, "expression") 4595 condition = f"WHEN {condition} THEN " if condition else else_ 4596 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4597 return f"{condition}{insert}"
4605 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4606 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4607 empty = expression.args.get("empty") 4608 empty = ( 4609 f"DEFAULT {empty} ON EMPTY" 4610 if isinstance(empty, exp.Expression) 4611 else self.sql(expression, "empty") 4612 ) 4613 4614 error = expression.args.get("error") 4615 error = ( 4616 f"DEFAULT {error} ON ERROR" 4617 if isinstance(error, exp.Expression) 4618 else self.sql(expression, "error") 4619 ) 4620 4621 if error and empty: 4622 error = ( 4623 f"{empty} {error}" 4624 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4625 else f"{error} {empty}" 4626 ) 4627 empty = "" 4628 4629 null = self.sql(expression, "null") 4630 4631 return f"{empty}{error}{null}"
4637 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4638 this = self.sql(expression, "this") 4639 path = self.sql(expression, "path") 4640 4641 passing = self.expressions(expression, "passing") 4642 passing = f" PASSING {passing}" if passing else "" 4643 4644 on_condition = self.sql(expression, "on_condition") 4645 on_condition = f" {on_condition}" if on_condition else "" 4646 4647 path = f"{path}{passing}{on_condition}" 4648 4649 return self.func("JSON_EXISTS", this, path)
4651 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4652 array_agg = self.function_fallback_sql(expression) 4653 4654 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4655 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4656 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4657 parent = expression.parent 4658 if isinstance(parent, exp.Filter): 4659 parent_cond = parent.expression.this 4660 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4661 else: 4662 this = expression.this 4663 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4664 if this.find(exp.Column): 4665 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4666 this_sql = ( 4667 self.expressions(this) 4668 if isinstance(this, exp.Distinct) 4669 else self.sql(expression, "this") 4670 ) 4671 4672 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4673 4674 return array_agg
4682 def grant_sql(self, expression: exp.Grant) -> str: 4683 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4684 4685 kind = self.sql(expression, "kind") 4686 kind = f" {kind}" if kind else "" 4687 4688 securable = self.sql(expression, "securable") 4689 securable = f" {securable}" if securable else "" 4690 4691 principals = self.expressions(expression, key="principals", flat=True) 4692 4693 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4694 4695 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4719 def overlay_sql(self, expression: exp.Overlay): 4720 this = self.sql(expression, "this") 4721 expr = self.sql(expression, "expression") 4722 from_sql = self.sql(expression, "from") 4723 for_sql = self.sql(expression, "for") 4724 for_sql = f" FOR {for_sql}" if for_sql else "" 4725 4726 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4732 def string_sql(self, expression: exp.String) -> str: 4733 this = expression.this 4734 zone = expression.args.get("zone") 4735 4736 if zone: 4737 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4738 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4739 # set for source_tz to transpile the time conversion before the STRING cast 4740 this = exp.ConvertTimezone( 4741 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4742 ) 4743 4744 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4754 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4755 filler = self.sql(expression, "this") 4756 filler = f" {filler}" if filler else "" 4757 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4758 return f"TRUNCATE{filler} {with_count}"
4760 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4761 if self.SUPPORTS_UNIX_SECONDS: 4762 return self.function_fallback_sql(expression) 4763 4764 start_ts = exp.cast( 4765 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4766 ) 4767 4768 return self.sql( 4769 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4770 )
4772 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4773 dim = expression.expression 4774 4775 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4776 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4777 if not (dim.is_int and dim.name == "1"): 4778 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4779 dim = None 4780 4781 # If dimension is required but not specified, default initialize it 4782 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4783 dim = exp.Literal.number(1) 4784 4785 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4787 def attach_sql(self, expression: exp.Attach) -> str: 4788 this = self.sql(expression, "this") 4789 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4790 expressions = self.expressions(expression) 4791 expressions = f" ({expressions})" if expressions else "" 4792 4793 return f"ATTACH{exists_sql} {this}{expressions}"
4795 def detach_sql(self, expression: exp.Detach) -> str: 4796 this = self.sql(expression, "this") 4797 # the DATABASE keyword is required if IF EXISTS is set 4798 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4799 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4800 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4801 4802 return f"DETACH{exists_sql} {this}"
4810 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4811 this_sql = self.sql(expression, "this") 4812 if isinstance(expression.this, exp.Table): 4813 this_sql = f"TABLE {this_sql}" 4814 4815 return self.func( 4816 "FEATURES_AT_TIME", 4817 this_sql, 4818 expression.args.get("time"), 4819 expression.args.get("num_rows"), 4820 expression.args.get("ignore_feature_nulls"), 4821 )
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4828 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4829 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4830 encode = f"{encode} {self.sql(expression, 'this')}" 4831 4832 properties = expression.args.get("properties") 4833 if properties: 4834 encode = f"{encode} {self.properties(properties)}" 4835 4836 return encode
4838 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4839 this = self.sql(expression, "this") 4840 include = f"INCLUDE {this}" 4841 4842 column_def = self.sql(expression, "column_def") 4843 if column_def: 4844 include = f"{include} {column_def}" 4845 4846 alias = self.sql(expression, "alias") 4847 if alias: 4848 include = f"{include} AS {alias}" 4849 4850 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
4862 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4863 partitions = self.expressions(expression, "partition_expressions") 4864 create = self.expressions(expression, "create_expressions") 4865 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
4867 def partitionbyrangepropertydynamic_sql( 4868 self, expression: exp.PartitionByRangePropertyDynamic 4869 ) -> str: 4870 start = self.sql(expression, "start") 4871 end = self.sql(expression, "end") 4872 4873 every = expression.args["every"] 4874 if isinstance(every, exp.Interval) and every.this.is_string: 4875 every.this.replace(exp.Literal.number(every.name)) 4876 4877 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4890 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4891 kind = self.sql(expression, "kind") 4892 option = self.sql(expression, "option") 4893 option = f" {option}" if option else "" 4894 this = self.sql(expression, "this") 4895 this = f" {this}" if this else "" 4896 columns = self.expressions(expression) 4897 columns = f" {columns}" if columns else "" 4898 return f"{kind}{option} STATISTICS{this}{columns}"
4900 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4901 this = self.sql(expression, "this") 4902 columns = self.expressions(expression) 4903 inner_expression = self.sql(expression, "expression") 4904 inner_expression = f" {inner_expression}" if inner_expression else "" 4905 update_options = self.sql(expression, "update_options") 4906 update_options = f" {update_options} UPDATE" if update_options else "" 4907 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
4918 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4919 kind = self.sql(expression, "kind") 4920 this = self.sql(expression, "this") 4921 this = f" {this}" if this else "" 4922 inner_expression = self.sql(expression, "expression") 4923 return f"VALIDATE {kind}{this}{inner_expression}"
4925 def analyze_sql(self, expression: exp.Analyze) -> str: 4926 options = self.expressions(expression, key="options", sep=" ") 4927 options = f" {options}" if options else "" 4928 kind = self.sql(expression, "kind") 4929 kind = f" {kind}" if kind else "" 4930 this = self.sql(expression, "this") 4931 this = f" {this}" if this else "" 4932 mode = self.sql(expression, "mode") 4933 mode = f" {mode}" if mode else "" 4934 properties = self.sql(expression, "properties") 4935 properties = f" {properties}" if properties else "" 4936 partition = self.sql(expression, "partition") 4937 partition = f" {partition}" if partition else "" 4938 inner_expression = self.sql(expression, "expression") 4939 inner_expression = f" {inner_expression}" if inner_expression else "" 4940 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4942 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4943 this = self.sql(expression, "this") 4944 namespaces = self.expressions(expression, key="namespaces") 4945 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4946 passing = self.expressions(expression, key="passing") 4947 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4948 columns = self.expressions(expression, key="columns") 4949 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4950 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4951 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4957 def export_sql(self, expression: exp.Export) -> str: 4958 this = self.sql(expression, "this") 4959 connection = self.sql(expression, "connection") 4960 connection = f"WITH CONNECTION {connection} " if connection else "" 4961 options = self.sql(expression, "options") 4962 return f"EXPORT DATA {connection}{options} AS {this}"
4967 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4968 variable = self.sql(expression, "this") 4969 default = self.sql(expression, "default") 4970 default = f" = {default}" if default else "" 4971 4972 kind = self.sql(expression, "kind") 4973 if isinstance(expression.args.get("kind"), exp.Schema): 4974 kind = f"TABLE {kind}" 4975 4976 return f"{variable} AS {kind}{default}"
4978 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 4979 kind = self.sql(expression, "kind") 4980 this = self.sql(expression, "this") 4981 set = self.sql(expression, "expression") 4982 using = self.sql(expression, "using") 4983 using = f" USING {using}" if using else "" 4984 4985 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 4986 4987 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5006 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5007 # Snowflake GET/PUT statements: 5008 # PUT <file> <internalStage> <properties> 5009 # GET <internalStage> <file> <properties> 5010 props = expression.args.get("properties") 5011 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5012 this = self.sql(expression, "this") 5013 target = self.sql(expression, "target") 5014 5015 if isinstance(expression, exp.Put): 5016 return f"PUT {this} {target}{props_sql}" 5017 else: 5018 return f"GET {target} {this}{props_sql}"
5026 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5027 if self.SUPPORTS_DECODE_CASE: 5028 return self.func("DECODE", *expression.expressions) 5029 5030 expression, *expressions = expression.expressions 5031 5032 ifs = [] 5033 for search, result in zip(expressions[::2], expressions[1::2]): 5034 if isinstance(search, exp.Literal): 5035 ifs.append(exp.If(this=expression.eq(search), true=result)) 5036 elif isinstance(search, exp.Null): 5037 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5038 else: 5039 if isinstance(search, exp.Binary): 5040 search = exp.paren(search) 5041 5042 cond = exp.or_( 5043 expression.eq(search), 5044 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5045 copy=False, 5046 ) 5047 ifs.append(exp.If(this=cond, true=result)) 5048 5049 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5050 return self.sql(case)